优化多线程应用程序
现代 CPU 每年都在增加更多核心。截至 2024 年,你可以购买拥有超过 200 个核心的服务器处理器!即便是拥有 16 个执行线程的笔记本电脑,如今也已相当普遍。由于每颗 CPU 中蕴含着如此强大的处理能力,如何有效利用所有硬件线程变得愈加具有挑战性。让软件能够随着 CPU 核心数量的增长而良好扩展,对于应用程序的未来成功至关重要。
服务器和客户端产品在利用并行性(parallelism)方面存在差异。大多数服务器平台设计用于处理来自大量用户的请求。这些请求通常彼此独立,因此服务器可以并行处理它们。如果系统上有足够的负载,应用程序本身可以是单线程的,平台利用率仍然很高。然而,当你将服务器平台用于 HPC(高性能计算)或 AI 计算时,则需要利用所有可用的计算能力。另一方面,笔记本电脑和台式机等客户端平台拥有为单个用户服务的全部资源。在这种情况下,应用程序必须充分利用所有可用核心,以提供最佳用户体验。本章将重点介绍能够扩展到大量核心的应用程序。
从软件角度来看,实现并行性主要有两种方式:多进程(multiprocessing)和多线程(multithreading)。在多进程应用程序中,多个独立进程并发运行。每个进程都有自己的内存空间,并通过管道、套接字或共享内存等进程间通信机制与其他进程通信。在多线程应用程序中,单个进程包含多个线程,这些线程共享进程的相同内存空间和资源。同一进程中的线程可以更方便地通信和共享数据,因为它们可以直接访问相同的内存空间。然而,线程间的同步通常更为复杂,容易出现竞态条件(race conditions)和死锁(deadlocks)等问题。本章将主要关注多线程应用程序,但某些技术也可以应用于多进程应用程序。本章将同时展示两种类型的应用示例。
在讨论面向吞吐量的应用程序时,我们可以区分以下两种类型:
- 大规模并行应用程序(Massively parallel applications)。此类应用程序通常能随核心数量良好扩展。它们旨在处理大量独立任务。大规模并行程序通常使用分治技术(divide-and-conquer technique)将工作分解为更小的任务(也称为工作线程,worker threads),并并行处理它们。科学计算、视频渲染、数据分析、AI 等都是此类应用的典型例子。这类应用的主要障碍是共享资源(如内存带宽)的饱和,这可能会有效地阻塞进程中的所有工作线程。
- 需要同步的应用程序(Applications that require synchronization)。此类应用程序的工作线程共享资源以完成任务。工作线程相互依赖,这会产生某些线程被阻塞的时段。数据库、Web 服务器和其他服务器应用程序都是此类应用的典型例子。这类应用的主要挑战是最大限度地减少所需的同步,并避免共享资源上的竞争。
本章将探讨如何分析这两种类型应用程序的性能。由于本书关注的是底层性能,我们不会讨论算法层面的优化,如无锁数据结构,这些内容在其他书籍中已有充分涵盖。