【问题标题】:How to avoid a busy while loop in event-driven Java如何在事件驱动的 Java 中避免繁忙的 while 循环
【发布时间】:2019-10-10 10:11:41
【问题描述】:

我目前正在以以下方式开发我的事件驱动 Java 软件(这是我的主要方法的本质):

while(true) {
    Event event = eventListener.poll();
    if(event != null) {
        // do something
    } else {
        // do nothing as usual, but burn CPU time.
    }
}

根据我正在构建的内容,eventListener 可能是正在侦听外部 websocket、轮询 Redis 通道以获取更新或等待来自同一框上的另一个进程的消息(可能通过 UDP/TCP /shm)。

我的想法是,当eventListener 返回 null(大多数情况下)时,这种繁忙的循环方法会浪费大量 CPU 时间,因为它只是坐在那里旋转。但是,我不知道如何处理这种设计,除了在每次迭代时放置一个Thread.sleep 这不是一个很好的解决方案。

理想情况下我想要一个方法:

void run(Event event) {
    // do something
}

其中run 会在任何时间触发eventListener 时被调用。如果没有这样的事件可用,理想情况下该进程应该只是闲置。

现在,我知道有一些 websocket 库实际上可以做到这一点,我想知道的是如何为自己构建这样的东西,并将我的 CPU 从坐在那里无所事事的浪费中解放出来?

【问题讨论】:

  • 最好的方法是实现一个事件传递系统,poll 不需要忙于等待。例如,如果poll 归结为select 调用一些底层文件句柄(套接字、命名管道等),那么您浪费的 CPU 时间非常少,因为内核知道在它处于休眠状态时让您的进程进入休眠状态。等待一个事件。
  • @JoachimSauer 您是否可以详细说明一下,也许可以举一个简短的例子?我不熟悉 poll 不需要忙等待的事件传递系统。我在帖子中举的例子是我的知识范围。
  • 这不是您等待的级别。您需要(例如)将您的套接字通信包装在一个类中,并在套接字读取时进行非忙碌等待。当您在套接字上获得某些东西时,您会生成事件(在您的包装类中),您的应用程序的其他组件可以注册这些事件。
  • @ABC:归根结底,如果您的 poll 在某处归结为 InputStream.read(),那么这已经不是忙等待:您的进程在等待时不会占用大量 CPU 时间新数据到达。其他机制,例如 NIO/select 允许更多功能(例如同时从多个套接字读取并在 任何 个套接字返回数据时返回),但这并不是绝对必要的。

标签: java design-patterns polling java-11 event-driven


【解决方案1】:

您需要使用java non-blocking IO,并且可能是一些支持通过java NIO 进行高级通信的库(例如netty,它支持HTTP、websockets 和redis 等的NIO 样式通信)。

以下是 NIO 工作原理的简短说明。你正在寻找的东西是Selector。它允许等待通道上的数据(这是文件或网络连接等的抽象)可用。这种等待(Selector.select 方法)是阻塞的,当一些数据可供读取或要写入的输出缓冲区可以获得新数据时,操作系统会恢复该进程。

代码大致如下所示:

Selector selector = createSelector();
Channel channel = createChannelForSocket();

SelectionKey key = channel.register(selector);

while(true) {

  int readyChannels = selector.select(TIMEOUT);

  if(readyChannels == 0) continue;

  Set<SelectionKey> selectedKeys = selector.selectedKeys();

  for(SelectionKey key : selectedKeys) {

    if (key.isReadable()) {
        readDataFromChannel(key.channel())
    } else if (key.isWritable()) {
        writeDataToChannel(key.channel())
    }

  }
}

使用 netty,您有更多高级代码,您可以在其中定义一个 Handler,它有一个类似 void channelRead(ChannelHandlerContext ctx, Object msg) 的方法,它是一种读取事件侦听器,您可以实现它来侦听读取事件。

netty 有一个内置循环,看起来类似于上面的示例,但它适用于许多事件侦听器,并将这些事件传播到特定的侦听器。

【讨论】:

    【解决方案2】:

    如果您对大规模使用事件驱动架构感兴趣。您可能想要使用强大的“事件总线”,例如 Apache Kafka 或 AWS SNS+SQS。为了更容易,您可以使用 kalium.alkal.io。这将无缝地处理 POJO 或 protobuf 对象的反序列化。

    kalium.on(Event.class, event -> {
    
       //doSomething with the event
    });
    
    

    【讨论】:

      猜你喜欢
      • 2013-12-05
      • 2011-06-28
      • 2013-08-29
      • 2012-06-12
      • 2010-11-14
      • 1970-01-01
      • 2016-07-29
      • 2018-02-17
      • 2017-01-18
      相关资源
      最近更新 更多