异步事件循环设计和问题
我正在使用 epoll/devpoll/kqueue/poll/select (包括 windows-select)为异步套接字 IO 设计事件循环。
我有两个执行 IO 操作的选项:
非阻塞模式,轮询 EAGAIN
- 将套接字设置为非阻塞模式。
- 读/写套接字。
- 如果操作成功,则将完成通知发布到事件循环。
- 如果我收到 EAGAIN,请将套接字添加到“选择列表”并轮询套接字。
轮询方式:轮询然后执行
- 将套接字添加到选择列表并轮询它。
- 等待它可读可写
- 读/写的
- 通知将完成通知发布到成功的事件循环
对我来说,在正常模式下使用时,首先看起来需要更少的系统调用, 特别是对于写入套接字(缓冲区相当大)。 看起来还可以减少“选择”数量的开销 执行,尤其是当你没有可以很好扩展的东西时 如 epoll/devpoll/kqueue。
问题:
- 第二种方法有什么优点吗?
- 在众多操作系统上,套接字/文件描述符上的非阻塞操作是否存在可移植性问题:Linux、FreeBSD、Solaris、MacOSX、Windows。
注意:请不要建议使用现有的 event-loop/socket-api 实现
I'm designing event loop for asynchronous socket IO using epoll/devpoll/kqueue/poll/select (including windows-select).
I have two options of performing, IO operation:
Non-blocking mode, poll on EAGAIN
- Set socket to non-blocking mode.
- Read/Write to socket.
- If operation succeeds, post completion notification to event loop.
- If I get EAGAIN, add socket to "select list" and poll socket.
Polling mode: poll and then execute
- Add socket to select list and poll it.
- Wait for notification that it is readable writable
- read/write
- Post completion notification to event loop of sucseeds
To me it looks like first would require less system calls when using in normal mode,
especially for writing to socket (buffers are quite big).
Also it looks like that it would be possible to reduce the overhead over number of "select"
executions, especially it is nice when you do not have something that scales well
as epoll/devpoll/kqueue.
Questions:
- Are there any advantages of the second approach?
- Are there any portability issues with non-blocking operations on sockets/file descriptors over numerous operating systems: Linux, FreeBSD, Solaris, MacOSX, Windows.
Notes: Please do not suggest using existing event-loop/socket-api implementations
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我不确定是否存在跨平台问题;您最多必须使用 Windows Sockets API,但结果相同。
否则,您似乎在这两种情况下都进行轮询(避免阻塞等待),因此两种方法都可以。只要您不将自己置于阻塞的位置(例如,在没有数据时读取,在缓冲区已满时写入),就没有任何区别。
也许第一种方法更容易编码/理解;所以,就这样吧。
您可能有兴趣查看 libev 和 c10k 问题 有关此主题的有趣想法/方法。
I'm not sure there's any cross-platform problem; at the most you would have to use Windows Sockets API, but with the same results.
Otherwise, you seem to be polling in either case (avoiding blocking waits), so both approaches are fine. As long as you don't put yourself in a position to block (ex. read when there's no data, write when buffer's full), it makes no difference at all.
Maybe the first approach is easier to code/understand; so, go with that.
It might be of interest to you to check out the documentation of libev and the c10k problem for interesting ideas/approaches on this topic.
第一个设计是前摄器模式,第二个是反应堆模式
反应堆模式的一个优点是,您可以设计 API,这样您就不必分配读取缓冲区,直到数据实际上是可以读取的。这会减少等待 I/O 时的内存使用量。
The first design is the Proactor Pattern, the second is the Reactor Pattern
One advantage of the reactor pattern is that you can design your API such that you don't have to allocate read buffers until the data is actually there to be read. This reduces memory usage while you're waiting for I/O.
根据我对低延迟套接字应用程序的经验:
对于写入 - 尝试从写入线程直接写入套接字(您需要为此获取事件循环互斥体),如果写入不完整,则使用事件循环订阅写入准备状态(select/waitformultipleobjects)当套接字变得可写以进行读取时,从事件循环线程写入
- 始终“订阅”所有套接字的读取准备状态,因此当套接字变得可读时,您始终从事件循环线程内读取
from my experience with low latency socket apps:
for writes - try to write directly into the socket from writing thread (you need to obtain event loop mutex for that), if write is incomplete subscribe to write readiness with event loop (select/waitformultipleobjects) and write from event loop thread when socket gets writable
for reads - be always "subscribed" for read readiness for all sockets, so you always read from within event loop thread when the socket gets readable