并发代码的设计要义再与谨慎思考,然而,多线程代码比串行代码需要考虑更多因素,不仅需要考虑普通因素,如:封装,耦合等,还需要分析共享那些数据,如何同步那些数据的访问等。本章内容包含:考虑使用多少线程,由那个线程执行什么代码,以及如何影响代码的清晰度,最后怎样让代码达到最优性能。
1.1在线程间切分任务方法
-
先在线程间切分数据,再开始处理
eg:最简单的切分数据方法,将最前面的N个元素,分配给一个线程,将其后的N个元素分配给另外一个线程,以此类推。
-
以递归方式划分数据
基本步骤:选定一个元素为比较的基准元素,将数据集按大小划分前后两部分。重新构成新序列,再针对这两部分递归排序。
-
依据工作类别划分任务
要点:每个线程都独立执行不同的任务,不依赖其他线程,即每部分代码只承担单一的功能职责。
方法如下:
多线程代码的性能受到诸多因素的影响,具体如下:
-
处理器的数量
-
数据竞争和缓存乒乓
一个数据复制到两个处理器对应的缓存中,从而让它们运行无碍,但如果其中一个线程改动了数据,则该变化必须传达至另一个处理器缓存,但是因为耗时,会导致数据竞争。
-
不经意共享
若一个缓存块中数据互不关联,且需要被多个线程访问,这就会导致不经意共享问题。
-
数据的紧凑程度
若单个线程访问的数据在内存松散分布,那它们就可能被纳入不同的缓存块,相反,假如单个线程访问的数据在内存中紧凑聚集,则它们很可能位于同一缓存块。
-
过度任务切换与线程过饱和
如果线程数目过多,须全速运行的线程数目也持续增加,早晚将超出可供调配的处理器数目,操作系统也会随之开始剧烈的切换任务。
3设计数据结构以提升多线程程序的性能
-
针对复杂操作的数据划分
首先遵循上述介绍的原则,只有是在线程间划分大块数据,它们全部都适用;其次,还要仔细分析数据访问模式的所有细节,并预判出所有损失的潜在诱因。
-
其他数据结构的访问模式
给定一个线程,尽可能缩减它需要的数据量,令不同的线程所访问的数据互相充分隔离,以防不经意共享
4.设计并发代码时要额外考虑的因素
-
并发算法代码中的异常安全
解决办法:加入异常安全,工具:std::async()
-
可伸缩性和Amdahl定律
伸缩性的意义在于,如果系统增加了处理器,就应该确保应用程序能充分利用它们;Amdahl定律:若想利用并发特性提升程序性能,就应当统筹协调应用程序的整体设计,尽可能令并发程度达到最大,并且确保实际的工作量始终维持一定水平,足以让多个处理器有效运行。
-
利用多线程“掩藏”等待行为
若线程所含处理器的数目等于线程的数目,则无论出于什么缘由而等待,一旦线程发生阻塞,处理器便无所事事,即浪费了CPU时间,因此,如果能够预知,某一线程将花费相当部分的运行时间进行等待。就可以多运行一个或几个线程,以充分利用空闲的CPU时间。
-
借并发特性改进响应能力
可以指派一个新线程完整地执行冗长的任务,GUI事件则由另外一个线程专门处理。