epoll的哪些不为人知的特性,能让你在编程中如鱼得水?
摘要:之前曾经使用 epoll 构建过一个轻量级的 tcp 服务框架: 一个工业级、跨平台、轻量级的 tcp 网络服务框架:gevent 在调试的过程中,发现一些 epoll 之前没怎么注意到的特性。 a) iocp 是完全线程安全的,即同时可以
之前曾经使用 epoll 构建过一个轻量级的 tcp 服务框架:
一个工业级、跨平台、轻量级的 tcp 网络服务框架:gevent
在调试的过程中,发现一些 epoll 之前没怎么注意到的特性。
a) iocp 是完全线程安全的,即同时可以有多个线程等待在 iocp 的完成队列上;
而 epoll 不行,同时只能有一个线程执行 epoll_wait 操作,因此这里需要做一点处理,
网上有人使用 condition_variable + mutex 实现 leader-follower 线程模型,但我只用了一个 mutex 就实现了,
当有事件发生了,leader 线程在执行事件处理器之前 unlock 这个 mutex,
就可以允许等待在这个 mutex 上的其它线程中的一个进入 epoll_wait 从而担任新的 leader。
(不知道多加一个 cv 有什么用,有明白原理的提示一下哈)
b) epoll 在加入、删除句柄时是可以跨线程的,而且这一操作是线程安全的。
之前一直以为 epoll 会像 select 一像,添加或删除一个句柄需要先通知 leader 从 epoll_wait 中醒来,
在重新 wait 之前通过 epoll_ctl 添加或删除对应的句柄。但是现在看完全可以在另一个线程中执行 epoll_ctl 操作
而不用担心多线程问题。这个在 man 手册页也有描述(man epoll_wait):
NOTES
While one thread is blocked in a call to epoll_pwait(), it is possible for another thread to
add a file descriptor to the waited-upon epoll instance. If the new file descriptor becomes
ready, it will cause the epoll_wait() call to unblock.
For a discussion of what may happen if a file descriptor in an epoll instance being monitored
by epoll_wait() is closed in another thread, see select(2).
c) epoll 有两种事件触发方式,一种是默认的水平触发(LT)模式,即只要有可读的数据,就一直触发读事件;
还有一种是边缘触发(ET)模式,即只在没有数据到有数据之间触发一次,如果一次没有读完全部数据,
则也不会再次触发,除非所有数据被读完,且又有新的数据到来,才触发。使用 ET 模式的好处是,
不用在每次执行处理器前将句柄从 epoll 移除、在执行完之后再加入 epoll 中,
(如果不这样做的话,下一个进来的 leader 线程还会认为这个句柄可读,从而导致一个连接的数据被多个线程同时处理)
从而导致频繁的移除、添加句柄。好多网上的 epoll 例子也推荐这种方式。但是我在亲自验证后,发现使用 ET 模式有两个问题:
1)如果连接上来了大量数据,而每次只能读取部分(缓存区限制),则第 N 次读取的数据与第 N+1 次读取的数据,
有可能是两个线程中执行的,在读取时它们的顺序是可以保证的,但是当它们通知给用户时,第 N+1 次读取的数据
有可能在第 N 次读取的数据之前送达给应用层。这是因为线程的调度导致的,虽然第 N+1 次数据只有在第 N 次数据
读取完之后才可能产生,但是当第 N+1 次数据所在的线程可能先于第 N 次数据所在的线程被调度,上述场景就会产生。
