Поток Java блокируется при регистрации канала с помощью селектора, когда вызывается select (). Что делать? - PullRequest
14 голосов
/ 29 июня 2009

У меня есть основной вопрос. Почему и как метод регистра SelectableChannel может быть при блокировке вызова. Позвольте мне представить сценарий.

Я создал объект Selector в классе Register следующим образом.

private static Selector selector = Selector.open();

У меня также есть метод в том же классе (Register), чтобы зарегистрировать канал с помощью селектора.

public static SelectionKey registerChannel(SelectableChannel channel, int ops)
                             throws IOException {
   channel.configureBlocking(false);
   return channel.register(selector, ops);
}

И есть еще один класс с именем Request, в котором есть метод, который считывает данные из каналов, обрабатывает и вызывает следующий метод для регистрации канала.

selectonKey = Register.register(socketChannel, SelectionKey.OP_READ);

Здесь на этом этапе поток заблокирован, не давая понять, что он ждет. Я проверил, что селектор открыт. Пожалуйста, предоставьте мне некоторую помощь, чтобы понять, как я могу решить эту проблему. Могу ли я снять блокировку?

Любой вклад будет оценен.

Добавление к тому, что я описал. Дальнейшие тесты показали, что если метод Register.register вызывается из того же потока, он может зарегистрироваться, но после этого, если какой-то другой поток пытается вызвать метод, поток не продвигается.

Ответы [ 5 ]

35 голосов
/ 01 февраля 2010

Это базовая особенность большинства реализаций NIO, которая не очевидна из документации.

Вам нужно сделать все зарегистрированные вызовы из того же потока, который делает ваш выбор, или возникнут взаимоблокировки. Обычно это делается путем предоставления очереди регистрации / отмены регистрации / изменения процентов, которая записывается в, а затем вызывается selector.wakeup (). Когда выбранный поток просыпается, он проверяет очередь и выполняет любые запрошенные операции.

10 голосов
/ 11 июля 2009

Вам нужно использовать блокировку и синхронизировать вручную.

В том же потоке, в котором вы запускаете цикл выбора, есть ReentrantLock:

final ReentrantLock selectorLock = new ReentrantLock();

Тогда, когда вам нужно зарегистрироваться в селекторе, сделайте что-то вроде этого:

selectorLock.lock();
try {
    selector.wakeup();
    socketChannel.register(selector, ops);
} finally {
    selectorLock.unlock();
}

Наконец, во время вашего цикла, который вы вызываете accept (), что-то вроде этого:

selectorLock.lock();
selectorLock.unlock();

selector.select(500);

А затем продолжайте с остальной логикой.

Эта конструкция гарантирует, что вызов register() не будет блокироваться, поскольку между соответствующими вызовами wakeup() и register() никогда не будет другого select().

0 голосов
/ 30 января 2019

Я согласен с ответом @ Darron о том, что вы должны передавать register вызовы в цепочку селектора, но вы не должны использовать selector.wakeup, поскольку это вводило бы условия гонки (представьте, что цепочка селектора занята обработкой других регистраций и ваши wakeup не разбудить никого). К счастью, Java NIO предоставил Pipe, так что вы можете позволить селектору прослушивать как register вызовы, так и другие события.

По сути, вот что нужно сделать:

val registrationPipe = Pipe.open()
registrationPipe.source().configureBlocking(false)
registrationPipe.source().register(selector, SelectionKey.OP_READ)
// now start your selector thread

// now to register a call from other threads using message pleaseRegisterMe
registrationPipe.sink().write(pleaseRegisterMe)

// inside your selector thread
val selectionKey = iterator.next()
if (selectionKey.channel() === registrationPipe.source()) {
    registrationPipe.source().read(pleaseRegisterMe)
    // do something with the message pleaseRegisterMe and do the actual register
}

Вот полный рабочий пример .

0 голосов
/ 26 февраля 2014

Зарегистрируйте свой канал из любой темы:

synchronized (selectorLock2) {
   selector.wakeup();
   synchronized (selectorLock1) {
       channel.register(selector, ops);
   }
}

Ваш цикл выбора должен выглядеть следующим образом:

while (true) {
   synchronized (selectorLock1) {
       selector.select();
   }
   synchronized (selectorLock2) {}

   ....
}
0 голосов
/ 29 июня 2009

Вы пытались напечатать трассировку стека всех потоков в вашей программе (используя kill -QUIT в Unix или Ctrl + Break в Windows или используя утилиту jstack)?

AbstractSelectableChannel содержит блокировку, на которой configureBlocking и register необходимо синхронизировать. Эта блокировка также доступна через метод blockingLock(), и поэтому другой поток может потенциально удерживать блокировку, вызывая блокировку вашего регистрационного вызова на неопределенный срок (но без трассировки стека это трудно определить).

...