Селектор NIO: как правильно зарегистрировать новый канал при выборе

У меня есть подклассы Thread с частным Selector и общедоступным методом register(SelectableChannel channel, ...), который позволяет другим потокам регистрировать каналы для селектора.

Как сказано здесь, канал register() блокируется во время селектора select()/select(long timeout), поэтому нам нужно wakeup() селектор.

Моя нить выбирается неограниченно (если только она не прерывается), и ей действительно удается попасть в следующий выбор до вызова канала register(). Поэтому я подумал, что я использую простой замок с блоками synchronized, чтобы гарантировать, что register() произойдет первым.

Код: (ненужный код удален для чтения)

public class SelectorThread extends Thread {
  ...

  public void register(SelectableChannel channel, Attachment attachment) throws IOException {
    channel.configureBlocking(false);
    synchronized (this) { // LOCKING OCCURS HERE
      selector.wakeup();
      channel.register(selector,
                       SelectionKey.OP_READ,
                       attachment);
    }
  }

  @Override
  public void run() {
    int ready;
    Set<SelectionKey> readyKeys;
    while (!isInterrupted()) {
      synchronized (this) {} // LOCKING OCCURS HERE

      try {
        ready = selector.select(5000);
      } catch (IOException e) {
        e.printStackTrace();
        continue;
      }

      if (ready == 0) {
        continue;
      }

      readyKeys = selector.selectedKeys();

      for (SelectionKey key : readyKeys) {
        readyKeys.remove(key);

        if (!key.isValid()) {
          continue;
        }

        if (key.isReadable()) {
          ...
        }
      }
    }
  }
}

Эта простая блокировка позволяет register() произойти до того, как поток продолжит следующий цикл выбора. Насколько я проверял, это работает как предполагается.

Вопросы: Это "хороший" способ сделать это или есть какие-то серьезные недостатки? Было бы лучше использовать List или Queue (как предложено здесь) для хранения каналов для регистрации или более сложного блокировки типа это вместо этого? Какими будут плюсы и минусы этого? Или есть какие-то "еще лучшие" способы?

Ответ 1

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

Основным недостатком этого подхода является то, что вы говорите, что звонки регистрируют козырь всего остального, даже обслуживая запросы. Что может быть правдой в вашей системе, но обычно это не так, я бы сказал, что это возможная проблема. Небольшая проблема, которая является более перспективным мышлением, заключается в том, что вы блокируете сам SelectorThread, который в этом случае является более крупным объектом. Неплохо, неважно, хотя, когда вы расширяетесь, этот замок будет просто документировать и принимать во внимание, когда другие клиенты используют этот класс. Лично я хотел бы сделать еще один замок, чтобы избежать любых непредвиденных будущих опасностей.

Лично мне нравятся методы очередей. Они назначают роли вашим потокам, таким как мастер и рабочие. В то время как все типы управления происходят на главном компьютере, например, после каждой проверки выбора для большего количества регистраций из очереди, очищать и обрабатывать любые задачи чтения, обрабатывать любые изменения в общей настройке соединения (разъединяет и т.д.)... "bs" < Модель w390 > , похоже, довольно хорошо подходит к этой модели, и это довольно стандартная модель. Я не думаю, что это плохо, потому что это делает код немного менее взломанным, более проверяемым и более легким для чтения imo. Просто требуется больше времени, чтобы выписать.

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

Grizzly Nio Framework, в то время как немного старый, в прошлый раз, когда я его использовал, основная runloop была неплохой. Он настраивает для вас много очередей.

Apache Mina Подобным образом он обеспечивает структуру очередей.

Но я имею в виду, в конце концов, это зависит от того, над чем вы работаете.

  • Это проект для одного человека, который просто должен работать с каркасом?
  • Это фрагмент кода, который вы хотите прожить годами?
  • Это фрагмент производственного кода, который вы повторяете?

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

Ответ 2

Просто обработайте Selector и т.д., так как не потокобезопасны, выполните все действия, связанные с определенным действием, в том же потоке, что и Даррон.

Модель селектора NIO concurrency - дерьмо. Я должен это назвать, потому что это огромная трата времени для всех, кто пытается ее изучить. В конце концов, вывод состоит в том, чтобы забыть об этом, не для одновременного использования.

Ответ 3

Все, что вам нужно, это wakeup() перед register() и, в цикле select, короткий сон перед продолжением, если "ready" равно нулю, чтобы дать register() возможность запуска. Никакой дополнительной синхронизации: это уже достаточно плохо; не делай хуже Я не фанат этих очередей вещей для регистрации, отмены, изменения операций с процентами и т. Д.: они просто упорядочивают вещи, которые действительно могут быть выполнены параллельно.