一起来学POSIX thread 之 线程利弊与选择
1、线程的利
在多处理器系统中,利用线程的并行性可以提高计算性能。一个运行在双CPU上的计算密集型多线程程序几乎可以获得传统的单线程程序两倍的性能。“几乎两倍”是基于这样的事实:创建额外的线程和执行线程间的同步会带来额外的开销。
虽然线程在多处理器的系统中能带来程序性能的提高,但性能的提高并不和处理器的个数成正比,而是和线程的并行率有关,这里我们需要引入Amdahl法则。在Amdahl法则等式中,p代表可并行代码与整个执行时间的比率,n代表代码可以使用的处理器数目。我们假设没有使用线程时的工作时间为1,则使用线程进行并行的工作时间=非并行工作时间(1-p)+并行工作时间(p/n)。Amdahl法则为:
Speedup=1/((1-p)+ (p/n))
Amdahl法则显示了串行限制并行的简单关系。当程序没有可以并行代码时(p = 0)加速比为1,即不是一个并行的程序。如果程序不需要任何同步或者串行代码(p = 1),则加速比为n(处理器的数目)。相反的如果需要同步的越多,则并行能带来的好处就越少。换言之,没有依赖性的活动比高度相关的活动更具有可扩展性:因为独立的活动之间需要更少的同步。
Amdahl法则是一个帮助你理解可扩展性的极好的思维训练,然而它却不是一个实用的工具,因为几乎不可能为每个程序精确技术p值。为精确计算p值,不仅需要考虑代码中所有的串行段,而且还要考虑操作系统内核甚至硬件的因素。多处理器系统的硬件必须提供一些同步访问内存的机制。当每个处理器有自己的数据高速缓存的数据以及内存中的数据保持一致。必须考虑所有这些串行以获得精确的p值计算。
当应用程序在等待外设I/O操作时,线程为应用程序提供了可以执行其他的计算的能力,为程序并发提供了更有效,更自然的开发方式。
线程开发必然会引入同步机制,这样能清晰的表明程序中事件之间的依赖性而不会像串行编程中那么的模棱两可。
2、线程的弊
任何事物都具备双面性,线程也不例外也有弊端,在程序中引入线程会增加额外的计算负荷:同步。在任何线程代码中你都不可避免的需要使用某种同步机制(互斥量,条件变量等)。使用太多的同步很容易损失性能。
从程序中排除瓶颈代码,如添加线程来执行多个并发的I/O操作,可能会妨碍发现其他底层的瓶颈问题——ANSI C 库、操作系统、文件系统、设备驱动程序、内存和I/O结构或者设备控制器。这些影响通常难于预测或度量,无法清晰记录。
一个很少被外部事件阻塞的计算密集型线程,无法与同类其他线程有效地共享一个处理器。一个I/O线程可能隔段时间会中断它一次,但是I/O线程会被其他外部事件阻塞,所以计算密集型线程还是会再次运行。同一个程序中的多个线程如何共享处理器还与线程的调度策略有关。
尽管线程编程模型的基本思想简单,但是编写实际的代码不是件容易的事。编写能够在多个线程中良好工作的代码需要认真的思考和计划。你需要明白同步协议和程序中的不变量,你不得不避免死锁、竞争和优先级倒置。
线程代码相对于串行代码跟难于调试,bug很可能跟特定的执行顺序相关,进行调试时因为改变了执行顺序而使bug无法重现。
3、是否使用线程是一个问题?
如果一些问题本身就是非并发的,添加线程只能降低程序的性能并使程序复杂。如果程序中的每一步都需要上一步的结果,则使用线程不会有任何帮助。
适合使用线程的应用:
1)计算密集型应用,为了能在多处理器系统上运行,将这些计算分解到多个线程中实现。
2)I/O密集型应用,为提高性能,将I/O操作重叠。很多线程同时等待不同的I/O操作。
大部分程序有一些本质上的并发,即使那种在处理命令的同时从输入设备中读取一下命令的简单并发。多线程程序通常比串行程序更快、响应性能更好,它们比实现统同样功能的非线程异步程序更易于开发和维护。
那么,你该使用线程?你可能不必为你开发的所有程序使用它,但线程编程确实是所有软件开发人员应该理解且掌握的技术。