12

他のスレッドがチャネルをセレクターに登録できるようThreadにするprivateメソッドとSelectorpublicメソッドでサブクラス化されています。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()、スレッドが次の選択ループを続行する前に発生することができます。私がテストした限り、これは想定どおりに機能します。

質問: それはそれを行うための「良い」方法ですか、それともそれに深刻な欠点がありますか?リストまたはキュー(ここで提案されているように)を使用して登録用のチャネルを保存するか、代わりにこのようなより洗練されたロックを使用する方がよいでしょうか?その長所/短所は何でしょうか?それとも「さらに良い」方法はありますか?

4

4 に答える 4

4

ダロンが提案したように、セレクターなどをスレッドセーフではないものとして扱い、同じスレッドですべての選択関連アクションを実行します。

NIO セレクターの同時実行モデルはでたらめです。それを勉強しようとするすべての人にとって、それは膨大な時間の無駄だからです。結局のところ、結論は、忘れてください、同時使用用ではありません。

于 2012-10-10T16:48:03.883 に答える
4

必要なのは , のwakeup()register()と、select ループで、'ready' が 0 の場合に続行する前に短いスリープregister()を実行して、実行する機会を与えることだけです。追加の同期はありません。すでに十分に悪いです。これ以上悪化させないでください。私は、登録、キャンセル、関心操作の変更などのこれらのキューのファンではありません。それらは、実際に並行して実行できることを順番に並べるだけです。

于 2012-10-11T03:11:51.787 に答える
3

空のブロックでのロック取得がコンパイル時に削除されないことに、私は実際に驚いています。それが機能するのはかなりクールです。つまり、それは機能し、プリエンプティブであり、最も美しいアプローチではありませんが、機能します。予測可能であるため、スリープよりも優れています。ウェイクアップ コールを使用するため、純粋に選択タイムアウトに依存している場合は、定期的な更新ではなく、必要に応じて進行が行われることがわかります。

このアプローチの主な欠点は、登録するための呼び出しが他の何よりも優先され、リクエストにサービスを提供していると言っていることです。あなたのシステムではこれが当てはまるかもしれませんが、通常はそうではありません。これは考えられる問題だと思います。より前向きな小さな問題は、この場合は一種のより大きなオブジェクトである SelectorThread 自体をロックすることです。悪くはありませんが、拡張すると、他のクライアントがこのクラスを使用するたびに、このロックを文書化し、考慮する必要があります。個人的には、予期せぬ将来の危険を避けるために、完全に別のロックを作成することにします.

個人的には、キューイングテクニックが好きです。このように、マスターとワーカーのように、スレッドにロールを割り当てます。すべてのタイプの制御はマスターで行われますが、キューからの登録を選択して確認するたびに、読み取りタスクをクリアしてファームアウトし、接続設定全体の変更 (切断など) を処理します...「bs」同時実行性モデルはこのモデルをかなり受け入れているようで、かなり標準的なモデルです。コードのハックが少なくなり、テストしやすくなり、読みやすくなるので、悪いことではないと思います。書き出すのにもう少し時間がかかります。

認めますが、私が最後にこのようなことを書いてから長い時間が経ちましたが、キューイングを処理するようなライブラリが他にもあります。

Grizzly Nio フレームワークは少し古いですが、前回使用したときは、メインのランループは悪くありませんでした。それはあなたのために多くの待ち行列をセットアップします。

Apache Minaキューイング フレームワークを提供するという点で似ています。

しかし、最終的には、あなたが何に取り組んでいるかによって異なります。

  • フレームワークをいじるだけの一人のプロジェクトですか?
  • 何年も使い続けたい製品コードですか?
  • それはあなたが反復している製品コードですか?

これを顧客に提供するサービスの中核部分として使用する予定がない限り、あなたのアプローチは問題ないと思います。長期的には、メンテナンスの問題が発生する可能性があります。

于 2012-10-11T05:12:10.330 に答える
0

可能な方法は、チャネル登録 (または NIO ループ内で実行する必要がある他の外部タスク) を選択ループに挿入することです。以下のデモを参照してください。

//private final Set<ExternalEvent> externalTaskEvents = ConcurrentHashMap.newKeySet();
//...

while (!Thread.currentThread().isInterrupted()) {
    try {
        selector.select();
    } catch (IOException ex) {
        ex.printStackTrace(Log.logWriter);
        return;
    }

    //handle external task events
    Iterator<ExternalEvent> eitr = externalTaskEvents.iterator();
    while (eitr.hasNext()) {
        ExternalEvent event = eitr.next();
        eitr.remove();
        if(event.task != null){
            event.task.accept(event);
        }
    }

    //handle NIO network events
    Iterator<SelectionKey> nitr = selector.selectedKeys().iterator();
    while (nitr.hasNext()) {
        SelectionKey key = nitr.next();
        nitr.remove();
        if (!key.isValid()) {
            continue;
        }
        try {
            if (key.isAcceptable()) {
                onAcceptable(key);
            } else if (key.isConnectable()) {
                onConnectable(key);
            } else {
                if (key.isReadable()) {
                    onReadable(key);
                }
                if (key.isWritable()) {
                    onWritable(key);
                }
            }
        } catch (IOException | InterruptedException | CancelledKeyException ex) {
            ex.printStackTrace(Log.logWriter);
            //...
        }
    }
}
于 2017-01-07T16:37:54.550 に答える