基于事件的并发
基础
之前一直用thread 来写并发的程序,但是在一些其他类型的并发程序也是用基于事件的方式,比如GUI程序以及http server 类的程序。
基于事件的并发主要解决两个问题:
- anaging concurrency correctly in multi-threaded applications can be challenging
- in a multi-threaded application, the developer has little or no control over what is schedule at a given moment in time
Event-based Concurrency 的简单定义:
you simply wait for something (i.e., an “event”) to occur; when it does, you check what type of event it is and do the small amount of work it requires (which may include issuing I/O requests, or scheduling other events for future handling, etc.). That’s it
经典的事件程序:
//伪代码
while (1) {
events = getEvents();
for (e in events)
processEvent(e); // event handler
}
事件循环的API
select() OR poll()
select() 需要注意的:
- note that it lets you check whether descriptors can be read from as well as written to
- note the timeout argument
事件循环的一个特征: With an event-based approach, however, there are no other threads to run: just the main event loop. And this implies that if an event handler issues a call that blocks, the entire server will do just that: block until the call completes.
这个特征导出了另个一要求 no blocking calls are allowed.
解决的这个要求的一些方法
- Asynchronous I/O
很多现代操作系统都会介绍asynchronous I/O这个新式的硬盘IO处理方式 AIO control block (*nix)- 状态管理 UNIX SIGNALS
仍然存在的问题
- when systems moved from a single CPU to multiple CPUs, some of the simplicity of the event-based approach disappeared
- it does not integrate well with certain kinds of systems activity, such as paging
- event-based code can be hard to manage over time, as the exact semantics of various routines changes
比如代码重构 从not block 重构成 block 的程序- though asynchronous disk I/O is now possible on most platforms, it has taken a long time to get there , and it never quite integrates with asynchronous network I/O in as simple and uniform a manner as you might think
epoll和select/poll的区别联系
- select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的
- select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
- select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。