关于多线程的一点总结

来源:互联网 发布:资产重组 知乎 编辑:程序博客网 时间:2024/04/20 06:49

昨天又在TopLanguage上看到一些关于轻量级线程的讨论,于是对其中很有用的一段话转过来、记录下来以供自己日后慢慢研究。

下面是由red...@gmail.com发表在TopLanguage上的


1. process per connection / thread per connection
这在连接数不多的时候编码方便. 并且, 在连接数不多而要求网络吞吐量高的场合, 这种方式是最容易实现, 效果也好.

2. 单一 selector/preactor
需要编写大量的状态机代码, 比较麻烦. 配合 epoll/kqueue/ devpoll 等机制, 没有 io block 问题的时候, 这种模式可 以轻易处理大量连接, cpu 消耗少.

需要注意的是, 文件访问不一定会造成大的 io block, 看业务目标和设计. 例如, 发送文件内容的时候, 可以使用 sendfile 调用, 或者 (有人提出过, 我还没有研 究过) 将文件 mmap 到内存中, 然后用 zero copy api 去发送这段内存.

外界的数据库访问之类的, 则一定会造成 block.

3. 线程池

half-sync / half-async 方式 一个 selector 负责接收请求, 一堆线程处理请求 缺点是 切换的 thread context 太多, 对 cpu cache 也不利.

leader / follower 方式 优点是减少了 thread context 切换, 对 cpu cache 也更有利. ACE 的书上写这个模式的缺点是, 编写程序更复杂.

但是根据我自己的实践, 这里面还有一个对性能很不利的点:
hs ha 方式, selector 可以有很多数据作为自己线程的私有数据, 访问的时候不 必加锁.

领导者跟随者模式中, 由于所有线程都可以访问所有的数据结构, 造成所有的数据 访问之前都需要加锁(可能有的数据结构: 发送队列, 全局连接表 或者 os 具体 poll 设施的数据结构), 这在多core 的情况下, 会引起很大的性能损失. 如果使用小的加锁粒度, 那么要申请的锁数目众多, 做一件事情可能要加解多次锁; 如果 使用大粒度锁, 那么锁竞争冲突可能会严重.

我没有做实际的测试, 但是担心领导者追随者这种方式, 在大连接数, 多 cpu core 的情况下表现会差, 所以放弃了这种方式.

我现在写的库, 实际上是使用 ha hs 方式, 但是, 多数简单的任务, 直接就在 selector 线程中处理了, 计算量大的任务, 或者是有 io block 的任务, 才交给 线程池进行处理, 对于这种复杂的任务来说, thread context 的切换开销, cpu cache 的损失, 也就不算什么了.

4. erlang 的方式
我想过写状态机不方便, 如果能够象写顺序执行的代码一样写代码就好了, 但是再 想到要处理的种种问题, 以及状态机其实写熟了也就那样了, 就算了 :)

BTW: ACE 的selector 代码, 在事件发生的时候, 先将这个事件源上的事件关闭, 处理完了再打开, 这在大连接数的情况下会造成很大的开销, OS call 的开销不小.


对应的我也列出我对上面处理方法的理解:


1.thread per connection

由于我们现在做的一个COM接口中实现对请求的处理就是thread per connection,确实这种方法实现起来最简单,但是由于发现我们的请求大多数都是短请求,数据量也少,所以很不适合这种模型,频繁的创建、销毁线程,是一种很浪费的操作,所以在考虑之后重新设计。(不过由于时间和技术的一些限制,暂时只能先用上了)

2.selector/preactor

不是很明白上面对应的地方,我是理解为select模型的。select模式,之前我也写过一个小的测试程序,还不是很理解,应该是由系统来维护读集和写集,然后当判断可读或者可写时进行适当的操作。如果理解的正确,那么对于多线程,我还是觉得这个是我现在知识范围内最好的解决方案。

3.线程池

哈哈,这个比起select还是好懂一些,但是内在的调用机制还没有研究,所以也就是只能从性能上说确实比第一种好,而且实现起来应该也不是很难。

4.erlang

之前就没听说过。唉……看来“革命尚未成功,同志仍需努力”!

原创粉丝点击