多线程程序开发简介

一、线程 / Threading

线程这个概念大概在1993年后才慢慢流行起来。线程是操作系统进行调度的最小单位,拥有少量的资源,如寄存器和栈。线程的特点是共享地址空间,从而高效地共享数据。多线程的价值是更好地发挥多核处理器的功能。

二、使用线程的几种方式

1. 流水线

每个线程反复地在数据系列集上执行同一种操作,并把操作结果传递给下一步骤的其他线程,这就是流水线方式

在流水线方式中,数据元素流串行地被一组线程顺序处理。每个线程依次在每个元素上执行一个特定的操作,并将结果传递给流水线中的下一个线程。

图1

2. 工作组

每个线程在自己的数据上执行操作。工作组中的线程可能执行同样的操作,也可能执行不同的操作,但是它们一定独立地执行。

工作组模式中,数据由一组线程分别独立处理。通常有两种模式:SIMD(single instruction, multiple data, 单指令多数据流)和MIMD(multiple instruction, multiple data, 多指令多数据)。SIMD是指所有的工作线程在不同的数据部分上执行相同的操作,MIMD是指工作组中的线程在不同的数据上执行不同的操作。

图2

3. 客户端 / 服务器

一个客户端为每一件工作与一个独立的服务器“订契约”。通常“订契约”是匿名的,一个请求通过某种接口提交。

在客户服务器系统中,客户端请求服务器对一组数据执行某个操作。服务器独立地执行操作——客户端或者等待服务器执行,或者并行地执行,在后面需要时再查找结果。

图3

三、线程的好处

多线程编程具有如下优点:

在多处理器系统中开发程序的并行性。并行性这一优点需要特殊硬件支持,其他优点对硬件无要求。
在等待慢速外设I/O操作结束的同时,程序可以执行其他计算,为程序的并发提供更有效、更自然的开发方式。
一种模块化编程模型,能清晰地表达程序中独立事件间的相互关系。

四、线程的代价

1. 计算负荷

线程代码中的负荷包括由于线程间同步所导致的直接影响。很多算法在某些情况下可避免同步,但在几乎任何线程代码中都需要使用某种同步机制,同步很容易损失性能。

计算密集型线程数量若比可用的处理器多,则可能比单线程实现获得更好的代码结构,但程序性能也会更糟,这是由于多线程结构在要完成的工作上增加了同步和调度开销,而可用的资源并没有变。

2. 编程规则

线程模型基本思想简单,但编写能在多线程中良好工作的代码需要认真思考和规划,包括同步协议,避免死锁、竞争和优先级倒置。如果有可用的库,应尽量使用库代码而不是自己编写。

3. 更难以调试

调试不可避免的改变了事件的时序,这对于串行代码问题不大,但对于异步代码却是致命的。一个线程因调试陷阱而运行得慢了,要跟踪的问题可能就不会出现,调试无法再现的错误是一件让人头疼的事情。

五、多线程适用场合

从功能上讲,没有什么是多线程能做到而单线程做不到的,反之亦然。

如果用很少的CPU负载就能让IO跑满,或者用很少的IO流量就能让CPU跑满,那么多线程就没有什么优势。

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

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

· 有多个CPU可用,单核机器上多线程无性能优势;
· 线程间有共享数据,即内存中的全局状态;
· 共享的数据是可以修改的; ·
· 事件的响应有优先级差异,可用专门线程处理高优先级事件,防止优先级反转;
· 延迟和吞吐量同样重要,不是简单的IO密集或CPU密集型程序;
· 利用异步操作,如记日志,无论发日志消息还是写日志文件,都不应阻塞关键路径;
· 可扩展,一个好的多线程程序应能享受增加CPU数目带来的好处;
· 多线程能有效地划分责任与功能,让每个线程的逻辑简单,任务单一,便于编码。

六、多线程常用编程模型

多线程常用编程模型有如下几种:

· 每个请求创建一个线程,使用阻塞式IO操作(伸缩性不佳);
· 使用线程池,同样使用阻塞式IO操作;
· 使用非阻塞IO+IO多路复用;
· Leader/Follower等高级模式;

1. one loop per thread

此模型下,程序里的每个IO线程有一个event loop(用作IO多路复用),用于处理读写和定时时间。event loop代表了线程的主循环,需要让哪个线程干活,就把timer或IO channel注册到哪个线程的loop里即可。

event loop描述如下:

while there are still events to process:
    e = get the next event
    if there is a callback associated with e:
        call the callback

2. Leader / Follower

此模型会创建一个线程池,每个线程有三种状态:leading, following, processing。Leader线程负责监听请求,其他线程作为follower处于等待状态,当leader收到请求后,首先通知一个follower线程将其提拔为新的leader,然后自己去处理这个请求,处理完毕后加入follower线程等待队列,等待下次成为leader。

Leader/Follower模式避免了线程动态创建和销毁的额外开销,将线程放在池中,无需交换数据,将上下文切换、同步、数据移动和动态内存管理的开销都降到了最低。

图4

3. 推荐模式

推荐的多线程编程模式:one loop per thread + 线程池

event loop用作IO多路复用,配合非阻塞IO和定时器;线程池用作计算,可以是任务队列或生产者消费者队列。

小结

线程无法给所有编程问题提供最好的解决方案。线程并不总是容易使用,也不能保证总是提供更好的性能。某些问题本身是非并发的,使用线程只能降低程序的性能并使程序复杂。大部分程序有一些本质上的并发,这种情况下,多线程程序通常比串行程序更快、响应性能更好,而且比实现同样功能的非线程异步程序更易于开发和维护。(张玉遵 | 天存信息)

posted @ 2021-05-31 11:34  天存信息  阅读(348)  评论(0编辑  收藏  举报