|
楼主 |
发表于 2009-11-23 17:49:08
|
显示全部楼层
哈哈。惊喜的发现了一个好消息!!!
Dev_Poll_Reactor是支持多线程的,不需要用户做任何同步。解释下理由:
1.
多线程解多路复用,需要考虑两个方面的同步问题。
1)
并发解复用的问题。比如,select和epoll在多线程同时wait的时候,如果一个event发生,会有多个线程同时收到通知被唤醒。而windows下的wfmo就不会有这个问题,只有一个线程会收到通知。
2)
并发处理handle的问题。tcp socket不支持多线程同时读或同时写同一socket(一个读一个写没问题),所以在对一个handle进行io的时候,要确保不会有其他线程同时也在对这个handle进行操作。udp socket天生是并发性的,udp协议保证多线程同时读或同时写一个socket不会发生corrupt, lose or scramble data。
2.
所以leader/follower 模式对应的解决方案也是从这两方面着手:
1)
线程池中只有一个leader才可以调用select或epoll。这样就消除了并发demultiplex。
2)
设计一个协议,在一个handle上的event被dispatch到某个handler处理的时候,暂停(suspend)对该handle的监听,当处理完毕后在恢复。这样保证不会有多线程对同一handle进行读写操作。
3.
Dev_Poll_Reactor也是从这两方面解决的,不过它的方式是implicit的。
1)
每次只wait 1个fd,而且加EPOLLONESHOT flag
我做了一个验证测试:一个socket,events设为 EPOLLIN | EPOLLONESHOT,然后 2个线程同时调用 epoll_wait(epollfd, events, 1 , -1);都阻塞之后,发送数据给这个socket,结果只有一个线程收到通知返回。
这点好像是借鉴了wfmo,每次调用原子性的只返回一个handle的事件。
2)
仍然是神奇的EPOLLONESHOT,自动承担了suspend的功能,而Dev_Poll_Reactor框架在handle*处理后又会调用rusume把这个handle的相应mask(包括EPOLLONESHOT)加回去。
所以,理论上可以证明Dev_Poll_Reactor是可以多线程运行的。不过还需要具体测试的验证,example下有个测试程序,据说测出了TP_Reactor的bug,我等有时间用这个程序测一下Dev_Poll_Reactor看看。
不过,这种解决方案是可能有性能阻碍的:
1)
每次只wait 1个fd,这样当有n个fd ready的时候,需要调用epoll_wait() n次。不知道epoll_wait()的具体实现有没有使用缓存技术,就是把一次调用的结果内部缓存起来,下次调用可以从缓存里取,等缓存中数量不够才真正去poll。但即使如此,epoll_wait()毕竟是system call,要经过2次运行态切换,成本远大于一般function call。虽然用户可以自己做缓存来减少调用次数,但是实现起来相当复杂。因为要考虑如果中间调用了epoll_ctl,缓存会部分失效,而跟踪所有epoll_ctl调用需要花点功夫。
但是我个人感觉这个成本应该不算太大问题,还是可以接受的。
2)
EPOLLONESHOT可能在一定程度上降低了并发度。
我做了这样一个测验:一个socket,events设为 EPOLLIN | EPOLLOUT | EPOLLONESHOT,
先epoll_wait(epollfd, events, 1 , -1);这时会触发EPOLLOUT。然后向socket发数据,再wait就不会有事件了。也就是说只要EPOLLIN或EPOLLOUT有其一触发,整个handle就会被remove出去。这样有时候会影响对同一socket的并发读和写。
但是如果在wait之前发数据,是会触发EPOLLIN | EPOLLOUT两个事件的,因此这个问题的影响有限。而且在高负荷高并发服务器中,这个socket上减少的并发可以为在其他socket上增加的并发所弥补。只要最终并发(确切的说是并行)数不小于cpu数就可以了。
因此个人认为这个方案是非常好的解决方案。我打算在Epoll Proactor中也采用这种方式来支持多线程。
[ 本帖最后由 wishel 于 2009-11-23 18:02 编辑 ] |
|