除非您想编写自己的 IO 线程池,否则 glibc 实现是一个可以接受的解决方案。对于完全在用户空间中运行的东西,它实际上工作得非常好。
根据我的经验,内核实现根本不适用于缓冲 IO(尽管我见过其他人说相反的说法!)。如果您想通过 DMA 读取大量数据,这很好,但如果您打算利用缓冲区高速缓存,这当然会浪费大量时间。
另请注意,内核 AIO 调用实际上可能会阻塞。有一个大小有限的命令缓冲区,大的读取被分解成几个较小的。一旦队列满了,异步命令就会同步运行。惊喜。一两年前我遇到过这个问题,但找不到解释。四处询问给了我“是的,当然,这就是它的工作原理”的答案。
据我了解,尽管多年来似乎有几种可行的解决方案,但支持缓冲 aio 的“官方”兴趣也不是很大。我读过的一些论点是“你无论如何都不想使用缓冲区”和“没有人需要那个”和“大多数人甚至不使用 epoll”。所以,嗯……嗯。
直到最近,能够epoll
通过已完成的异步操作获得信号是另一个问题,但与此同时,通过eventfd
.
请注意,glibc 实现实际上会在内部按需生成__aio_enqueue_request
线程。这可能没什么大不了的,因为生成线程不再那么昂贵,但应该意识到这一点。如果您对启动异步操作的理解是“立即返回”,那么该假设可能不正确,因为它可能首先产生了一些线程。
编辑:
作为旁注,在 Windows 下存在与 glibc AIO 实现中的情况非常相似的情况,其中“立即返回”排队异步操作的假设不正确。
如果您要读取的所有数据都在缓冲区缓存中,Windows 将决定改为同步运行请求,因为无论如何它都会立即完成。这是有据可查的,当然听起来也很棒。除非有几兆字节要复制,或者另一个线程有页面错误或同时执行 IO(因此竞争锁)“立即”可能是一个令人惊讶的长时间——我已经看到“立即”时间为 2 -5 毫秒。这在大多数情况下都没有问题,但例如在 16.66 毫秒帧时间的约束下,您可能不想冒险在随机时间阻塞 5 毫秒。因此,“可以从我的渲染线程执行异步 IO 没有问题,因为异步不会阻塞”的天真假设是有缺陷的。