OS 中的 POSIX 线程

2025年4月29日 | 9 分钟阅读

POSIX 线程通常称为 PThreads。它是一种独立于语言和并行执行模型的执行模型。它允许程序控制多个重叠的时间段内的不同工作流。每个工作流称为一个 线程。通过调用 POSIX 线程 API 来创建和控制这些流。POSIX 线程是 POSIX.1c 标准、线程扩展(IEEE Std 1003.1c-1995)定义的 API。

该 API 的实现可在许多类 Unix POSIX 兼容的操作系统上使用,例如 FreeBSD、NetBSD、OpenBSD、Linux、macOS、Android、Solaris、Redox 和 AUTOSAR Adaptive,通常以库 libPThread 的形式提供。DR-DOS 和 Microsoft Windows 实现也存在于 SFU/SUA 子系统中,该子系统提供了许多 POSIX API 的原生实现,以及第三方软件包(如 PThreads-w32),该软件包在现有 Windows API 之上实现了 PThreads

PThreads 是一个高度具体的**多线程**系统,是 UNIX 系统的默认标准。PThreads 是 POSIX 线程的缩写,POSIX 是可移植操作系统接口的缩写,它是一种操作系统必须实现的接口类型。POSIX 中的 PThreads 概述了操作系统必须提供的线程 API。

PThreads 在多处理器或多核系统上效果良好,在这些系统中,进程流可以被调度到另一个处理器上执行,通过并行或分布式处理提高速度,因为系统不必为进程创建新的系统、虚拟内存空间和环境,线程比分叉或创建新进程的开销小得多。

PThread 头文件

要使用 PThread 接口,我们必须在 CPP 脚本的开头包含 PThread.h 头文件。

为什么要使用 PThreads?

以下是关于在操作系统中使用 PThreads 的原因,例如:

  • 采用 PThreads 的根本目的是提高程序性能。
  • 与创建和管理进程的成本相比,创建和管理线程的开销要小得多。管理线程比管理进程所需的系统资源更少。
  • 进程内的所有线程共享相同的地址空间。在许多情况下,线程间通信比进程间通信更有效、更易于使用。
  • 在许多其他方面,多线程应用程序与非多线程应用程序相比,具有潜在的性能优势和实际优势。
  • CPU 工作与 I/O 重叠。例如,一个程序可能有一些部分需要执行长时间的 I/O 操作。当一个线程正在等待 I/O 系统调用完成时,其他线程可以执行 CPU 密集型工作。
  • 优先级/实时调度任务可以被调度为超越或中断低优先级任务。
  • 异步事件处理任务可以交错处理不确定频率和持续时间的服务事件。例如,Web 服务器可以传输来自先前请求的数据并管理新请求的到达。
  • 多线程应用程序可以在单处理器系统上工作,并且自然地利用多处理器系统的优势,而无需重新编译。
  • 在多处理器环境中,使用 PThreads 最重要的原因是利用潜在的并行性。
  • 要使程序利用 PThreads,它必须被组织成可以并发执行的离散、独立的任务。

示例

PThreads 定义了一组 C 编程语言类型、函数和常量。它通过 PThread.h 头文件和线程库实现。

大约有 100 个线程过程,都以 PThread_ 作为前缀,它们可以分为以下四组:

  • 线程管理 - 创建、连接线程等。
  • 互斥锁
  • 条件变量
  • 使用读/写锁和屏障在线程之间同步

POSIX 信号量 API 可与 POSIX 线程配合使用,但不是线程标准的一部分,它已在 POSIX.1b 实时扩展(IEEE Std 1003.1b-1993)标准中定义。因此,信号量过程的前缀是 sem_ 而不是 PThread_。下面是一个演示在 C 中使用 PThreads 的示例。

上面的程序创建了五个线程,每个线程执行 perform_work 函数,该函数将该线程的唯一编号打印到标准输出。如果程序员希望线程相互通信,则需要定义一个在任何函数作用域之外的变量,将其声明为全局变量。可以使用 gcc 编译器通过以下命令编译此程序:

输出

这是运行该程序的一种可能输出。

IN MAIN: Creating thread 0.
IN MAIN: Creating thread 1.
IN MAIN: Creating thread 2.
IN MAIN: Creating thread 3.
THREAD 0: Started.
IN MAIN: Creating thread 4.
THREAD 3: Started.
THREAD 2: Started.
THREAD 0: Will be sleeping for 3 seconds.
THREAD 1: Started.
THREAD 1: Will be sleeping for 5 seconds.
THREAD 2: Will be sleeping for 4 seconds.
THREAD 4: Started.
THREAD 4: Will be sleeping for 1 second.
IN MAIN: All threads are created.
THREAD 3: Will be sleeping for 4 seconds.
THREAD 4: Ended.
THREAD 0: Ended.
IN MAIN: Thread 0 has ended.
THREAD 2: Ended.
THREAD 3: Ended.
THREAD 1: Ended.
IN MAIN: Thread 1 has ended.
IN MAIN: Thread 2 has ended.
IN MAIN: Thread 3 has ended.
IN MAIN: Thread 4 has ended.
MAIN program has ended.

Windows 版 POSIX 线程

Windows 不原生支持 PThreads 标准。因此 PThreads4w 项目旨在提供一个可移植的开源包装器实现。此外,它还可以用于将 UNIX 软件(使用 PThreads)以很少或无需修改的方式移植到 Windows 平台。

PThreads4w 版本 3.0.0 或更高版本,在 Apache Public License v2.0 下发布,与 64 位或 32 位 Windows 系统兼容。版本 2.11.0,在 LGPLv3 许可证下发布,也与 64 位或 32 位兼容。

Mingw-w64 项目还包含 PThreadswinPThreads 的包装器实现,后者比 PThreads4w 项目使用更多的原生系统调用。

Windows Services for UNIX/Subsystem for UNIX-based Applications 包中提供的 Interix 环境子系统提供了 PThreads API 的原生端口,即不映射到 Win32/Win64 API,而是直接构建在操作系统系统调用接口之上。

PThreads 中的扩展工具

以下是 PThreads 中可用的扩展工具列表,例如:/p>

  • Etnus Total View 支持线程调试。
  • Smart GDB 用于线程调试。
  • Sun 的 DevPro 编译器套件附带的调试器能够理解线程。
  • 使用 TNF 工具来跟踪、调试和收集应用程序和库的性能分析信息。TNF 工具集成了来自内核以及多个用户进程和线程的跟踪信息,对于多线程代码尤其有用。
  • LockLint 验证多线程 ANSI C 程序中互斥锁和读/写锁的一致使用。LockLint 对互斥锁和读/写锁执行静态分析,并查找这些锁定技术的 Butc 使用情况。通过查找 Butc 使用锁定,LockLint 可以检测到数据竞争和死锁的最常见原因。

基本 PThreads 库调用

以下是对 PThreads 库调用的简要介绍,例如:

  • PThread_create:它创建一个新线程,初始化其属性,并使其可运行。

PThread_create 子程序创建一个新线程,并使用 attr 参数指定的属性对象初始化其属性。新线程继承其创建线程的信号掩码,但创建线程的任何挂起信号都将为新线程清除。

新线程通过 arg 参数使其可运行,并将执行 start_routine 例程。arg 参数是一个 void 指针,用于引用任何数据。

PThread_create 子程序通过线程参数返回新的线程标识符。调用者可以使用此线程标识符对线程执行各种操作。应检查此标识符以确保线程已成功创建。

  • void PThread_exit:它终止调用线程。

PThread_exit 子程序安全地终止调用线程,并为可能加入调用线程的任何线程存储终止状态。

与 exit 子程序不同,PThread_exit 子程序不会关闭文件。因此,任何仅由调用线程打开和使用的文件都必须在调用此子程序之前关闭。从线程的初始例程返回将隐式调用 PThread_exit 子程序,并使用返回值作为参数。

  • PThread_t PThread_self():它返回调用线程的标识符。

PThread_self 子程序返回调用线程的标识符。

int PThread_join:PThread_join 子程序会阻塞调用线程,直到调用中指定的线程终止。目标线程的终止状态在 status 参数中返回。

如果目标线程已经终止但尚未分离,则子程序会立即返回。即使目标线程尚未终止,也无法连接(join)一个已分离的线程。在所有连接的线程被唤醒后,目标线程会自动分离。

此子程序本身不会导致线程终止。它就像 PThread_cond_wait 子程序一样,用于等待一个特殊条件。

  • int PThread_detach:它将指定的线程与调用线程分离。

PThread_detach 子程序指示实现,当线程终止时,线程标识符位于 thread 位置的线程的存储可以被回收。此存储将在进程退出时被回收,无论线程是否已被分离,并且可能包括线程返回值占用的存储。

如果线程尚未终止,PThread_detach 不会导致其终止。对同一目标线程多次调用 PThread_detach 会导致错误。

如果目标线程已经终止但尚未分离,则子程序会立即返回。即使目标线程尚未终止,也无法连接(join)一个已分离的线程。在所有连接的线程被唤醒后,目标线程会自动分离。

此子程序本身不会导致线程终止。它就像 PThread_cond_wait 子程序一样,用于等待一个特殊条件。

  • int PThread_mutex_init:它初始化一个互斥锁并设置其属性。

PThread_mutex_init 子程序根据 mutex 属性对象 attr 初始化一个新的互斥锁并设置其属性。互斥锁最初是解锁的。

初始化互斥锁后,可以重复使用该互斥锁属性对象来初始化另一个互斥锁或将其删除。

  • int PThread_mutex_destroy:它删除一个互斥锁。

PThread_mutex_destroy 子程序删除互斥锁。删除互斥锁后,mutex 参数无效,直到通过调用 PThread_mutex_init 子程序再次初始化它。

  • int PThread_mutex_lock:它锁定一个互斥锁。

通过调用 PThread_mutex_lock 来锁定由 mutex 引用的互斥锁对象。如果互斥锁已被锁定,则调用线程将阻塞,直到互斥锁可用。此操作以由 mutex 引用的互斥锁对象处于锁定状态且调用线程持有该对象的方式返回。

  • int PThread_mutex_trylock:它尝试锁定一个互斥锁。

PThread_mutex_trylock 函数与 PThread_mutex_lock 类似,但如果由 mutex 引用的互斥锁对象当前被锁定(由任何线程,包括当前线程),则调用将立即返回。

  • int PThread_mutex_unlock:它解锁一个互斥锁。

PThread_mutex_unlock 函数释放互斥锁对象。互斥锁的释放方式取决于互斥锁的类型属性。如果在调用 PThread_mutex_unlock 时有线程阻塞在互斥锁对象上,则互斥锁变得可用,并使用调度策略来确定哪个线程将获取该互斥锁。