linux多线程服务端编程读书笔记——第三章

来源:互联网 发布:数据魔方为什么下线 编辑:程序博客网 时间:2024/06/06 14:10

本章作者主要是总结了一两种常用的线程模型。归纳了进程通信与线程同步的最佳实践


进程与线程的区别

  1. 进程是文件系统中的最重要的两个概念之一(令一个是文件)。简单地说,一个进程是内存中正在运行的程序。每个进程有自己独立的地址空间。
  2. 线程是依附在进程上面的,它的特点是共享地址空间,从而可以高效地共享数据。一台机器上的进程可以共享代码段,但是不能共享数据。如果多个进程大量共享内存,那就等于是把多进程当成多线程来写。

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

在高性能网络程序中,使用的最广泛的是“non-blocking IO + multiplexing IO”这种模型,即reactor模式。

在non-blocking IO + multiplexing IO 模型中,程序的主要结构是一个事件循环。以事件驱动和事件回调的方式实现业务逻辑。Reactor模型优点编写简单,效率不错,对于IO密集型应用是一个不错选择。但是它也有其本质的缺点:它要求事件的回调函数必须是非阻塞的,而且对于网络IO的请求相应协议,它容易割裂业务逻辑,使其散布与多个回调函数之间。


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

多线程的服务器编程模型主要有以下四种:

  • 为每个请求创建(临时创建)一个线程,使用阻塞式IO操作。
  • 使用线程池,同样使用阻塞式IO操作。相比于第一种,这是提高性能的措施。
  • 使用non-blocking IO + IO multiplexing
  • Leader/Follower等高级模型

muduo中主要采用的是第三种来编写多写成网络服务程序。即 non-blocking + one loop per thread。

non-blocking + one loop per thread

在这种模型下,程序里的每个IO线程都有一个event loop,用于处理读写事件。这种方式有以下好处:

  1. 线程数目固定,可以在启动的时候设置,不需要频繁创建和销毁
  2. 可以很方便在线程间调配负载(根据各个IO线程处理的IO事件多少来决定下一个IO事件由那个IO线程处理)
  3. IO事件发生的线程是固定的,同一个TCP不需要考虑事件并发。

    线程池
    对于没有IO而光有计算任务的线程,使用event loop有点浪费。一般用blocking quene的方法来实现任务队列。除了任务队列,还可以用BlockingQuene实现数据的生产者消费者队列,即T是数据类型而不是函数类型。

推荐模式
muduo推荐的C++多线程服务器编程模式为:one (event) loop per thread + thread pool。其中

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

进程间通信只用TCP

进程间通信包含下面的模式:

  • 匿名管道
  • 命名管道
  • POSIX消息队列
  • 共享内存
  • 信号
  • socket
  • ……..

进程间通信muduo主要选择socket。好处在于:

  • 跨主机,具有伸缩性
  • 容易debug

多线程服务器的适用场合

开发服务器程序的基本任务是处理并发连接,服务端处理网络并发连接主要有下面两种方式

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

为了充分利用硬件资源,本书主要采用后一种方式。下面需要解决的问题是:多个线程是应该属于一个进程,还是分属于多个进程?

首先,如果要在一台机器上提供一种服务或者执行一个任务,可用的模式有:

  • 运行一个单线程的进程
  • 运行一个多线程的进程
  • 运行多个单线程的进程
  • 运行多个多线程的进程

可以对这些模式做以下总结:

  • 模式1是不可伸缩的,不能发挥多核的计算能力
  • 模式3是公认的主流模式,它有以下两个子模式

    1. 3a 简单地把模式1种的进程运行多份
    2. 3b 主进程 + woker进程
  • 模式2 是被很多人鄙视的,因为多线程程序难写,而且与3相比没有什么优势

  • 模式4更是千夫所指,它汇聚了2和3的缺点。

下面主要讨论模式3和模式2的优劣
问题主要是:什么时候一个服务器程序应该用多线程编写??

必须使用单线程的场合

  1. 程序会调用fork
  2. 限制程序的CPU占有率

只有单线程的程序才能fork。而且单线程程序能限制CPU的占有率(无论如何,它都只占用一个core,如果多线程的话,可能会把多个核都占满),避免过分抢占系统资源

单线程的优缺点

单线程程序编程简单,但是由于eventloop的非抢占式,单线程可能会造成优先级反转(P70)。这个缺点可以由多线程来克服。

其实在性能上,多线程没有绝对的优势。因为如果很少的CPU负载能让IO慢跑,那多线程是没啥用处的,而且即使是在CPU满跑的情况下,选用模式3a应该是最合适的

综上,多线程几乎没有什么优势

适用于多线程的场景

  • 有多个CPU可用
  • 线程间有共享数据
  • 线程间的共享数据可以修改
  • 提供非匀质服务,也就是事件的相应有优先级差异,可以用专门的线程来处理优先级高的事件(这就是解决前面单线程会遇到的优先级反转问题)。
  • 程序要有相当的计算量
  • 利用异步操作
  • 能scale up。能享受增加CPU的好处

一个多线程程序中线程主要分为以下几类

  • IO线程。主循环是IO multiplexing,阻塞在epoll_wait/select/poll系统调用上。该线程也处理定时器事件。当然一些简单的计算也可以放进去
  • 计算线程。主循环是blocking quene,阻塞在condition variable的等待上。
  • 第三方库所用的线程。

多线程和单线程多进程的取舍

在其他条件相同的情况下,可以根据工作集的大小来选择

工作集:服务程序响应一次请求所访问的内存大小。

如果工作集较大,那么就用多线程,避免CPU cache换入换出,影响性能。否则就用单线程多进程,享受单线程编程的便利。

1 0
原创粉丝点击