Как обнаружить вызов Selector.wakeup - PullRequest
5 голосов
/ 02 декабря 2008

Если бы я написал:

int selectedChannels = selector.select();
Set selectedKeys = selector.selectedKeys();
if ( selectedChannels != selectedKeys.size() ) {
    // Selector.select() returned because of a call to Selector.wakeup()
    // so do synchronization.
}
// Continue with handling selected channels.

Правильно ли он обнаружит звонок-пробуждение?

Backgroundinformation:

Я пишу сервер, который большую часть времени просто получает пакеты и сохраняет их в файле. Очень редко у приложения возникает необходимость отправить себе специальный пакет. Для этого он инициирует соединение (из другого потока) с сокетом сервера:

SocketChannel channel = SocketChannel.open();
channel.configureBlocking( false );
channel.connect( new InetSocketAddress( InetAddress.getLocalHost(), PORT ));
selector.wakeup();
SelectionKey key = channel.register( selector, SelectionKey.OP_CONNECT );

Проблема в том, что SelectableChannel.register () может блокироваться, если основной поток уже находится в Selector.select (). Чтобы этого не случилось, я вызываю Selector.wakeup (), который позволяет преждевременно возвращать основной поток из select (). Чтобы убедиться, что другой поток имеет возможность завершить вызов регистра, мне нужно будет синхронизировать основной поток, но мне придется делать это после каждого возврата из select (). Если бы я мог определить, вернулся ли он из select () из-за вызова wakeup (), то я мог бы оптимизировать его только для этого случая.

Итак, теоретически фрагмент кода верхнего уровня должен работать, но мне было интересно, будет ли он это делать только потому, что он опирается на какое-то неуказанное поведение?

Спасибо за любые подсказки.

Ответы [ 4 ]

3 голосов
/ 02 декабря 2008

Я бы предположил, что предложенный фрагмент не будет работать вообще, в соответствии с контрактами Selector#select() и Selector#selectedKeys(). От Селектор :

  • Набор выбранных ключей - это набор ключей, так что канал каждого ключа был обнаружен готовым по крайней мере к одной из операций, определенных в наборе интересов ключа во время предыдущей операции выбора. Этот набор возвращается методом selectedKeys.
public abstract int select(long timeout)
                throws IOException
    Returns:
        The number of keys, possibly zero, whose ready-operation sets were
        updated

Когда я это прочитал, размер набора selectedKeys всегда должен совпадать с числом, возвращаемым select по определению. Я заметил - как и вы, возможно, также - что некоторые реализации не совсем следуют документации, и фактически selectedKeys возвращает все ключи с обновленными наборами готовых операций, даже если они не были обновлены во время вызова select. Единственный другой индикатор того, что селектор проснулся из-за вызова wakeup, может заключаться в том, что количество клавиш равно нулю; однако любой из этих методов в лучшем случае был бы ненадежным.

Обычный способ справиться с этим, как подразумевается, через контроль параллелизма. Я не буду беспокоиться о времени выполнения здесь; это классический пример преждевременной оптимизации .

Если вы действительно не беспокоитесь о допусках в одну микросекунду, вы не заметите никакого замедления - и если вас беспокоит этот уровень допуска, то Selector не будет достаточно надежным для вас в любом случае.

Вот пример обычного механизма для этого, использующего ReentrantLock для достижения соответствующего параллелизма:

ReentrantLock selectorGuard;
Selector selector;

private void doSelect() {
    // Don't enter a select if another thread is in a critical block
    selectorGuard.lock();
    selectorGuard.unlock();

    selector.select();
    Iterator<SelectionKey> keyIter = selector.selectedKeys().iterator();

    while(keyIter.hasNext()) {

        SelectionKey key = keyIter.next();
        keyIter.remove();

        // Process key
    }
}

private void addToSelector() {

    // Lock the selector guard to prevent another select until complete
    selectorGuard.lock();

    try {
        selector.wakeup();

        // Do logic that registers channel with selector appropriately

    } finally {
        selectorGuard.unlock();
    }
}
0 голосов
/ 02 мая 2017

Если select() возвращает ноль, либо истекло время ожидания, либо он проснулся.

0 голосов
/ 02 декабря 2008

Вы не можете быть уверены, что единственная причина, по которой селектор проснулся, была из-за вызова пробуждения. У вас также может быть активность сокетов.

Итак, вам нужно, чтобы вызывающий объект пробуждения также делал что-то вроде установки изменяемого логического значения, указывающего на его желание внимания. Цикл выбора может проверять значение этого логического значения каждый раз, когда он просыпается.

0 голосов
/ 02 декабря 2008

Я не понимаю, почему ваш код будет работать вообще.

Почему бы просто не проверить volatile после select?

...