iocp 完成端口 可以开多个_译:6. Epoll, Kqueue与IOCP

原文地址

The Node Experiment - Exploring Async Basics with Rust​cfsamson.github.io

译者注:

  • 已经征得原作者同意,翻译文档和把翻译半成品放在知乎上,详见issue。
  • 对于文章中不理解、难翻译的部分,我会咨询作者本人,主要通过issue交流,尽量确保翻译最基本的准确性。大家如果对翻译有异议的、对内容有疑问的都可以提出来,自己在github上提issue或者我帮你提。
  • 考虑到第一章中英文混合的方法看起来有点杂乱,所以目前采用的翻译方式是把中文翻译独立出来,放在xxxx_cn.md,方便对照。

正文

一些知名的库在Linux、Mac和Windows上分别使用Epoll、Kqueue和IOCP,从而实现跨平台事件队列。

Node运行时的一部分是基于libuv的。libuv是一个跨平台的异步I/O库。libuv还是Julia和Pyuv 创建跨平台事件队列的基石;大多数的语言都有对应的libuv绑定。

132a82bf50757b340a392dca79bcb824.png

在Rust中我们可以使用mio - Metal IO。Mio为Tokio提供了操作系统级别的事件队列。

Tokio是一个提供I/O、网络、调度等功能的运行时。Mio之于Tokio就像libuv之于Node

Tokio为许多Web框架提供了支持,其中包括有名的高性能框架Actix Web。

既然我们想要理解一切是如何运作的,我决定自己搭建一个极简版本的事件队列。由于灵感大部分源于mio,所以我决定将其命名为minimio

我已经在Epoll, Kqueue and IOCP explained一书中详细描述了它是如何工作的。我们还构建了一个事件循环,而我们也拿它作为本书中跨平台的事件循环。有兴趣的话,你可以访问 Github repository 查看代码。

尽管如此,我们还是会在此对它们进行简单的介绍,以便你能了解大概内容。

为什么要使用系统支持的事件队列?

之前的章节中我们已经谈到:我们需要与操作系统紧密合作,从而尽可能提高I/O操作的效率。诸如Linux、MacOS和Windows这样的操作系统都提供了好几种阻塞和非阻塞的执行I/O操作的方式。

阻塞操作对于程序员而言是最不灵活的,因为我们需要把控制权交给操作系统,然后操作系统挂起线程。最大的优点是,一旦我们等待的事件准备完毕,线程就会被唤醒。

非阻塞方式则相对更加灵活,但是需要有一种方式来告知我们任务是否已经完成了。这通常是通过返回某种类型的数据完成的,这些数据会表明任务是Ready还是NotReady。缺点就是,我们需要定期地检查状态,以便能够判断状态是否发生改变。

基于Epoll/kqueue/IOCP的事件队列是第三种方式,它结合了非阻塞方式的灵活性,同时避免了前面提到的缺点。

我们不会讨论 pollselect的方式,不过如果你想了解它们以及它们与 epoll的区别的话,可以看看这篇文章。

基于准备情况的事件队列(Readiness-based event queues)

Epoll和Kqueue被称为基于准备情况的事件队列,意思就是这两种事件队列会在一项操作能够被执行之时通知你,比如一个socket准备好被读取的时候。

使用epoll/kqueue从socket里读取数据的大致步骤:

  1. 调用名为epoll_createkqueue的系统调用,创建一个事件队列。
  2. 向操作系统申请一个套接字的文件描述符。
  3. 通过另一个系统调用,注册对该socket的Read事件的兴趣。我们还需要告知操作系统,当(1)中创建的事件队列中的事件就绪时,我们希望收到通知。
  4. 下一步,我们调用epoll_wait或者kevent,等待一个事件准备完毕。这个操作会阻塞(挂起)调用该函数的线程。
  5. 当事件准备就绪,我们的线程就会被恢复(不再阻塞),并且调用的“wait”函数会返回就绪事件的信息。
  6. 调用read,读取套接字的数据。

基于完成情况的事件队列(Completion-based event queues)

IOCP,即I/O Complete Ports(I/O完成端口),是一种基于完成情况的事件队列。这种类型的事件队列会在事件完成时通知你,比如在数据已经被写入缓冲区后。

以下是这种类型的事件队列的大致分解步骤:

  1. 通过调用CreateIoCompletionPort,创建一个事件队列。
  2. 创建一段缓冲区,并且向操作系统申请一个socket的句柄。
  3. 调用另一个系统调用,注册对该socket的Read事件的兴趣,与上面不同的是,我们还会传入(2)中创建的缓冲区,用于存放读取到的数据。
  4. 下一步,我们调用GetQueuedCompletionStatusEx,这个函数会一直阻塞,直到有一个事件完成。
  5. 我们的线程恢复运行(不再阻塞),而缓冲区中已经填充了我们所感兴趣的数据了。

Epoll

Epoll是Linux下实现事件队列的方式。Epoll在功能上与Kqueue有许多共通之处。相较于Linux平台下其他类似方法(如selectpoll),epoll的优势在于它能够非常高效地处理大量事件。

Kqueue

Kqueue是MacOs下实现事件队列的方式,它起源于BSD,存在于诸如FreeBSD、OpenBSD等操作系统中。Kqueue在功能上与Epoll相似,但是实际使用时有所不同。

有一些人会认为它使用起来更复杂、更抽象。

IOCP

IOCP/完成端口,是Windows下实现事件队列的方式。

完成端口会在事件完成时通知你。现在听起来好像与之前两种差别不大,但事实上,非也。当你想编写一个库的时候,差别就会非常显著。因为对二者进行抽象意味着你要么将IOCP抽象为readiness-base,要么将epoll/kqueue抽象为completion-based的。

将缓冲区”借给“操作系统带来的风险:缓冲区在等待操作返回(例子中是读取)时,需要保证缓冲区不变。

根据我的经验,将 readiness-based的I/O模型抽象为类似 completion-based的模型要比反过来做要简单。这意味着你应该先让IOCP的部分先运行起来,再尝试适配 epoll或者 kqueue
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值