该帖已经被评为精华帖
|
|
---|---|
作者 | 正文 |
发表时间:2007-06-29
现在好像默认就使用proactor方式了,用户在connectionMade里面写入requestRead。有一个int process()方法,返回值是1时表明用户已经处理,返回0表明用户想放入线程池处理,返回-1是错误,需要断开。所以想使用reactor方式时,必须要重写int process()了。我在想另一种方式,如果没有requestRead,则process直接调用dataReceived来作reactor方式的处理。细节上有些绕,还没想好,现在基本可以工作,从线程池里面回到libevent用的是event_msgqueue,还比较好用。
|
|
返回顶楼 | |
发表时间:2007-06-29
qiezi 写道 现在最不自然的地方,就是Protocol来处理是否放入线程池,因为read请求也是它发起的,问题在于线程池在reactor里面,所以现在是通过protocol找到factory再找到reactor,虽然直接用指针就定位到了(factory->reactor->addTask(this)),不过感觉很不舒服。这类问题在twisted和ace里面可能是使用全局变量或singleton来访问,但我想在一个系统里面同时跑起多个reactor(虽然不一定有用),更重要的原因是我极度讨厌全局变量和singleton。
使用 libevent 和线程池,一般很自然地就把线程池和 reactor 混在一起了。 主要的原因是 libevent 的 event_loop 的实现,当底层的 select/poll/epoll 探测到有事件的时候,event_loop 是逐个处理所有的事件,在 event_loop 内部调用所有的 callback 。而一般就是在 callback 里面直接处理事件了。 为了把 reactor/proactor 和线程池分开,可以用一个间接的方法。在 callback 中不直接处理事件,把事件保存到一个列表(eventList)里面,这样就可以使得 event_loop 要做的事情很简单。等到 event_loop 运行完之后,就可以使用相应的线程池策略来处理 eventList 了。 在这里有类似的描述 Modification of the leader-follower pattern for Proactor 代码大概是这样的 class Reactor { public: int handle_events() { if( mEventList->getCount() <= 0 ) { // 把所有的 event 保存到 mEventList 中 event_loop( xxx ); } Event * event = mEventList->remove( 0 ); if( NULL != event ) { // process event } } private: List * mEventList; }; int main( ... ) { Reactor reactor; // 可以使用不同的线程池模型来处理这个循环 // 可以是 Half-Async/Half-Sync ,也可以是 Leader/Follower for( ; ; ) { reactor.handle_events(); } return 0; } 我也正准备按这个思路来尝试修改 spserver ,然后对比一下 HAHS 和 LF 线程池模型的性能差异。 从目前想象到的运行过程来看,LF 的线程切换应该比 HAHS 少很多,性能应该会更好。 关键的地方是 引用 (Please, see Collections of results below how to avoid/minimize the cost of operations on collections).
|
|
返回顶楼 | |
发表时间:2007-06-29
LF线程切换会比HAHS少?目前你的HAHS实现是怎样的?我是做了一个同步队列,所以这个线池里面的线程是阻塞在同步队列上的,这些线程的关系应该也是LF。这种情况下,HAHS可能同时运行的线程数比LF多一个,在多CPU上感觉差异并不太大。不过我那个实现用了同步队列,线程锁会影响性能。改用LF也会有锁的问题,必须保证在一个线程取到一个socket事件时,从libevent里面移除该socket的所有事件,以防止多个线程跑同一个socket,这时锁是免不了的。虽然libevent会在发生事件时移除该句柄上的事件,但似乎只是移除一个事件,而且这个过程也不是线程安全的,所以我对于实际性能会不会提高还有疑问。另一个LF方式的缺点,就是各个处理线程都忙时,不会再接受连接,而HAHS方式是先接下来再说,接下来的连接还可以先接收数据,至于哪种方式更合理,我也不能确定。
不知道我对于这2种模式有没有理解上的偏差。 我也订了一套POSA2,周末到货,看看再说。 |
|
返回顶楼 | |
发表时间:2007-06-29
引用 LF线程切换会比HAHS少?目前你的HAHS实现是怎样的?我是做了一个同步队列,所以这个线池里面的线程是阻塞在同步队列上的,这些线程的关系应该也是LF。
目前是实现了一个 Executor ,这个 Executor 对象是一个主动对象,内部维持一个任务队列和一个线程池( worker )。创建 Executor 对象的时候,自动会创建一个线程(称为 scheduler 线程 ),这个线程复杂分派队列中的任务给线程池中的线程。 Executor 的使用场景类似这样: void main_thread( .... ) { Executor executor; for( ; ; ) { Task * task = xxxxx; executor.execute( task ); } } 这样带来的好处是 Executor 可以用 lazy 的方式来创建线程池里面的线程,也可以在必要的时候回收线程。 另外就是使用者容易使用,接口容易理解。这种线程池的使用和普通的 connection pool 之类的池很类似。 过量的任务只会阻塞 scheduler ,不会阻塞 main_thread 。 坏处就是多了线程的切换。executor.execute 的时候,从 main_thread 切换到 scheduler 线程,然后 scheduler 线程分派任务,又从 scheduler 切换到 worker 线程。 引用 另一个LF方式的缺点,就是各个处理线程都忙时,不会再接受连接,而HAHS方式是先接下来再说,接下来的连接还可以先接收数据,至于哪种方式更合理,我也不能确定。
LF 的确有这种问题。所以可能要做到由用户来选择,把 HAHS 和 LF 封装起来,可以方便地切换。 |
|
返回顶楼 | |
发表时间:2007-07-01
引用 为了把 reactor/proactor 和线程池分开,可以用一个间接的方法。在 callback 中不直接处理事件,把事件保存到一个列表(eventList)里面,这样就可以使得 event_loop 要做的事情很简单。等到 event_loop 运行完之后,就可以使用相应的线程池策略来处理 eventList 了。
从目前想象到的运行过程来看,LF 的线程切换应该比 HAHS 少很多,性能应该会更好。 今天试着用这个思路来实现 Leader/Follower ,为 SPServer 增加了一个 SP_LFServer 的类,接口和 SP_Server 一样。 最新代码 http://spserver.googlecode.com/files/spserver-0.6.src.tar.gz 然后修改了 testhttp.cpp 的代码,使得可以在命令行指定使用的线程池模型,然后用 ab 做了一些简单的测试。 testhttp.cpp 的功能很简单,只是把收到的 http 请求解释出来,然后把解释到的内容作为 response 返回给客户端。由于 testhttp.cpp 的功能足够简单,所以用来测试线程的切换的消耗还是比较理想的。 从初步的测试结果来看,在 testhttp.cpp 这种应用中, LF 比 HAHS 快。 测试不是很严格,针对 HAHS 和 LF 分别测试使用 1 个线程和 2 个线程的情况,每个 case 运行 5 次,取中间的结果。性能最好的是 LF 使用 1 个线程的时候,因为这里完全没有线程的切换, 。 对比上次 测试memcached 得到的数据,LF 使用 1 线程的时候,处理请求的速度和 memcached 差不多。memcached 是单线程的。 对于 HAHS 模型的测试结果 bash-2.05a$ ./testhttp -s hahs -t 1 bash-2.05a$ ./ab -n 10000 -c 100 -k http://127.0.0.1:8080/ Requests per second: 3464.96 [#/sec] (mean) bash-2.05a$ ./testhttp -s hahs -t 2 bash-2.05a$ ./ab -n 10000 -c 100 -k http://127.0.0.1:8080/ Requests per second: 2768.06 [#/sec] (mean) 对于 LF 模型的测试结果 bash-2.05a$ ./testhttp -s lf -t 1 bash-2.05a$ ./ab -n 10000 -c 100 -k http://127.0.0.1:8080/ Requests per second: 4576.40 [#/sec] (mean) bash-2.05a$ ./testhttp -s lf -t 2 bash-2.05a$ ./ab -n 10000 -c 100 -k http://127.0.0.1:8080/ Requests per second: 2951.07 [#/sec] (mean) |
|
返回顶楼 | |
发表时间:2007-07-01
有没有测试多CPU?结果可能会不同吧。
现在无论是使用LF还是HAHS,都有个锁的问题。底层无论是使用select还是epoll,都无法在不加锁的情况下,在一个原子操作中完成取出事件并取消已经注册的事件(select当然是操作fd_set),这就给LF方式带来了不方便,必须用线程锁了。我感觉要做出最高效的应用,可能还是要自己去整底层API,依赖libevent可能还是有局限性,自己做比较麻烦的是超时,有时间看看libevent是如何实现的。感觉每种方案都有不完善的地方,不知道linux内核中实现的aio怎么样。 |
|
返回顶楼 | |
发表时间:2007-07-01
引用 有没有测试多CPU?结果可能会不同吧。 测试的主要关键点是在测试案例的选取上。 就目前这个 testhttp.cpp 的案例来说,多 CPU 对性能的提升应该不大了。对于这种案例,如果要在多 CPU 上获得更好的性能,应该使用 memcached 的方法,每个线程使用一个 event_base 。 引用 底层无论是使用select还是epoll,都无法在不加锁的情况下 在 POSA2 书中,多次提到 win32 WaitForMultiObjects 函数允许线程池在同一个句柄集上同时等待来支持并发句柄集。 引用 可能还是要自己去整底层API,依赖libevent可能还是有局限性 对于 event-driven 编程方式的封装,libevent 其实做得很好了。libevent 主要的特点是使用 callback ,但这个也是由 event-driven 决定的。这种实现方式就相当于设计模式中的“被动迭代器”。 通过上面提到的方法可以把“被动迭代器”转换为“主动迭代器”。 引用 为了把 reactor/proactor 和线程池分开,可以用一个间接的方法。在 callback 中不直接处理事件,把事件保存到一个列表(eventList)里面,这样就可以使得 event_loop 要做的事情很简单。等到 event_loop 运行完之后,就可以使用相应的线程池策略来处理 eventList 了。 如果要改进,应该就是要考虑如何一步到位地把 event-driven 编程方式封装为“主动迭代器”的方式。 |
|
返回顶楼 | |
发表时间:2007-08-08
SP_MsgDecoder 实现时,COPY缓冲能不能很好的解决?
|
|
返回顶楼 | |
发表时间:2007-08-08
linux网络内核没有实现aio
|
|
返回顶楼 | |
发表时间:2007-08-08
qiezi 写道 底层无论是使用select还是epoll,都无法在不加锁的情况下,在一个原子操作中完成取出事件并取消已经注册的事件。
经过测试,我这个说法是错误的。epoll有个EPOLLONESHOT可以完成一次性事件,可以让epoll_wait每次只取一个事件,从而达到没有线程锁的情况下实现LF模式。一个线程调用epoll_wait时还允许其它线程调用epoll_ctl来注册事件。多个线程在同一个epoll描述符上执行epoll_wait内核会进行排队,不会出现一个事件在有EPOLLONESHOT时还被多个线程得到的情况,所以可以在无锁的情况下完成整个过程,目前简单测试通过了。 |
|
返回顶楼 | |