大学还没毕业的时候,为了应付毕业找工作,了解过所谓“事件驱动”的一些情况(其实就是select/poll/epoll),特别是epoll。不过现在搬砖搬久了,也就渐渐忘记……忽然心血来潮,复习一下。温故而知新——希望有新的认识。
epoll是Linux提供的I/O event notification facility。在需要监听的fd数量很多(成千上万)而同一时刻可读/可写的数量又比较少(几个?几十个?几百个?)的情况下,性能要明显优于select、poll。
与epoll直接相关的API有: 创建epoll fd:epoll_create/epoll_create1 操作epoll fd:epoll_ctl 监听epoll fd:epoll_wait/epoll_pwait
int epoll_create(int size);
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
typedef union epoll_data {
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; // events is a bit set
epoll_data_t data;
};
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
int epoll_pwait(int epfd, struct epoll_event* events, int maxevents, int timeout, const sigset_t* sigmask);
监听的接口比较简单,暂时没什么好介绍。
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.
假设我们有一个多线程的程序,一个线程负责建立连接和管理其它线程,我们暂且称之为master线程;多个线程负责处理连接后的业务逻辑,我们称之为worker线程。master线程建立连接后,调用epoll_ctl将socketfd注册到某个worker线程。worker线程调用epoll_wait监听事件。这样是不是线程安全的呢?
上面那段英文,有一个单词特别重要——waited-upon。当worker线程被阻塞在epoll_wait的时候,另一个线程调用epoll_ctl往其epfd注册fd,是线程安全的。但是这里有一个临界条件:worker线程可能刚好被唤醒,而不是阻塞在epoll_wait,此时这样的操作就不是线程安全的。
If a file descriptor being monitored by select() is closed in another thread, the result is unspecified. On some UNIX systems, select() unblocks and returns, with an indication that the file descriptor is ready (a subsequent I/O operation will likely fail with an error, unless another the file descriptor reopened between the time select() returned and the I/O operations was performed). ** On Linux (and some other systems), closing the file descriptor in another thread has no effect on select(). In summary, any application that relies on a particular behavior in this scenario must be considered buggy. **