NIO选择器:如何在选择时正确注册新通道

2022-09-04 22:11:10

我有一个使用私有和公共方法的子类化,该方法允许其他线程将通道注册到选择器。ThreadSelectorregister(SelectableChannel channel, ...)

正如这里所回答的,通道在选择器/期间阻塞,所以我们需要选择器。register()select()select(long timeout)wakeup()

我的线程无限期地选择(除非它被中断),它实际上设法在调用通道之前进入下一个选择。所以我想我使用一个简单的锁和块来确保首先发生。register()synchronizedregister()

代码:(为了便于阅读,删除了不相关的代码)

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()

问题:这是一种“好”的方式,还是有什么严重的缺点?使用列表或队列(如此处建议)来存储用于注册的通道,还是使用像这样的更复杂的锁更好?这有什么优点/缺点?或者有什么“更好”的方法吗?


答案 1

只需将选择器等视为线程不安全,按照Darron的建议,在同一线程上执行所有与选择相关的操作。

NIO选择器的并发模型是胡说八道。我必须把它叫出来,因为对于每个试图研究它的人来说,这都是一个巨大的浪费时间。最后,结论是,算了,它不是并发使用的。


答案 2

我实际上很惊讶在编译时没有删除空块的锁获取。很酷,它的工作原理。我的意思是它有效,它是先发制人的,它不是最漂亮的方法,但它有效。它比睡眠更好,因为它是可预测的,并且由于您使用唤醒呼叫,因此如果您完全依赖于选择超时,则知道将根据需要取得进展,而不是定期更新。

这种方法的主要缺点是,您说注册的调用胜过其他任何内容,甚至是服务请求。在您的系统中可能是正确的,但通常情况并非如此,我会说这是一个可能的问题。一个更具前瞻性的小问题是,你锁定了SelectorThread本身,在这种情况下,它是一个更大的对象。还不错,虽然随着扩展,这个锁并不好,但只要其他客户使用这个类,这个锁就必须记录下来并考虑在内。就我个人而言,我会选择完全做另一个锁,以避免任何不可预见的未来危险。

就个人而言,我喜欢排队技术。他们以这种方式将角色分配给线程,例如主线程和辅助角色。而所有类型的控制都发生在主服务器上,例如在每次选择检查队列中的更多注册之后,清除并归入任何读取任务,处理整个连接设置中的任何更改(断开连接等)...“bs”并发模型似乎很好地接受了这个模型,它是一个非常标准的模型。我不认为这是一件坏事,因为它使代码不那么黑客,更易于测试,并且更易于阅读imo。只是需要多一点时间写出来。

虽然我承认,自从我上次写这些东西以来已经很久了,还有其他库可以帮你处理排队的问题。

灰熊蔚蓝框架虽然有点旧,但上次我用它的时候,主循环还不错。它为您设置了很多队列。

阿帕奇米娜类似之处在于它提供了一个队列框架。

但我的意思是,最终这取决于你在做什么。

  • 这是一个单人项目,只是为了玩框架吗?
  • 它是一段你想持续多年的生产代码吗?
  • 它是您正在迭代的一段生产代码吗?

除非您计划将其用作您为客户提供的服务的核心部分,否则我会说您的方法很好。从长远来看,它可能只是存在维护问题。