找回密码
 用户注册

QQ登录

只需一步,快速开始

查看: 32753|回复: 35

打造Epoll Proactor

[复制链接]
发表于 2009-11-19 16:52:17 | 显示全部楼层 |阅读模式
Linux下的aio实现性能比较差(据说是用多线程同步io模拟的,我没具体确认),因此如果要做高性能服务器,只能用ACE_Dev_Poll_Reactor。但windows下却是Proactor性能最高,这样开发的软件不能在linux和windows之间轻易移植。
我做了一个Epoll Proactor,用epoll而不是多线程来模拟aio。目前已经实现了ACE_Epoll_Asynch_Read_Stream,ACE_Epoll_Asynch_Write_Stream,ACE_Epoll_Asynch_Accept,ACE_Epoll_Asynch_Transmit_File。其他比如ACE_Epoll_Asynch_Read_File可以用ACE自带的实现,只是通知机制换为notification pipe注册到epoll。

我把代码发上来,大家有兴趣可以帮助测试下,谢谢。


[ 本帖最后由 wishel 于 2009-11-22 16:23 编辑 ]

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?用户注册

×
 楼主| 发表于 2009-11-19 16:54:24 | 显示全部楼层
编译:
先要修改Asynch_IO.h

ACE_Asynch_Read_Stream::Result中添加(大概第388行):
friend class ACE_Epoll_Asynch_Read_Stream_Result;

ACE_Asynch_Write_Stream::Result中添加(大概第545行):
friend class ACE_Epoll_Asynch_Write_Stream_Result;

ACE_Asynch_Accept::Result中添加(大概第970行):
friend class ACE_Epoll_Asynch_Accept_Result;

ACE_Asynch_Transmit_File::Result中添加(大概第1202行):
friend class ACE_Epoll_Asynch_Transmit_File_Result;

不需要重新编译ACE。

用法:
在main()中添加
#if defined (ACE_HAS_EVENT_POLL)

        ACE_Epoll_Proactor epollProactor;
        ACE_Proactor proactor(&epollProactor);
        ACE_Proactor::instance(&proactor);
#endif // defined (ACE_HAS_EVENT_POLL)
发表于 2009-11-19 17:04:15 | 显示全部楼层
呵呵,看来wishel 没少花心思哪!先下载下来,回头研究一下。
通知机制换为notification pipe注册到epoll,没看懂,dev_poll_reactor不支持通知机制吧.

另外,使用epoll实现Proactor,已经有人做过类似的事情,Google一下关键字TProactor,
或者去这个地址http://www.terabit.com.au/solutions.php
不过这个项目貌似好久没有更新了,ACE的版本比较老,5.5.x吧,但也并非不可用,可以参考一下。
 楼主| 发表于 2009-11-19 17:12:07 | 显示全部楼层
原帖由 modern 于 2009-11-19 17:04 发表
呵呵,看来wishel 没少花心思哪!先下载下来,回头研究一下。
通知机制换为notification pipe注册到epoll,没看懂,dev_poll_reactor不支持通知机制吧.

另外,使用epoll实现Proactor,已经有人做过类似的事情,Google一下关键字TPro ...

唉,快累死我了,上个周末通宵达旦。
没用dev_poll_reactor,用的原生epoll
epoll可以监控pipe的,pipe是unix domain socket做的

谢谢你的建议,我去看看。
 楼主| 发表于 2009-11-19 17:17:01 | 显示全部楼层
TProactor可能使用的dev_poll_reactor?
貌似它已经实现了leader/follower的线程池,可以参考下。

我现在的实现还只能单线程,等测一段时间稳定了再加l/f线程池。
发表于 2009-11-19 21:03:41 | 显示全部楼层
赞!wishel的钻研精神让人敬佩!比我强多了。
 楼主| 发表于 2009-11-20 11:52:16 | 显示全部楼层
老大过谦了。我也是难得一时激情而已。:lol

modern先大致看下吧,我正在进行架构的一次大的refactor。唉本来已经完成了开始测试了,一不小心删了程序文件:'( :'( 正在查linux下怎么恢复误删除文件。大概明天能改出来相对稳定版本。完了就就冻结设计,原则上只修改bug或加新features,不再动架构。
 楼主| 发表于 2009-11-22 16:23:07 | 显示全部楼层
架构调整完毕,现在有点像流水了。Visitor pattern,平时总感觉太heavy用不上,真到用上的时候,真是很好很强大。Tansimit_File补充了header_and_trailer。另外把前面写的FSMPServerACE_Asynch_Transmit_File改写了下,这样可以配合起来测试了。





http://www.acejoy.com/bbs/viewthread.php?tid=1493&pid=5498&page=1&extra=page%3D1
发表于 2009-11-23 09:16:20 | 显示全部楼层
周末简单的看了wishel的代码,思路很不错,而且代码也十分工整。
1、从代码分析目前压力应该还是再I/O读写,IOCP的I/O读写由windows内核帮忙完成,
显然效率是很高的,而目前这套框架是由运行Proactor的单线程负责I/O读写,
似乎不能完全发挥EPoll的威力,期待Wishel的LF线程池版本。
2、Handle_Event调度的时候,个人认为顺序可以考虑调整一下,优先级错误>写>读,
是否效果更好一些。
 楼主| 发表于 2009-11-23 17:49:08 | 显示全部楼层
哈哈。惊喜的发现了一个好消息!!!
Dev_Poll_Reactor是支持多线程的,不需要用户做任何同步。解释下理由:
1.
多线程解多路复用,需要考虑两个方面的同步问题。

1)
并发解复用的问题。比如,selectepoll在多线程同时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才可以调用selectepoll。这样就消除了并发demultiplex

2)
设计一个协议,在一个handle上的eventdispatch到某个handler处理的时候,暂停(suspend)对该handle的监听,当处理完毕后在恢复。这样保证不会有多线程对同一handle进行读写操作。

3.
Dev_Poll_Reactor也是从这两方面解决的,不过它的方式是implicit的。

1)
每次只wait 1fd,而且加EPOLLONESHOT flag

我做了一个验证测试:一个socketevents设为 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_Reactorbug,我等有时间用这个程序测一下Dev_Poll_Reactor看看。


不过,这种解决方案是可能有性能阻碍的:
1)
每次只wait 1fd,这样当有nfd ready的时候,需要调用epoll_wait() n次。不知道epoll_wait()的具体实现有没有使用缓存技术,就是把一次调用的结果内部缓存起来,下次调用可以从缓存里取,等缓存中数量不够才真正去poll。但即使如此,epoll_wait()毕竟是system call,要经过2次运行态切换,成本远大于一般function call。虽然用户可以自己做缓存来减少调用次数,但是实现起来相当复杂。因为要考虑如果中间调用了epoll_ctl,缓存会部分失效,而跟踪所有epoll_ctl调用需要花点功夫。

但是我个人感觉这个成本应该不算太大问题,还是可以接受的。
2)
EPOLLONESHOT可能在一定程度上降低了并发度。

我做了这样一个测验:一个socketevents设为 EPOLLIN | EPOLLOUT | EPOLLONESHOT
epoll_wait(epollfd, events, 1 , -1);这时会触发EPOLLOUT。然后向socket发数据,再wait就不会有事件了。也就是说只要EPOLLINEPOLLOUT有其一触发,整个handle就会被remove出去。这样有时候会影响对同一socket的并发读和写。
但是如果在wait之前发数据,是会触发EPOLLIN | EPOLLOUT两个事件的,因此这个问题的影响有限。而且在高负荷高并发服务器中,这个socket上减少的并发可以为在其他socket上增加的并发所弥补。只要最终并发(确切的说是并行)数不小于cpu数就可以了。

因此个人认为这个方案是非常好的解决方案。我打算在Epoll Proactor中也采用这种方式来支持多线程。

[ 本帖最后由 wishel 于 2009-11-23 18:02 编辑 ]
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

Archiver|手机版|小黑屋|ACE Developer ( 京ICP备06055248号 )

GMT+8, 2024-5-2 09:03 , Processed in 0.025856 second(s), 6 queries , Redis On.

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表