10 Multithreading

CPU的时钟频率受到物理因素的限制。当时钟频率受到限制时,提高CPU密集型程序的吞吐量的方法是同时进行多个任务。有三种并行处理的方法:
•使用多个CPU或多核CPU,如本章所述。
•利用现代CPU的乱序执行能力,如第11章所述。
•利用现代CPU的向量操作,如第12章所述。
大多数现代CPU都具有两个或更多的核心,可以预期将来核心数量将会增加。要使用多个CPU或CPU核心,我们需要将工作分解为多个线程。这里有两个主要原则:功能分解和数据分解。在此,功能分解意味着不同的线程执行不同类型的工作。例如,一个线程可以处理用户界面,另一个线程可以处理与远程数据库的通信,第三个线程可以执行数学计算。重要的是,用户界面不在同一个线程中作为非常耗时的任务,因为这将导致令人恼人的长而不规则的响应时间。通常有用的是将耗时的任务放入具有低优先级的单独线程中。
然而,在许多情况下,存在一个占用大部分资源的单一任务。在这种情况下,我们需要将数据分成多个块,以利用多个处理器核心。然后,每个线程应处理自己的数据块。这就是数据分解。
在决定是否优势地使用并行处理时,区分粗粒度并行和细粒度并行非常重要。粗粒度并行是指可以独立于在并行运行的其他任务进行长序列操作的情况。细粒度并行是一种将任务分成许多小子任务的情况,但在需要与其他子任务进行协调之前,不可能长时间地工作在特定的子任务上。
与粗粒度并行相比,多线程技术在细粒度并行方面的效率更高,因为不同线程之间的通信和同步较慢。如果粒度太细,则将任务分成多个线程不是优势。乱序执行(第11章)和向量操作(第12章)是更有用的利用细粒度并行的方法。
利用多个CPU核心的方法是将工作分成多个线程。线程的使用在第61页上讨论。在数据分解的情况下,最好没有更多具有相同优先级的线程,而是等于系统中可用的核心或逻辑处理器的数量。可用逻辑处理器的数量可以通过系统调用(例如Windows中的GetProcessAffinityMask)确定。
有几种将工作负载分配给多个CPU核心的方法:
• 定义多个线程,并将相等数量的工作分配给每个线程。该方法适用于所有编译器。
• 使用自动并行化。Gnu和Intel编译器可以自动检测代码中的并行化机会,并将其分成多个线程,但编译器可能无法找到最佳的数据分解方法。
• 使用OpenMP指令。OpenMP是在C++和Fortran中指定并行处理的标准。这些指令由大多数编译器支持。有关详细信息,请参阅www.openmp.org和编译器手册。
• 使用std::thread。它的功能较OpenMP少,但在各个平台上是标准化的。
• 使用具有内部多线程的函数库,例如Intel Math Kernel Library。
多个CPU核心或逻辑处理器通常共享同一级缓存,至少在最后级缓存中,有些情况甚至共享同一级1缓存。共享同一级缓存的优点是线程之间的通信变得更快,并且线程可以共享相同的代码和只读数据。缺点是如果线程使用不同的内存区域,缓存将被填满,并且如果线程对相同的内存区域进行写操作,就会发生缓存争用。
只读数据可以在多个线程之间共享,而被修改的数据应该为每个线程单独分开。将两个或更多线程写入同一缓存行是不好的,因为线程会使彼此的缓存失效,导致延迟增加。最简单的方法是在线程函数中将线程特定数据声明为局部变量,这样它将存储在栈上。每个线程都有自己的栈。或者,您可以定义一个结构体或类来包含线程特定的数据,并为每个线程创建一个实例。为了避免多个线程写入同一缓存行,该结构体或类应该至少按照缓存行大小进行对齐。现代处理器上的缓存行大小通常为64字节。未来处理器上的缓存行大小可能会更大(128或256字节)。
线程之间的通信和同步有各种方法,比如信号量、互斥锁和消息系统等。所有这些方法都需要时间。因此,应该组织数据和资源,以使线程之间的通信量最小化。例如,如果多个线程共享同一个队列、列表、数据库或其他数据结构,则可以考虑为每个线程提供自己的数据结构,然后在所有线程完成耗时的数据处理后将这些数据结构合并。
在只有一个逻辑处理器的系统上运行多个线程如果这些线程竞争相同资源并没有优势。但是,将耗时的计算放在一个优先级低于用户界面的单独线程中是一个好主意。将文件访问和网络访问放在单独的线程中也很有用,这样一个线程可以进行计算,而另一个线程正在等待来自硬盘或网络的响应。
10.1 Simultaneous multithreading
许多微处理器可以在每个核心中运行两个线程。例如,一个有四个核心的处理器可以同时运行八个线程。这个处理器有四个物理处理器但是八个逻辑处理器。"Hyperthreading"是英特尔用于表示同时多线程的术语。在同一个核心中运行两个线程将始终竞争相同的资源,如缓存和执行单元。如果任何共享资源是限制性因素,则使用同时多线程不会带来优势。相反,由于缓存清除和其他资源冲突,每个线程可能运行速度不到一半。但是,如果大部分时间用于缓存未命中、分支预测失误或长依赖链,则每个线程将以超过单线程速度的一半运行。在这种情况下使用同时多线程是有优势的,但性能并没有翻倍。共享核心资源的线程将始终比在核心中单独运行的线程运行更慢。
通常需要进行实验以确定在特定应用程序中是否使用同时多线程是有利的。如果同时多线程不是有利的,则需要查询某些操作系统函数(例如 Windows 中的 GetLogicalProcessorInformation)以确定处理器是否具有同时多线程。如果是这样,则可以通过仅使用偶数逻辑处理器(0、2、4等)来避免同时多线程。旧操作系统缺少区分物理处理器数量和逻辑处理器数量所需的功能。
没有办法告诉处理器给一个线程比另一个线程更高的优先级。因此,低优先级线程从在同一核心中运行的高优先级线程中窃取资源的情况经常发生。避免在同一个处理器核心中运行具有广泛不同优先级的两个线程是操作系统的责任。然而,当代操作系统不能充分解决这个问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值