第三章——读书笔记

本文探讨了多线程服务器的编程模型,包括one loop per thread、线程池和推荐模式,并分析了多线程服务器的适用场合,如处理并发连接,以及何时选择单线程。同时,解释了线程间的分工,如IO线程、计算线程和第三方库线程,并讨论了多线程在高并发、吞吐量和响应时间方面的影响。
摘要由CSDN通过智能技术生成

多线程服务器的适用场合与常用编程模型

3.1 进程与线程

一个进程是“内存中正在运行的程序”,每个进程有自己独立的地址空间。“在同一个进程”还是“不再同一个进程”,是系统功能划分的重要决策点。

线程的特点是共享地址空间,从而可以高效地共享数据。一台机器上的多个进程能高效地共享代码段(操作系统可以映射为同样的物理内存),但不能共享数据。

3.2 单线程服务器的常用编程模型

在高性能的网络程序中,使用得最广泛得是non-blocking I/O + I/O multiplexing这种模型,即Reactor模型。

non-blocking I/O + I/O multiplexing这种模式中,程序的基本结构是一个事件循环,以事件驱动和事件回调的方式实现业务逻辑。

while(!done)
{
    int timeout_ms = max(1000, getNextTimeCallback());
    int retval = ::poll(fds, nfds, timeout_ms);
    if(retval < 0)
    {
        //处理错误,回调用户的error handler
    }
    else
    {
        //处理到期的timer,回调用户的timer handler
        if(retval > 0)
            //处理I/O事件,回调用户的IO event handler
    }
}

3.3 多线程服务器的常用编程模型

3.3.1 one loop per thread

在此种模型下,程序里的每个I/O线程都有一个event loop,用于处理读写和定时事件。

这种方式的好处是:

  • 线程数目基本固定,可以在程序启动的时候设置,不会频繁创建与销毁。
  • 可以很方便地在线程间调配负载。
  • IO事件发生的线程是固定的,同一个TCP连接不必考虑事件并发。

Eventloop代表了线程的主循环,需要让哪个线程干活,就把timerIO channel注册到哪个线程的loop里即可。对实时性有要求的connection可以单独用一个线程;数据量大的connection可以独占一个线程,并把数据处理任务分摊另几个计算线程中(用线程池);其他次要的辅助性connections可以共享一个线程。

对于non-trivial的服务端程序,一般会采用non-blocking I/O + I/O multiplexing,每个connection/acceptor都会注册到某个event loop上,程序里有多个event loop,每个线程至多有一个event loop

3.3.2 线程池

对于没有I/O而光有计算任务的线程,使用event loop有点浪费,用一种补充方案,用blocking queue实现的任务队列。

typedef boost::function<void()> Functor;
BlockingQueue<Functor> taskQueue;	//线程安全的阻塞队列

void workerThread()
{
    while(running)
    {
        Functor task = taskQueue.take();	//this blocks
        task();
    }
}

用这种方式实现线程池特别容易,以下是启动容量为N的线程池。

int N = num_of_computing_threads;
for(int i = 0; i < N; i++)
{
    create_thread(&workThread);
}

使用起来也简单

Foo foo;
boost::funtion<void()> task = boost::bind(&Foo::calc, &foo);
taskQueue.post(task);

3.3.3 推荐模式

推荐的C++多线程服务端编程模式为one loop per thread + thread pool

  • event loop用作IO multiplexing,配合non-blocking IO和定时器
  • thread pool用来做计算,具体可以是任务队列或生产者消费者队列。

3.5 多线程服务器的适用场合

开发服务端程序的一个基本任务是处理并发连接,现在服务端网络编程处理并发连接主要有两种方式。

  • 当“线程”很廉价时,一台机器上可以创建远高于CPU数目的“线程”。这时一个线程只处理一个TCP连接,通常使用阻塞IO。
  • 当线程很宝贵时,一台机器上只能创建与CPU数目相当的线程。这时一个线程要处理多个TCP连接上的IO,通常使用非阻塞的IO和IO multiplexing。

3.5.1 必须用单线程的场合

有两种场合必须使用单线程

  • 程序可能会fork()
  • 限制程序的CPU占用率;

3.5.2 单线程程序的优缺点

从编程的角度,单线程程序的优点是:简单。

Event loop有一个明显的缺点,它是非抢占的。假设事件a的优先级高于事件b,处理事件a需要1ms,处理事件b需要10ms。如果事件b稍早于a发生,那么当事件a到来时,程序已经离开了poll()的调用,并开始处理事件b。事件a要等上10ms才有机会被处理,总的响应时间为11ms。这相当于发生了优先级反转。这个缺点可以使用多线程来客服。

3.5.3 使用多线程程序的场景

多线程的适用场景是:提高相应速度,让IO和“计算”相互重叠,降低延迟。虽然多线程不能提高绝对性能,但是能提高平均响应性能。

一个程序要做成多线程的,大致要满足:

  • 有多个CPU可用,单核机器上多线程没有性能优势。
  • 线程间有共享数据,即内存中的全局状态。
  • 共享的数据是可以修改的,而不是静态的常量表。如果数据不能修改,那么可以在进程间用shared memory
  • 提供非均质的服务。即,事件的响应有优先级差异,我们可以用专门的线程来处理优先级高的事件。防止优先级反转。
  • 多线程能有效地划分责任与功能,让每个线程的逻辑比较简单,任务单一,便于编码。而不是把所有的逻辑都塞到event loop里面,不同类别的事件之间相互影响。

线程的分类

一个多线程服务程序中的线程大致可以分为3类。

  1. IO线程,这类线程的主循环是IO multiplexing,阻塞地等在select/poll/epoll_wait系统调用上。这类线程也处理定时事件。当然它的功能不止IO。
  2. 计算线程,这类线程的主循环是blocking queue,阻塞地等在condition variable上,这类线程一般位于thread pool中。这种线程通常不涉及IO,一般要避免任何阻塞操作。
  3. 第三方库所用的线程,比如logging

3.6 多线程服务器的适用场合例释和答疑

1.Linux能同时启动多少个线程

对于32-bit Linux,一个进程的地址空间是4GB,其中用户态能访问3GB左右,而一个线程的默认栈大小是10MB,一个进程大约最多能同时启动300个线程。

2.多线程能提高并发度么?

如果指的是“并发连接数”,则不能。

假如单纯采用thread per connection的模型,那么并发连接数最多300。采用前文推荐的one loop per thread,至少不逊于单线程程序。

小结:thread per connection不适合高并发场合,其scalability不佳,one loop per thread的并发度足够大,且与CPU数目成正比。

3.多线程能提高吞吐量

对于计算密集型服务,不能。

4.多线程能减低响应时间么?

如果设计合理,充分利用多核资源的话,可以。在突发请求时效果尤为明显。

例如:多线程处理输入

  1. 读取并解析客户端输入。
  2. 操作hashtable
  3. 返回客户端。

在单线程模式下,这3步是串行执行的。在启用多线程模式时,它会启用多个输入线程(默认是4个),并在建立连接时按round-robin法把新连接分派给其中一个输入线程,这就是one loop per thread模型。这样一来,第1步的操作就能多线程并行,在多核机器上提高多用户的响应速度。第2步用了全局锁。

5.多线程程序如何让IO和“计算”相互重叠,降低延迟

基本思路是,把IO操作通过BlockingQueue交给别的线程去做,自己不必等待。

例1:日志。

在一次请求响应中,可能要写多条日志消息,而如果采用同步的方式写文件,多半会降低性能,因为:

  • 文件操作一般比较慢,服务线程会等在IO上,让CPU闲置,增加响应时间。
  • 就算有buffer,也不行。多个线程一起写,为了不至于把buffer写错乱,往往需要加锁。这样会让服务线程互相等待,降低并发度。

解决方法是单独用一个logging线程,负责写磁盘文件,通过一个或多个BlockingQueue对外提供接口。别的线程要写日志的时候,先把消息准备好,然后往queue里一塞就行,基本不用等待。这样服务线程的计算就和logging线程的磁盘IO相互重叠,降低了服务线程的响应时间。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值