操作系统 POSIX线程

2024年11月09日 操作系统 POSIX线程 极客笔记

操作系统 POSIX线程

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是Portable Operating System Interface的缩写,它是操作系统必须实现的一种接口类型。POSIX中的PThreads概述了操作系统必须提供的线程API。

在多处理器或多核系统上,PThreads的工作效果很好,因为进程流可以被调度到另一个处理器上执行,通过并行或分布式处理提高速度,因为系统不会为进程创建一个新的系统、虚拟内存空间和环境,所以线程相对于forking或创建一个新进程的开销较小。

PThread头文件

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

#include <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:

#include 
#include 
#include 
#include 
#include 

#define NUM_THREADS 5

void *perform_work(void *arguments)
{
  int index = *((int *)arguments);
  int sleep_time = 1 + rand() % NUM_THREADS;
  printf("THREAD %d: Started.\n", index);
  printf("THREAD %d: Will be sleeping for %d seconds.\n", index, sleep_time);
  sleep(sleep_time);
  printf("THREAD %d: Ended.\n", index);
  return NULL;
}

int main(void) 
{
  PThread_t threads[NUM_THREADS];
  int thread_args[NUM_THREADS];
  int i;
  int result_code;

  //create all threads one by one
  for (i = 0; i < NUM_THREADS; i++) 
{
    printf("IN MAIN: Creating thread %d.\n", i);
    thread_args[i] = i;
    result_code = PThread_create(&threads[i], NULL, perform_work, &thread_args[i]);
    assert(!result_code);
  }

  printf("IN MAIN: All threads are created.\n");

  //wait for each thread to complete
  for (i = 0; i < NUM_THREADS; i++) 
{
    result_code = PThread_join(threads[i], NULL);
    assert(!result_code);
    printf("IN MAIN: Thread %d has ended.\n", i);
  }

  printf("MAIN program has ended.\n");
  return 0;
}

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

gcc PThreads_demo.c -PThread -o PThreads_demo

输出

这是运行此程序时的众多可能输出之一。

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.

POSIX线程在Windows上的应用

Windows不原生支持PThreads标准。因此PThreads4w项目致力于提供一个可移植且开源的包装实现。同时,它可以在不或只做少量修改的情况下,将使用PThreads的UNIX软件移植到Windows平台。

PThreads4w 3.0.0版本及更高版本,使用Apache Public License v2.0发布,兼容64位或32位的Windows系统。LGPLv3许可下的2.11.0版本,同样兼容64位或32位。

Mingw-w64项目还包含一个对PThreads和winPThreads的包装实现,使用比PThreads4w项目更多的原生系统调用。

Windows的Unix Services for UNIX/Subsystem for UNIX-based Applications软件包中也提供了一个PThreads API的本地移植版本,即不通过Win32/Win64 API映射,而是直接建立在操作系统的系统调用接口之上。

PThreads的扩展工具

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

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

参数指定的参数,并且将指定的 start_routine 函数作为其初始函数。创建的线程将在调用 PThread_create 后立即运行。

成功创建线程后,将保存线程标识符到 thread 参数指向的位置,并返回0。如果发生错误,返回相应的错误代码。

  • PThread_join: 它等待指定线程的终止。
int PThread_join(PThread_t thread, void **value_ptr) ;

PThread_join子例程挂起调用线程,直到由thread参数指定的目标线程终止。如果目标线程已经终止,则子例程立即返回。可以显式地将目标线程分离以避免成为可连接的。

如果value_ptr参数不为NULL,则目标线程的退出状态将存储在其所指向的位置中。如果线程被取消,则退出状态将为PTHREAD_CANCELED。

  • PThread_exit:它终止调用线程。
void PThread_exit(void *value_ptr);

PThread_exit子例程终止调用线程,并将值value_ptr返回给调用PThread_join的任何其他线程。等待调用线程终止的任何线程都可以通过PThread_join子例程获取调用线程的退出状态。

一旦调用了PThread_exit子例程,调用线程将立即终止,并且不会返回给其调用者。

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

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

  • PThread_cancel:它请求取消指定的线程。
int PThread_cancel(PThread_t thread);

PThread_cancel子例程请求取消由thread参数指定的目标线程。如果线程仍可取消,则将异步取消它。子例程不会等待目标线程终止。

如果目标线程尚不可取消,则取消请求将延迟,直到目标线程进行取消点。

参数,并执行 start_routine 例程。参数 arg 是一个空指针,用于引用任何数据。

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

  • void PThread_exit: 终止调用线程。
void PThread_exit(void *value_ptr);

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

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

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

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

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

int PThread_join(PThread_t thread, void **value_ptr);

如果目标线程已经终止但尚未分离,该子程序将立即返回。即使目标线程尚未终止,也无法加入已分离的线程。在所有已加入的线程被唤醒后,目标线程会自动分离。

该子程序本身不会导致线程终止。它类似于PThread_cond_wait子程序,用于等待特殊条件。

  • int PThread_detach:它将指定的线程从调用线程中分离。
int PThread_detach(PThread_t thread, void **value_ptr);

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

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

如果目标线程已经终止但尚未分离,则子例程立即返回。即使未终止,也无法加入已分离的线程。在所有已加入的线程被唤醒后,目标线程会自动分离。它初始化一个互斥锁并设置其属性。

int PThread_mutex_init (PThread_mutex_t *mutex, PThread_mutexattr_t *attr);

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

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

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

被删除.

int PThread_mutex_destroy(PThread_mutex_t *mutex);

PThread_mutex_destroy 子程序删除互斥体。删除互斥体后,参数 mutex 无效,直到再次通过调用 PThread_mutex_init 子程序对其进行初始化为止。

  • int PThread_mutex_lock: 锁定互斥体。
int PThread_mutex_lock (PThread_mutex_t *mutex);

调用 PThread_mutex_lock 锁定由 mutex 引用的互斥体。如果互斥体已被锁定,则调用线程将被阻塞,直到互斥体可用为止。此操作返回通过 mutex 被调用的线程处于锁定状态。

  • int PThread_mutex_trylock: 它尝试锁定一个互斥锁。
int PThread_mutex_trylock(PThread_mutex_t *mutex); 

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

  • int PThread_mutex_unlock: 它解锁一个互斥锁。
int PThread_mutex_unlock(PThread_mutex_t *mutex); 

函数 PThread_mutex_unlock 释放互斥锁对象。互斥锁如何释放取决于互斥锁的类型属性。如果在调用 PThread_mutex_unlock 时有线程阻塞在互斥锁对象上,这个互斥锁被称为,然后互斥锁变为可用状态,并且调度策略用于确定哪个线程将获得该互斥锁。

本文链接:http://so.lmcjl.com/news/17365/

展开阅读全文