bigywater.tech
Java · 246

Selector.wakeup:如何唤醒阻塞中的事件循环

事件循环线程阻塞在 select() 上时,别的线程怎么叫醒它?提前叫醒、重复叫醒、被中断、被关闭、注册新 channel 分别会发生什么?这篇逐项真实运行。

30 μs
wakeup() 到阻塞的 select() 返回的延迟中位数
1 次
提前调用 1000 次 wakeup() 后立刻返回的 select 次数
1895 ms
select 阻塞时 register 新 channel 但不 wakeup,select 返回前等待的时间

wakeup() 的语义与延迟

实测 · 本机执行输出
  select() 阻塞 200 ms 后另一个线程 wakeup():select 在 1179 μs 内返回,返回值 0(没有就绪的 key)
  wakeup → select 返回的延迟(500 次):中位 30 μs,P90 37 μs,最大 231 μs
  没有线程在 select 时先 wakeup():下一次 select(5000) 立刻返回(21 μs);再下一次 select(300) 正常阻塞了 302 ms(wakeup 的效果只保留一次)
  连续 wakeup() 1000 次再 select:第 1 次 31 μs 返回,第 2 次阻塞 202 ms,第 3 次阻塞 200 ms(多次 wakeup 合并成一次)
  wakeup() 之后调用 selectNow(),再 select(200):阻塞了 202 ms(selectNow 清除了之前的 wakeup)
  没有线程等待时 wakeup() 每次 31 ns(重复调用 1000000 次)

interrupt 与 close

实测 · 本机执行输出
  线程在 select() 里被 interrupt():312 μs 内返回(没有抛异常),返回后线程的中断标志 = true
  带着中断标志进入 select 循环:300 ms 内 select() 空返回 392428 次(后 100 ms 有 141234 次)——和 park 一样,中断标志不清除时会变成忙等
  另一个线程 close() 正在 select 的 Selector:select 在 3793 μs 内返回(select 返回 0,isOpen = false)
  关闭之后再 select → ClosedSelectorException
  关闭之后 wakeup() → 没有异常

阻塞时注册与改 interestOps

实测 · 本机执行输出
  select(2000) 阻塞时,另一个线程 register 一个监听 socket,随后有客户端连接上来:register 调用本身耗时 1599 μs(没有被阻塞);select 在 1895 ms 后返回,返回的就绪 key 数 = 0——等到超时才返回,新注册的 key 没有唤醒它
  返回之后再 selectNow():就绪 key 数 = 1,isAcceptable = true
  同样的场景,register 之后调用 wakeup():select 在 1.00 ms 后返回,就绪 key 数 = 0
  已注册的 key,在 select 阻塞期间调用 interestOps(OP_ACCEPT)(此时已有客户端在等待连接):select 在 1892 ms 后返回,就绪 key 数 = 0

分步演示

Selector.wakeup
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。JDK 21.0.12,macOS
    • 没有验证:Linux 的 epoll 实现的差别、select 在大量连接下的性能、Netty 的 wakeup 合并优化(见 Netty 篇目)、wakeup 的底层机制(管道或 eventfd,这里没有看)、虚拟线程里阻塞的 Selector
    京ICP备2026067502号-1京公网安备11010502063487号