bigywater.tech
Rust · 138

非阻塞 IO

非阻塞 IO 不是「更快」,而是「不用等」。这篇验证它的返回形式、忙等的真实代价、用 poll 在一个线程里同时盯多个连接,以及一个容易踩的克隆共享陷阱。

305.1 ms
忙等 300 ms 消耗的 CPU 时间(poll 只要 0.0 ms)
1,222,679 次
忙等循环在 300 ms 里空转的次数
1 个线程
poll 同时服务 3 个连接所用的线程数

WouldBlock

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    没有客户端时 accept:WouldBlock,耗时 3 µs(立即返回)
    没有数据时 read:WouldBlock,耗时 6 µs
    对端发 2 字节后 read: Ok(2)
    对端关闭后 read: Ok(0)(Ok(0) = EOF)

阻塞式 TCP 见TCP Listener 与 TcpStream。

忙等与 poll 的代价

实测 · 本机执行输出
  编译:成功
  busy      等到数据用了 300 ms,循环 1222679 次,消耗 CPU 时间 305.1 ms
  blocking  等到数据用了 310 ms,循环 1 次,消耗 CPU 时间 0.2 ms
  poll      等到数据用了 300 ms,循环 1 次,消耗 CPU 时间 0.0 ms

CPU 时间通过 clock_gettime(CLOCK_PROCESS_CPUTIME_ID) 读取;poll 通过 extern 声明直接调用 libc,没有使用 mio。

一个线程服务多个连接

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    一个线程(ThreadId(1))按就绪顺序处理了 3 个连接:[("client1", 40), ("client2", 80), ("client0", 120)](客户端 0/1/2 分别在 120/40/80 ms 发数据)

异步运行时在这个基础上封装,见Async Runtime 做了什么。

克隆共享非阻塞标志

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    只对 a 调用 set_nonblocking(true);用克隆 b 去 read:Err(WouldBlock)(立即返回 WouldBlock,没有阻塞 —— 非阻塞标志属于同一个打开的 socket)

分步演示

非阻塞 IO
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。poll 在 macOS 上可用;没有使用 kqueue / epoll / mio
    • 没有验证:kqueue 与 epoll 的性能对比、边沿触发与水平触发、大量连接(上万)时 poll 的开销、非阻塞 connect、写方向的 WouldBlock(发送缓冲区满)
    京ICP备2026067502号-1京公网安备11010502063487号