活动介绍

线程编程:Pthreads深入解析

立即解锁
发布时间: 2025-08-26 00:03:44 阅读量: 7 订阅数: 27
PDF

并行编程:多核与集群系统的实践指南

### 线程编程:Pthreads 深入解析 在并行计算领域,多线程编程是实现高效计算的重要手段。许多并行计算平台,尤其是多核平台,提供了共享地址空间,基于线程的编程模型自然成为了这些架构的首选。在这个模型中,所有线程都可以访问共享变量。本文将深入探讨 Pthreads 编程,包括线程的创建、管理、同步等方面的内容。 #### 1. Pthreads 概述 Pthreads 即 POSIX 线程模型,它基于 C 语言定义了线程编程的标准。在一个进程中,所有线程共享一个公共的地址空间,这意味着全局变量和动态生成的数据对象可以被该进程的所有线程访问。不过,每个线程都有自己独立的运行时栈,用于控制激活的函数和存储局部变量,这些局部变量只能由执行线程直接访问。 由于线程的运行时栈在线程终止后会被删除,因此将线程 A 运行时栈中的局部变量引用传递给线程 B 是很危险的。 Pthreads 的数据类型、接口定义和宏通常通过头文件 `<pthread.h>` 提供,所以在 Pthreads 程序中必须包含这个头文件。Pthreads 的函数和数据类型遵循一定的命名约定: - 函数命名形式为 `pthread[ <object>] <operation> ()`,其中 `<operation>` 描述要执行的操作,可选的 `<object>` 描述该操作应用的对象。例如,`pthread_mutex_init()` 用于初始化互斥变量,这里 `<object>` 是 `mutex`,`<operation>` 是 `init`。 - 对于涉及线程操作的函数,会省略 `<object>` 的指定,如 `pthread_create()` 用于创建线程。 - 所有 Pthreads 函数执行成功时返回值为 0,失败时会返回 `<error.h>` 中的错误代码,因此程序中也应包含这个头文件。 - Pthreads 数据类型与 MPI 类似,描述的是不透明对象,其具体实现对程序员隐藏。数据类型命名形式为 `pthread_<object>_t`,例如 `pthread_mutex_t` 表示互斥变量,若省略 `<object>`,则为 `pthread_t` 表示线程。 以下是一些重要的 Pthreads 数据类型及其含义: | Pthreads 数据类型 | 含义 | | --- | --- | | `pthread_t` | 线程 ID | | `pthread_mutex_t` | 互斥变量 | | `pthread_cond_t` | 条件变量 | | `pthread_key_t` | 访问键 | | `pthread_attr_t` | 线程属性对象 | | `pthread_mutexattr_t` | 互斥属性对象 | | `pthread_condattr_t` | 条件变量属性对象 | | `pthread_once_t` | 一次性初始化控制上下文 | 线程的执行采用两步调度方法。程序员需要将程序划分为合适数量的用户线程,这些用户线程由库调度器映射到系统线程,然后由操作系统的调度器在计算系统的处理器上执行。程序员无法控制操作系统的调度器,对库调度器的影响也很小,因此不能直接将用户级线程映射到计算系统的处理器上。不过,大多数情况下,库和操作系统提供的调度能取得不错的效果,减轻了程序员的编程负担。 #### 2. 创建和合并线程 当一个 Pthreads 程序启动时,会有一个主线程执行 `main()` 函数。主线程可以通过调用 `pthread_create()` 函数创建更多线程: ```c int pthread_create (pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg) ``` - 第一个参数是指向 `pthread_t` 类型对象的指针,也就是线程标识符(TID),由 `pthread_create()` 生成,后续可用于其他 Pthreads 函数识别该线程。 - 第二个参数是指向 `pthread_attr_t` 类型的属性对象的指针,用于定义生成线程的期望属性。若为 `NULL`,则生成具有默认属性的线程。若需要不同的属性值,需要在调用 `pthread_create()` 之前创建并初始化属性数据结构。 - 第三个参数指定生成线程要执行的函数 `start_routine()`,该函数应接受一个 `void *` 类型的参数,并返回相同类型的值。 - 第四个参数是指向传递给线程函数 `start_routine()` 的参数值的指针。 如果线程函数需要多个参数,必须将所有参数放入一个数据结构中,然后将该数据结构的地址作为参数传递给线程函数。如果父线程使用相同的线程函数启动多个线程,但参数值不同,应为每个线程使用单独的数据结构来指定参数,以避免参数值被过早覆盖或被多个子线程并发修改的问题。 线程可以通过调用 `pthread_self()` 函数确定自己的线程标识符: ```c pthread_t pthread_self() ``` 要比较两个线程的线程 ID,可以使用 `pthread_equal()` 函数: ```c int pthread_equal (pthread_t t1, pthread_t t2) ``` 该函数在 `t1` 和 `t2` 不指向同一个线程时返回 0,否则返回非零值。由于 `pthread_t` 是不透明的数据结构,只能使用 `pthread_equal()` 来比较线程 ID。 一个进程能够创建的线程数量通常受系统限制。Pthreads 标准规定每个进程至少可以创建 64 个线程,但具体系统的限制可能更高。在大多数系统中,可以通过调用 `sysconf (SC_THREAD_THREADS_MAX)` 来确定最大可启动的线程数: ```c maxThreads = sysconf (SC_THREAD_THREADS_MAX) ``` 知道这个限制后,程序可以避免启动超过最大数量的线程。如果达到限制,调用 `pthread_create()` 函数将返回错误值 `EAGAIN`。 线程可以通过以下两种方式终止: - 线程函数执行完毕,例如调用 `return`。 - 线程显式调用 `pthread_exit()` 函数: ```c void pthread_exit (void *valuep) ``` 参数 `valuep` 指定要返回给等待该线程终止的其他线程的值。当线程函数终止时,会隐式调用 `pthread_exit()`,并将线程函数的返回值作为参数。需要注意的是,线程的返回值不应是指向线程函数或其调用的其他函数的局部变量的指针,因为这些局部变量存储在运行时栈上,线程终止后可能不再存在,其内存空间可能会被其他线程重用。建议使用全局变量或动态分配的变量。 一个线程可以通过调用 `pthread_join()` 函数等待另一个线程终止: ```c int pthread_join (pthread_t thread, void **valuep) ``` - 参数 `thread` 指定要等待终止的线程的 TID。 - 参数 `valuep` 指定存储该线程返回值的内存地址。 调用 `pthread_join()` 的线程会被阻塞,直到指定的线程终止。如果多个线程使用 `pthread_join()` 等待同一个线程终止,所有等待线程都会被阻塞,直到该线程终止,但只有一个等待线程能成功存储返回值,其他等待线程调用 `pthread_join()` 的返回值为错误值 `ESRCH`。 Pthreads 库的运行时系统为每个线程分配一个内部数据结构,用于存储控制线程执行所需的信息和数据。在调用 `pthread_join()` 后,终止线程的内部数据结构会被释放,无法再访问。如果没有对某个线程调用 `pthread_join()`,该线程终止后其内部数据结构不会被释放,会一直占用内存空间,直到整个进程终止。为避免这种情况,可以调用 `pthread_detach()` 函数: ```c int pthread_detach (pthread_t thread) ``` 该函数通知运行时系统,具有 TID `thread` 的线程终止后,其内部数据结构可以立即分离。线程可以自行分离,也可以由其他线程分离。线程进入分离状态后,调用 `pthread_join()` 会返回错误值 `EINVAL`。 下面是一个 Pthreads 程序的示例,用于两个矩阵的乘法: ```c #include <pthread.h> typedef struct { int size, row, column; double (*MA)[8], (*MB)[8], (*MC)[8]; } matrix_type_t; void *thread_mult (void *w) { matrix_type_t *work = (matrix_type_t *) w; int i, row = work->row, column = work->column; work -> MC[row][column] = 0; for (i=0; i < work->size; i++) work->MC[row][column] += work->MA[row][i] * work->MB[i][column]; return NULL; } int main() { int row, column, size = 8, i; double MA[8][8], MB[8][8], MC[8][8]; matrix_type_t *work; pthread_t thread[8*8]; for (row=0; row<size; row++) for (column=0; column<size; column++) { work = (matrix_type_t *) malloc (sizeof (matrix_type_t)); work->size = size; work->row = row; work->column = column; work->MA = MA; work->MB = MB; work->MC = MC; pthread_create (&(thread[column + row*8]), NULL, thread_mult, (void *) work); } for (i=0; i<size*size; i++) pthread_join (thread[i], NULL); } ``` 在这个示例中,为结果矩阵 `MC` 的每个元素创建一个单独的线程。每个线程执行 `thread_mult()` 函数,计算输入矩阵 `MA` 的一行和 `MB` 的一列的标量积。主线程创建所有线程后,使用 `pthread_join()` 等待它们终止。不过,这个程序的可扩展性较差,因为为输出矩阵的每个元素创建一个线程,即使对于中等大小的矩阵,也可能达到系统允许的最大线程数。对于更大的矩阵,应该使用固定数量的线程,每个线程计算输出矩阵的一个块。 #### 3. Pthreads 线程协调 由于进程中的线程共享公共地址空间,它们可以并发访问共享变量。为避免竞态条件,需要对这些并发访问进行协调。Pthreads 提供了互斥变量和条件变量来实现这种协调。 ##### 3.1 互斥变量 在 Pthreads 中,互斥变量是预定义的不透明类型 `pthread_mutex_t` 的数据结构,用于确保对公共数据的互斥访问,即同一时间只有一个线程可以独占访问公共数据结构,其他线程必须等待。 互斥变量有两种状态:锁定和解锁。为确保对公共数据结构的互斥访问,需要为该数据结构分配一个单独的互斥变量。所有访问线程必须遵循以下行为: - 在访问公共数据结构之前,调用特定的 Pthreads 函数锁定相应的互斥变量。成功锁定后,该线程成为互斥变量的所有者。 - 每次访问公共数据结构后,解锁相应的互斥变量。解锁后,该线程不再是互斥变量的所有者,其他线程可以成为所有者并访问数据结构。 如果线程 A 试图锁定已经被线程 B 拥有的互斥变量,线程 A 会被阻塞,直到线程 B 解锁该互斥变量。Pthreads 运行时系统确保同一时间只有一个线程是特定互斥变量的所有者。但如果线程在访问数据结构之前没有锁定互斥变量,就无法保证互斥访问。 互斥变量与数据结构的分配是由程序员通过使用特定互斥变量的锁定和解锁操作来隐式完成的,没有显式的分配。为了提高 Pthreads 程序的可读性,程序员可以将公共数据结构和保护它的互斥变量组合成一个新的结构。 互斥变量可以静态声明或动态生成,但在使用之前必须初始化: - 对于静态分配的互斥变量 `mutex`,可以使用预定义的宏 `PTHREAD_MUTEX_INITIALIZER` 进行初始化: ```c mutex = PTHREAD_MUTEX_INITIALIZER ``` - 对于任意互斥变量(静态分配或动态生成),可以通过调用 `pthread_mutex_init()
corwn 最低0.47元/天 解锁专栏
赠100次下载
继续阅读 点击查看下一篇
profit 400次 会员资源下载次数
profit 300万+ 优质博客文章
profit 1000万+ 优质下载资源
profit 1000万+ 优质文库回答
复制全文

相关推荐

郑天昊

首席网络架构师
拥有超过15年的工作经验。曾就职于某大厂,主导AWS云服务的网络架构设计和优化工作,后在一家创业公司担任首席网络架构师,负责构建公司的整体网络架构和技术规划。
最低0.47元/天 解锁专栏
赠100次下载
百万级 高质量VIP文章无限畅学
千万级 优质资源任意下载
千万级 优质文库回答免费看
立即解锁

专栏目录

最新推荐

具有特色的论证代理与基于假设的论证推理

### 具有特色的论证代理与基于假设的论证推理 在当今的人工智能领域,论证代理和论证推理是两个重要的研究方向。论证代理可以在各种场景中模拟人类进行辩论和协商,而论证推理则为解决复杂的逻辑问题提供了有效的方法。下面将详细介绍论证代理的相关内容以及基于假设的论证推理。 #### 论证代理的选择与回复机制 在一个模拟的交易场景中,卖家提出无法还钱,但可以用另一个二手钢制消声器进行交换。此时,调解人询问买家是否接受该提议,买家有不同类型的论证代理给出不同回复: - **M - agent**:希望取消合同并归还消声器。 - **S - agent**:要求卖家还钱并道歉。 - **A - agen

知识工作者认知增强的负责任以人为本人工智能

### 知识工作者认知增强的负责任以人为本人工智能 #### 1. 引言 从制造业经济向服务经济的转变,使得对高绩效知识工作者(KWs)的需求以前所未有的速度增长。支持知识工作者的生产力工具数字化,带来了基于云的人工智能(AI)服务、远程办公和职场分析等。然而,在将这些技术与个人效能和幸福感相协调方面仍存在差距。 随着知识工作者就业机会的增加,量化和评估知识工作的需求将日益成为常态。结合人工智能和生物传感技术的发展,为知识工作者提供生物信号分析的机会将大量涌现。认知增强旨在提高人类获取知识、理解世界的能力,提升个人绩效。 知识工作者在追求高生产力的同时,面临着平衡认知和情感健康压力的重大

基于神经模糊的多标准风险评估方法研究

### 基于神经模糊的多标准风险评估方法研究 #### 风险评估基础 在风险评估中,概率和严重程度的分级是重要的基础。概率分级如下表所示: | 概率(概率值) | 出现可能性的分级步骤 | | --- | --- | | 非常低(1) | 几乎从不 | | 低(2) | 非常罕见(一年一次),仅在异常条件下 | | 中等(3) | 罕见(一年几次) | | 高(4) | 经常(一个月一次) | | 非常高(5) | 非常频繁(一周一次,每天),在正常工作条件下 | 严重程度分级如下表: | 严重程度(严重程度值) | 分级 | | --- | --- | | 非常轻微(1) | 无工作时间

基于进化算法和梯度下降的自由漂浮空间机器人逆运动学求解器

### 基于进化算法和梯度下降的自由漂浮空间机器人逆运动学求解器 #### 1. 自由漂浮空间机器人(FFSR)运动方程 自由漂浮空间机器人(FFSR)由一个基座卫星和 $n$ 个机械臂连杆组成,共 $n + 1$ 个刚体,通过 $n$ 个旋转关节连接相邻刚体。下面我们来详细介绍其运动方程。 ##### 1.1 位置形式的运动方程 - **末端执行器(EE)姿态与配置的关系**:姿态变换矩阵 $^I\mathbf{R}_e$ 是配置 $q$ 的函数,$^I\mathbf{R}_e$ 和 $\mathbf{\Psi}_e$ 是 EE 方位的两种不同表示,所以 $\mathbf{\Psi}_

城市货运分析:新兴技术与集成平台的未来趋势

### 城市货运分析:新兴技术与集成平台的未来趋势 在城市货运领域,为了实现减排、降低成本并满足服务交付要求,软件系统在确定枢纽或转运设施的使用以及选择新的运输方式(如电动汽车)方面起着关键作用。接下来,我们将深入探讨城市货运领域的新兴技术以及集成平台的相关内容。 #### 新兴技术 ##### 联网和自动驾驶车辆 自动驾驶车辆有望提升安全性和效率。例如,驾驶辅助和自动刹车系统在转弯场景中能避免碰撞,其警报系统会基于传感器获取的车辆轨迹考虑驾驶员反应时间,当预测到潜在碰撞时自动刹车。由于驾驶员失误和盲区问题,还需采用技术提醒驾驶员注意卡车附近的行人和自行车骑行者。 自动驾驶车辆为最后一公

物联网与人工智能在医疗及网络安全中的应用

### 物联网与人工智能在医疗及网络安全中的应用 #### 物联网数据特性与机器学习算法 物联网(IoT)数据具有多样性、大量性和高速性等特点。从数据质量上看,它可能来自动态源,能处理冗余数据和不同粒度的数据,且基于数据使用情况,通常是完整且无噪声的。 在智能数据分析方面,许多学习算法都可应用。学习算法主要以一组样本作为输入,这组样本被称为训练数据集。学习算法可分为监督学习、无监督学习和强化学习。 - **监督学习算法**:为了预测未知数据,会从有标签的输入数据中学习表示。支持向量机(SVM)、随机森林(RF)和回归就是监督学习算法的例子。 - **SVM**:因其计算的实用性和

多媒体应用的理论与教学层面解析

# 多媒体应用的理论与教学层面解析 ## 1. 多媒体资源应用现状 在当今的教育体系中,多媒体资源的应用虽已逐渐普及,但仍面临诸多挑战。相关评估程序不完善,导致其在不同教育系统中的应用程度较低。以英国为例,对多媒体素养测试的重视程度极低,仅有部分“最佳证据”引用在一些功能性素养环境中认可多媒体评估的价值,如“核心素养技能”概念。 有观点认为,多媒体素养需要更清晰的界定,同时要建立一套成果体系来评估学生所达到的能力。尽管大部分大学教师认可多媒体素养的重要性,但他们却难以明确阐述其具体含义,也无法判断学生是否具备多媒体素养能力。 ## 2. 教学设计原则 ### 2.1 教学设计的重要考量

认知计算与语言翻译应用开发

# 认知计算与语言翻译应用开发 ## 1. 语言翻译服务概述 当我们获取到服务凭证和 URL 端点后,语言翻译服务就可以为各种支持语言之间的文本翻译请求提供服务。下面我们将详细介绍如何使用 Java 开发一个语言翻译应用。 ## 2. 使用 Java 开发语言翻译应用 ### 2.1 创建 Maven 项目并添加依赖 首先,创建一个 Maven 项目,并添加以下依赖以包含 Watson 库: ```xml <dependency> <groupId>com.ibm.watson.developer_cloud</groupId> <artifactId>java-sdk</

医学影像处理与油藏过滤问题研究

### 医学影像处理与油藏过滤问题研究 #### 医学影像处理部分 在医学影像处理领域,对比度受限的自适应直方图均衡化(CLAHE)是一种重要的图像增强技术。 ##### 累积分布函数(CDF)的确定 累积分布函数(CDF)可按如下方式确定: \[f_{cdx}(i) = \sum_{j = 0}^{i} p_x(j)\] 通常将期望的常量像素值(常设为 255)与 \(f_{cdx}(i)\) 相乘,从而创建一个将 CDF 映射为均衡化 CDF 的新函数。 ##### CLAHE 增强过程 CLAHE 增强过程包含两个阶段:双线性插值技术和应用对比度限制的直方图均衡化。给定一幅图像 \

地下油运动计算与短信隐写术研究

### 地下油运动计算与短信隐写术研究 #### 地下油运动计算 在地下油运动的研究中,压力降会有所降低。这是因为油在井中的流动速度会加快,并且在井的附近气体能够快速填充。基于此,能够从二维视角计算油在多孔空间中的运动问题,在特定情况下还可以使用并行数值算法。 使用并行计算算法解决地下油运动问题,有助于节省获取解决方案和进行计算实验的时间。不过,所创建的计算算法仅适用于具有边界条件的特殊情况。为了提高解决方案的准确性,建议采用其他类型的组合方法。此外,基于该算法可以对地下油的二维运动进行质量计算。 |相关情况|详情| | ---- | ---- | |压力降变化|压力降会降低,原因是油井