第三章 多线程试用场景和编程模型
事件驱动,大致框架:
while (!done) {
int timeout_ms = max(100, getNextTimeCallback());
int retval = ::poll(fds, nfds, timeout_ms);
if (retval < 0) {
处理错误,回调用户的error handler
} else {
处理到期的timers,回调用户的timer handler
if (retval > 0) {
处理IO事件,回调用户的IO event handler
}
}
}
Reactor模型
- 优点:不仅可以用于读写socket,连接的建立,DNS解析也可以非阻塞方式进行,以提高并发度和吞吐量,对于I/O密集型(进行在输入和输出话费大量时间)应用是个不错的选择
- 缺点:事件回调必须是非阻塞,对于设计网络IO请求响应式协议,容易割裂业务逻辑,使其分散到多个回调函数中,不容易维护和理解
one loop per thread
程序里每个IO线程有一个event loop(Reactor), 用于处理读写和定时事件(无论周期性还是单次)
优点:
- 线程数据基本固定,可以在程启动的时间设置,不会频繁创建与销毁
- 方便地在线程间调配负载
- IO事件发生的线程是固定的,同一个TCP连接不必考虑事件并发
Event loop,代表了线程的主循环,需要哪个线程干活,就把timer或IO channel(如TCP连接)注册到哪个线程的loop里即可。对实时性有要求的connection可以单独用一个线程;数据量大的connection可以独占一个线程,并把数据处理任务分摊到另外计算线程中(用线程池);其他辅助性connections可以共享一个线程
对于non-trivail服务端程序,一般采用non-blocking IO + IO multiplexing,每个connection/acceptor都会注册到某个event loop上,程序里有多个event loop,每个线程至多有一个event loop
如何线程安全?
线程池
没有IO,光有计算任务的线程,使用event loop比较浪费
多线程服务器的试用场合
- 当线程很多时,可以让一个线程只处理一个tcp连接(甚至半个),通常使用阻塞I/O
- 当线程资源不多,线程数与cpu数想当时,通常用非阻塞I/O+多路复用