在高性能的网络程序中,使用得最为广泛的恐怕要数 “non-blocking IO + IO multiplexing”这种模型,即 Reactor 模式
while (!done)
{
int timeout_ms = max(1000, getNextTimedCallback());
int retval = ::poll(fds, nfds, timeout_ms);
if (retval < 0) {
处理错误
} else {
处理到期的 timers
if (retval > 0) {
处理 IO 事件
}
}
}
=================================================================
1.每个请求创建一个线程,使用阻塞式 IO 操作。在 Java 1.4 引入 NIO 之前,这是Java 网络编程的推荐做法。可惜伸缩性不佳。
2. 使用线程池,同样使用阻塞式 IO 操作。与 1 相比,这是提高性能的措施。
3. 使用 non-blocking IO + one loop per thread。即 Java NIO 的方式。
4. Leader/Follower 等高级模式
One loop per thread
此种模型下,程序里的每个 IO 线程有一个 event loop (或者叫 Reactor),用 于 处 理
读写和定时事件(无论周期性的还是单次的),代码框架跟第 2 节一样。
这种方式的好处是:
� 线程数目基本固定,可以在程序启动的时候设置,不会频繁创建与销毁。
� 可以很方便地在线程间调配负载。
event loop 代表了线程的主循环,需要让哪个线程干活,就把 timer 或 IO channel
(TCP connection) 注册到那个线程的 loop 里即可。对实时性有要求的 connection 可
以单独用一个线程;数据量大的 connection 可以独占一个线程,并把数据处理任务分
摊到另几个线程中;其他次要的辅助性 connections 可以共享一个线程。
对于 non-trivial 的服务端程序,一般会采用 non-blocking IO + IO multiplexing,每个
connection/acceptor 都会注册到某个 Reactor 上,程序里有多个 Reactor,每个线程至多
有一个 Reactor。
多线程程序对 Reactor 提出了更高的要求,那就是“线程安全”。要允许一个线程往别
的线程的 loop 里塞东西,这个 loop 必须得是线程安全的。
线程池
不过,对于没有 IO 光有计算任务的线程,使用 event loop 有点浪费,我会用有一种
补充方案,即用 blocking queue 实现的任务队列(TaskQueue):
blocking_queue<boost::function<void()> > taskQueue;
void worker_thread()
{
while (!quit) {
boost::function<void()> task = taskQueue.take();
task(); // 在产品代码中需要考虑异常处理
}
}
用这种方式实现线程池特别容易:
启动容量为 N 的线程池:
int N = num_of_computing_threads;
for (int i = 0; i < N; ++i) {
create_thread(&worker_thread); // 伪代码:启动线程
}
4
// 线程安全的阻塞队列
// this blocks使用起来也很简单:
boost::function<void()> task = boost::bind(&Foo::calc, this);
taskQueue.post(task);
上面十几行代码就实现了一个简单的固定数目的线程池,功能大概相当于 Java 5 的
ThreadPoolExecutor 的某种“配置”。当然,在真实的项目中,这些代码都应该封装到一个
class 中,而不是使用全局对象。另外需要注意一点:Foo 对象的生命期,我的另一篇博客
《当析构函数遇到多线程——C++ 中线程安全的对象回调》详细讨论了这个问题
http://blog.csdn.net/Solstice/archive/2010/01/22/5238671.aspx
除了任务队列,还可以用 blocking_queue<T> 实现数据的消费者-生产者队列,即 T 的
是数据类型而非函数对象, queue 的消费者(s)从中拿到数据进行处理。这样做比 task queue
更加 specific 一些。
blocking_queue<T> 是多线程编程的利器,它的实现可参照 Java 5 util.concurrent 里的
(Array|Linked)BlockingQueue,通常 C++ 可以用 deque 来做底层的容器。Java 5 里的代
码可读性很高,代码的基本结构和教科书一致(1 个 mutex,2 个 condition variables),
健壮性要高得多。如果不想自己实现,用现成的库更好。(我没有用过免费的库,这里就不
乱推荐了,有兴趣的同学可以试试
Intel Threading Building Blocks 里 的
concurrent_queue<T>。)
归纳
总结起来,我推荐的多线程服务端编程模式为:event loop per thread + thread pool。
� event loop 用作 non-blocking IO 和定时器。
� thread pool 用来做计算,具体可以是任务队列或消费者-生产者队列。
=========================================================================
eventloop 用作 non-blockingIO 和定时器。
threadpool 用来做计算,具体可以是任务队列或消费者-生产者队列
任务对列,生产消费者 线程池
TaskQueue、Producer-Consumer Queue、 CountDownLatch
PTHREAD_MUTEX_ERRORCHECK排错
条件变量是非常底层的同步原语, 很少直接使用, 一般都是用它来实现高层的同施步,措 如 BlockingQueue 或 CountDownLatch
线程 分析工具如 Intel Thread Checker 和 Valgrind-Helgrind 等能识别 pthreads 调用,并依据 happens-before 关系 [Lamport 1978] 分析程序有无 data race
使用互斥器的条件变量的惯用手法 (idiom),关键是 RAII
同步线程处理业务逻辑,异步线程处理IO
异步线程只有一个,由主线程当
每个线程开启一个事件loop ,及epoll_wait
microhttp, libpg, libdrizzle, quickfix
https://google-glog.googlecode.com/files/glog-0.3.3.tar.gz
blocking_queue<T> 是多线程编程的利器,它的实现可参照 Java 5 util.concurrent 里的
(Array|Linked)BlockingQueue,通常 C++ 可以用 deque 来做底层的容器