一、线程 / Threading
线程这个概念大概在1993年后才慢慢流行起来。线程是操作系统进行调度的最小单位,拥有少量的资源,如寄存器和栈。线程的特点是共享地址空间,从而高效地共享数据。多线程的价值是更好地发挥多核处理器的功能。
二、使用线程的几种方式
1. 流水线
每个线程反复地在数据系列集上执行同一种操作,并把操作结果传递给下一步骤的其他线程,这就是流水线方式。
在流水线方式中,数据元素流串行地被一组线程顺序处理。每个线程依次在每个元素上执行一个特定的操作,并将结果传递给流水线中的下一个线程。
2. 工作组
每个线程在自己的数据上执行操作。工作组中的线程可能执行同样的操作,也可能执行不同的操作,但是它们一定独立地执行。
在工作组模式中,数据由一组线程分别独立处理。通常有两种模式:SIMD(single instruction, multiple data, 单指令多数据流)和MIMD(multiple instruction, multiple data, 多指令多数据)。SIMD是指所有的工作线程在不同的数据部分上执行相同的操作,MIMD是指工作组中的线程在不同的数据上执行不同的操作。
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模式避免了线程动态创建和销毁的额外开销,将线程放在池中,无需交换数据,将上下文切换、同步、数据移动和动态内存管理的开销都降到了最低。
3. 推荐模式
推荐的多线程编程模式:one loop per thread + 线程池。
event loop用作IO多路复用,配合非阻塞IO和定时器;线程池用作计算,可以是任务队列或生产者消费者队列。
小结
线程无法给所有编程问题提供最好的解决方案。线程并不总是容易使用,也不能保证总是提供更好的性能。某些问题本身是非并发的,使用线程只能降低程序的性能并使程序复杂。大部分程序有一些本质上的并发,这种情况下,多线程程序通常比串行程序更快、响应性能更好,而且比实现同样功能的非线程异步程序更易于开发和维护。(张玉遵 | 天存信息)