C语言中的POSIX线程与多线程编程:从入门到实践
C语言中的POSIX线程与多线程编程:从入门到实践
在现代软件开发中,多线程编程已成为提升程序性能和响应速度的关键技术之一。C语言作为一种经典的编程语言,提供了多种方式来实现多线程编程,其中 POSIX 线程库(pthreads)是最常用的实现方式之一。本文将深入探讨 C 语言中的 POSIX 线程及多线程编程,帮助初学者理解其基本概念、常用函数和同步机制。 (【C 言专栏】C 语言中的多线程编程 - 阿里云开发者社区)
一、什么是 POSIX 线程?
POSIX 线程(POSIX Threads),简称 pthreads,是 POSIX 标准定义的一套线程接口。它为多线程编程提供了统一的 API,使得程序能够在类 Unix 系统(如 Linux、macOS 等)上创建、管理和同步线程。在 Windows 系统上,也可以通过第三方库(如 pthreads-w32)实现 pthreads 接口。 (线程, Pthreads)
二、POSIX 线程的基本组成
POSIX 线程库主要由以下几个部分组成:
- 线程管理:创建、终止、等待线程等。
- 同步原语:互斥锁(mutex)、条件变量(condition variable)、信号量(semaphore)等。
- 线程属性:设置线程的栈大小、调度策略、分离状态等。
- 线程局部存储:为每个线程分配独立的数据。
三、常用头文件与函数
1. 头文件
在使用 POSIX 线程时,需要包含以下头文件:
#include <pthread.h> // POSIX 线程库
#include <semaphore.h> // POSIX 信号量库
#include <unistd.h> // 提供 sleep 等系统调用
2. 线程管理函数
创建线程
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
- thread: 输出型参数,用于接收新创建线程的标识符。通过该指针可操作或等待目标线程。
- attr: 可选参数,指向
pthread_attr_t
结构体的指针,用于设置线程属性(如栈大小、调度策略等)。若为NULL则使用默认属性。 - start_routine: 线程入口函数指针,新线程启动后将执行该函数。函数需符合
void *func(void *)
的签名规范。 - arg: 传递给线程入口函数的参数指针,允许通过该参数向新线程传递上下文数据。
等待线程结束
pthread_join(pthread_t thread, void **retval)
- thread: 输入参数,指定需等待的目标线程标识符。调用线程将阻塞直至该线程终止。
- retval: 输出型参数,用于接收目标线程的退出状态。若线程通过
pthread_exit
返回数据,可通过该指针获取。
退出线程
pthread_exit(void *retval)
- retval: 线程退出时的返回值指针。该值可通过
pthread_join
由其他线程获取,用于传递线程执行结果或状态。
获取当前线程 ID
pthread_self(void)
- 无参数:直接返回调用线程的标识符(
pthread_t
类型),用于在无明确线程句柄时获取自身标识。
3. 互斥锁函数
初始化互斥锁
pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr)
- mutex: 输出型参数,指向需初始化的互斥锁对象。
- attr: 可选参数,指向互斥锁属性对象的指针,用于配置锁类型(如普通锁、递归锁等)。若为NULL则使用默认属性。
加锁
pthread_mutex_lock(pthread_mutex_t *mutex)
- mutex: 输入参数,指向已初始化的互斥锁。调用线程将阻塞直至成功获取锁所有权,用于保护临界区资源。
解锁
pthread_mutex_unlock(pthread_mutex_t *mutex)
- mutex: 输入参数,指向需释放的互斥锁。仅持有锁的线程可执行此操作,释放后其他线程可竞争获取锁。
销毁互斥锁
pthread_mutex_destroy(pthread_mutex_t *mutex)
- mutex: 输入参数,指向需销毁的互斥锁对象。销毁前需确保锁未被任何线程持有。
4. 条件变量函数
初始化条件变量
pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr)
- cond: 输出型参数,指向需初始化的条件变量对象。
- attr: 可选参数,指向条件变量属性对象的指针,用于配置进程共享等属性。若为NULL则使用默认属性。
等待条件变量
pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)
- cond: 输入参数,指向条件变量。调用线程将阻塞直至收到该条件的信号。
- mutex: 输入参数,关联的互斥锁。此函数会自动释放锁并进入等待,被唤醒后重新获取锁,确保原子性操作。
发送信号
pthread_cond_signal(pthread_cond_t *cond)
- cond: 输入参数,指向需发送信号的条件变量。唤醒至少一个等待该条件的线程。
销毁条件变量
pthread_cond_destroy(pthread_cond_t *cond)
- cond: 输入参数,指向需销毁的条件变量对象。销毁前需确保无线程正在等待该条件。
5. 信号量函数
初始化信号量
sem_init(sem_t *sem, int pshared, unsigned int value)
- sem: 输出型参数,指向需初始化的信号量对象。
- pshared: 共享标志,0表示线程间共享,非0值表示进程间共享(需系统支持)。
- value: 信号量初始计数值,表示可用资源数量。
等待信号量
sem_wait(sem_t *sem)
- sem: 输入参数,指向信号量。若信号量计数值>0则减1并返回;若=0则阻塞直至计数值>0。
释放信号量
sem_post(sem_t *sem)
- sem: 输入参数,指向信号量。将信号量计数值加1,并唤醒等待该信号量的线程(若有)。
销毁信号量
sem_destroy(sem_t *sem)
- sem: 输入参数,指向需销毁的信号量对象。销毁前需确保无线程正在操作该信号量。
四、线程同步与互斥
在多线程编程中,多个线程可能会同时访问共享资源,导致数据竞争和不一致。为了解决这个问题,POSIX 线程库提供了多种同步机制:
1. 互斥锁(mutex)
互斥锁用于保护共享资源,确保同一时刻只有一个线程可以访问。
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&mutex);
// 访问共享资源
pthread_mutex_unlock(&mutex);
2. 条件变量(condition variable)
条件变量用于线程间的通信,允许线程在某些条件满足时被唤醒。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;pthread_mutex_lock(&mutex);
while (/* 条件不满足 */) {pthread_cond_wait(&cond, &mutex);
}
// 访问共享资源
pthread_mutex_unlock(&mutex);
3. 信号量(semaphore)
信号量是一种同步原语,用于控制多个线程对共享资源的访问。
sem_t sem;
sem_init(&sem, 0, 1);sem_wait(&sem);
// 访问共享资源
sem_post(&sem);sem_destroy(&sem);
五、线程属性与管理
1. 线程属性(pthread_attr_t)
线程属性用于设置线程的栈大小、调度策略、分离状态等。
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
// 设置其他属性
pthread_create(&thread, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);
2. 线程分离与连接
- 分离线程:
pthread_detach(pthread_t thread);
- 连接线程:
pthread_join(pthread_t thread, void **retval);
分离线程后,线程结束时会自动释放资源;连接线程用于等待线程结束并获取返回值。
六、完整示例:创建并同步两个线程
以下是一个简单的示例,演示如何创建两个线程并使用互斥锁进行同步:
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> // 引入 intptr_t 类型pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void *thread_func(void *arg) {intptr_t thread_id = (intptr_t)arg; // 使用 intptr_t 类型pthread_mutex_lock(&mutex);printf("线程 %ld 正在执行...\n", (long)thread_id);pthread_mutex_unlock(&mutex);return NULL;
}int main() {pthread_t threads[2];for (intptr_t i = 0; i < 2; i++) {if (pthread_create(&threads[i], NULL, thread_func, (void *)i) != 0) {perror("创建线程失败");exit(EXIT_FAILURE);}}for (int i = 0; i < 2; i++) {if (pthread_join(threads[i], NULL) != 0) {perror("等待线程失败");exit(EXIT_FAILURE);}}pthread_mutex_destroy(&mutex);return 0;
}
程序分析
在代码中,主线程创建了两个子线程,并等待它们完成。每个子线程在执行时会获取互斥锁(pthread_mutex_lock
),打印一条消息,然后释放互斥锁(pthread_mutex_unlock
)。由于互斥锁的存在,确保了在同一时刻只有一个线程能够执行打印操作,从而避免了输出的混乱。
然而,线程的执行顺序是由操作系统的调度器决定的,通常是基于时间片轮转或其他调度策略。因此,两个线程的执行顺序可能会有所不同,导致输出的顺序也不同。
可能的输出示例
-
线程 0 先执行:
线程 0 正在执行... 线程 1 正在执行...
-
线程 1 先执行:
线程 1 正在执行... 线程 0 正在执行...
在这个示例中,主线程创建了两个子线程,每个子线程在执行时会获取互斥锁,确保同一时刻只有一个线程在执行临界区代码。
七、总结
POSIX 线程库为 C 语言程序提供了强大的多线程支持,使得程序能够在类 Unix 系统上实现并发和并行处理。通过合理使用线程管理函数和同步机制,可以有效地提高程序的性能和响应速度。然而,多线程编程也带来了线程同步、死锁等挑战,需要开发者在设计和实现时特别注意。 (【C 言专栏】C 语言中的多线程编程 - 阿里云开发者社区, 基本功| 一文讲清多线程和多线程同步原创 - CSDN博客)
希望本文能够帮助初学者理解 POSIX 线程的基本概念和常用函数,为深入学习多线程编程奠定基础。