Linux编程:9、线程编程-互斥锁与条件变量
一、互斥量(互斥锁)
1. 概述
- 功能:在功能上类似初值为 1 的信号量,通过加锁和解锁操作,实现对临界区的互斥访问。
- 适用场景:多线程需要共享资源时,防止多个线程同时操作共享资源导致的数据不一致问题。
2. 相关接口
接口 | 头文件 | 功能 | 参数 | 返回值 |
---|---|---|---|---|
pthread_mutex_init | #include <pthread.h> | 以动态方式初始化互斥锁(初始化后为未锁定状态) | 参数 1:指向待初始化的互斥量;参数 2:互斥锁属性(为 NULL 时使用默认属性) | 成功返回 0;失败返回非零值 |
pthread_mutex_lock | #include <pthread.h> | 对指定互斥锁加锁(若已加锁,当前线程阻塞) | 待加锁的互斥锁 | 成功返回 0;失败返回错误代码 |
pthread_mutex_unlock | #include <pthread.h> | 对指定互斥锁解锁 | 待解锁的互斥锁 | 成功返回 0;失败返回错误代码 |
pthread_mutex_destroy | #include <pthread.h> | 销毁指定互斥锁 | 待销毁的互斥锁 | 成功返回 0;失败返回错误代码 |
- 初始化方式:
- 动态初始化:使用
pthread_mutex_init
函数。 - 静态初始化:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
。
- 动态初始化:使用
3. 实例:多线程购票问题
- 问题:多线程并发购票时,可能出现重复购票等数据不一致问题。
- 解决方案:使用互斥锁确保同一时间只有一个线程操作票数变量。
- 关键代码逻辑:
- 定义全局互斥锁
pthread_mutex_t mutex;
。 - 主线程中初始化互斥锁:
pthread_mutex_init(&mutex, NULL);
。 - 购票线程中,操作票数前加锁
pthread_mutex_lock(&mutex);
,操作完成后解锁pthread_mutex_unlock(&mutex);
。 - 线程结束后销毁互斥锁:
pthread_mutex_destroy(&mutex);
。
- 定义全局互斥锁
- 未加锁的代码:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h>int ticketCount = 100;struct info {char name[64];int count; };void* buyThread(void* arg) {struct info* info = (struct info*)arg;printf("%s 买票线程启动, 准备买 %d 张票\n", info->name, info->count);for (int i = 0; i < info->count; i++) {printf("当前剩余: %d 张票,%s 要买一张票\n", ticketCount, info->name);sleep(1);ticketCount--;sleep(1);}// 线程结束,返回指定的值pthread_exit((void*)"购票成功!"); }int main() {// 创建一个新线程(子线程)pthread_t thread1;struct info man1 = {"小明", 3};int ret = pthread_create(&thread1, NULL, buyThread, (void*)&man1);if (ret) { // ret != 0perror("线程创建失败");exit(EXIT_FAILURE);}pthread_t thread2;struct info man2 = {"小红", 3};ret = pthread_create(&thread2, NULL, buyThread, (void*)&man2);if (ret) { // ret != 0perror("线程创建失败");exit(EXIT_FAILURE);}printf("等待买票线程结束...\n");// 等待指定的线程结束void* thread_result;ret = pthread_join(thread1, &thread_result);if (ret) {perror("Thread join failed");exit(1);}printf("线程[%p] 结束, 返回值是: %s\n", thread1, (char*)thread_result);ret = pthread_join(thread2, &thread_result);if (ret) {perror("Thread join failed");exit(1);}printf("线程[%p] 结束, 返回值是: %s\n", thread2, (char*)thread_result);printf("\n 现在总票数是: %d\n", ticketCount);return 0; }
- 加锁的代码:
#include <cstdlib> #include <pthread.h> #include <cstring> #include <cstdio> #include <unistd.h> #include <ctime>int ticketCount = 100;// 定义一个互斥锁 pthread_mutex_t mutex;struct info {char name[64];int count; };void* buyThread(void* arg) {struct info* info = (struct info*)arg;printf("%s 买票线程启动, 准备买 %d 张票\n", info->name, info->count);for (int i = 0; i < info->count; i++) {int seconds = 1 + rand() % 3;sleep(seconds);// 进入临界区要加锁pthread_mutex_lock(&mutex);printf("当前剩余: %d 张票,%s 要买一张票\n", ticketCount, info->name);ticketCount--;// 出了临界区解锁pthread_mutex_unlock(&mutex);}// 线程结束,返回指定的值pthread_exit((void*)"购票成功!"); }int main() {srand(time(0));// 初始化锁int ret = pthread_mutex_init(&mutex, NULL);if(ret != 0) {perror("mutex init failed");exit(1);}// 创建一个新线程(子线程)pthread_t thread1;struct info man1 = {"小明", 3};ret = pthread_create(&thread1, NULL, buyThread, (void*)&man1);if (ret) { // ret != 0perror("线程创建失败");exit(EXIT_FAILURE);}pthread_t thread2;struct info man2 = {"小红", 3};ret = pthread_create(&thread2, NULL, buyThread, (void*)&man2);if (ret) { // ret != 0perror("线程创建失败");exit(EXIT_FAILURE);}printf("等待买票线程结束...\n");// 等待指定的线程结束void* thread_result;ret = pthread_join(thread1, &thread_result);if (ret) {perror("Thread join failed");exit(1);}printf("线程[%p] 结束, 返回值是: %s\n", thread1, (char*)thread_result);ret = pthread_join(thread2, &thread_result);if (ret) {perror("Thread join failed");exit(1);}printf("线程[%p] 结束, 返回值是: %s\n", thread2, (char*)thread_result);printf("\n 现在总票数是: %d\n", ticketCount);// 用完之后销毁互斥锁pthread_mutex_destroy(&mutex);return 0; }
二、条件变量
1. 概述
- 适用场景:存在多个生产者和消费者,资源个数不确定;消费者 / 生产者线程需等待特定条件才能进行操作。
- 核心:线程通过等待或发送信号,基于特定条件实现同步。
2. 相关接口
接口 | 头文件 | 功能 | 参数 | 返回值 |
---|---|---|---|---|
pthread_cond_init | #include <pthread.h> | 初始化条件变量 | 参数 1:待初始化的条件变量;参数 2:条件变量属性(为 NULL 时使用默认属性) | 成功返回 0;失败返回错误码 |
pthread_cond_wait | #include <pthread.h> | 使线程等待条件变量(需配合互斥锁使用) | 参数 1:指向条件变量的指针;参数 2:指向互斥锁的指针(调用前需由当前线程锁定) | 成功返回 0;失败返回错误码 |
pthread_cond_signal | #include <pthread.h> | 唤醒等待特定条件变量的一个线程 | 指向要发送信号的条件变量 | 成功返回 0;失败返回错误码 |
pthread_cond_broadcast | #include <pthread.h> | 唤醒所有等待指定条件变量的线程 | 指向要广播信号的条件变量 | 成功返回 0;失败返回错误码 |
pthread_cond_destroy | #include <pthread.h> | 销毁指定条件变量 | 待销毁的条件变量 | - |
初始化方式:
- 动态初始化:使用
pthread_cond_init
函数。 - 静态初始化:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
。
- 动态初始化:使用
pthread_cond_wait
执行过程:- 解锁当前持有的互斥锁,允许其他线程获取锁操作共享资源。
- 将当前线程加入与条件变量关联的等待队列,进入阻塞状态。
- 等待被
pthread_cond_signal
或pthread_cond_broadcast
唤醒。 - 被唤醒后,重新获取之前释放的互斥锁(若锁被其他线程持有,当前线程会阻塞等待)。
注意:
pthread_cond_wait
返回后需重新检查条件(通常用while
循环),因条件可能已变化。
3. 实例:生产者 - 消费者模型
- 场景:多个生产者线程生产数据,多个消费者线程消费数据,共享资源为
shared_data
。 - 关键代码逻辑:
- 定义全局条件变量
pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER;
和互斥锁pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
。 - 生产者线程:锁定互斥锁→生产数据→调用
pthread_cond_signal
唤醒消费者→解锁互斥锁。 - 消费者线程:锁定互斥锁→
while
循环检查条件(shared_data == 0
时调用pthread_cond_wait
等待)→消费数据→解锁互斥锁。 - 线程结束后销毁条件变量和互斥锁:
pthread_cond_destroy(&cond_var);
、pthread_mutex_destroy(&mutex);
。
- 定义全局条件变量
- 示例代码:
#include <pthread.h> #include <cstdio> #include <cstdlib> #include <cstring> #include <unistd.h> #include <ctime>#define PRODUCER_THREAD_NUM 5 #define CONSUMER_THREAD_NUM 5pthread_cond_t cond_var = PTHREAD_COND_INITIALIZER; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 共享数据 int shared_data = 0;void* producer_func(void* args) {char* producerName = static_cast<char*>(args);while(1) {sleep(rand() % 3 + 1); // 模拟生产耗时pthread_mutex_lock(&mutex);shared_data++;printf("%s 生产了一个产品,剩余产品数量: %d\n", producerName, shared_data);pthread_cond_signal(&cond_var); // 唤醒一个等待的消费者线程pthread_mutex_unlock(&mutex);}pthread_exit(NULL); }void* consumer_func(void* args) {char* consumerName = static_cast<char*>(args);while(1) {sleep(rand() % 3 + 1); // 模拟消费耗时pthread_mutex_lock(&mutex);while(shared_data == 0) {// 如果资源等于 0,等待成品生产pthread_cond_wait(&cond_var, &mutex);}shared_data--;printf("%s 消耗了一个产品,剩余产品数量: %d\n", consumerName,shared_data);pthread_mutex_unlock(&mutex);}pthread_exit(NULL); }int main() {srand(time(NULL));pthread_t producers[PRODUCER_THREAD_NUM];pthread_t consumers[CONSUMER_THREAD_NUM];for(int i = 0; i < PRODUCER_THREAD_NUM; i++) {char* buff = new char[256];sprintf(buff, "第 %d 号生产者线程", i + 1);int ret = pthread_create(&producers[i], NULL,producer_func, buff);if(ret != 0) {perror("pthread create failed");exit(1);}}for (int i = 0; i < CONSUMER_THREAD_NUM; i++) {char* buff = new char[256];sprintf(buff, "第 %d 号消费者线程", i + 1);int ret = pthread_create(&consumers[i], NULL,consumer_func, buff);if (ret != 0) {perror("pthread create failed");exit(1);}}for (int i = 0; i < PRODUCER_THREAD_NUM; i++) {pthread_join(producers[i], NULL);}for (int i = 0; i < PRODUCER_THREAD_NUM; i++){pthread_join(consumers[i], NULL);}pthread_cond_destroy(&cond_var);pthread_mutex_destroy(&mutex);return 0; }
三、线程的属性
1. 概述
- 线程属性用于设置线程的特性(如脱离状态、调度策略等),通过
pthread_attr_t
类型变量管理。
2. 基本接口
pthread_attr_init
:初始化线程属性,参数为待初始化的线程属性,成功返回 0,失败返回错误码。pthread_attr_destroy
:销毁线程属性(销毁后不影响已创建的线程,可重新初始化),参数为待销毁的线程属性,成功返回 0,失败返回错误码。
3. 主要属性
脱离状态属性(
pthread_attr_setdetachstate
):- 功能:设置线程是否可被
pthread_join
等待。 - 参数 2 可选值:
PTHREAD_CREATE_DETACHED
:线程为脱离状态,原线程不能用pthread_join
等待其结束。PTHREAD_CREATE_JOINABLE
(默认):原线程可用pthread_join
等待其结束。
- 功能:设置线程是否可被
调度策略属性(
pthread_attr_setschedpolicy
):- 功能:设置线程的调度策略。
- 参数 2 可选值:
SCHED_OTHER
:默认策略,由操作系统根据负载和优先级自动调度。SCHED_FIFO
:先进先出策略(需超级用户权限),同优先级线程按顺序执行,高优先级线程可抢占低优先级线程。SCHED_RR
:轮转调度策略,同优先级线程分时运行(获得时间片),高优先级线程可抢占低优先级线程。
优先级设置:通过
pthread_attr_setschedparam
设置,需结合调度策略,可通过sched_get_priority_max
和sched_get_priority_min
获取优先级范围。
4. 实例:设置线程属性
- 关键代码逻辑:
- 定义线程属性变量
pthread_attr_t thread_attr;
。 - 初始化属性:
pthread_attr_init(&thread_attr);
。 - 设置脱离状态:
pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
。 - 设置调度策略:
pthread_attr_setschedpolicy(&thread_attr, SCHED_OTHER);
。 - 设置优先级:通过
sched_param
结构体配置,调用pthread_attr_setschedparam
。 - 创建线程:
pthread_create(&a_thread, &thread_attr, thread_function, (void*)n);
。 - 销毁属性:
pthread_attr_destroy(&thread_attr);
。
- 定义线程属性变量
- 示例代码:
#include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <stdint.h>int finished = 0;void* thread_function(void* arg) {printf("线程开始运行...");int count = static_cast<int>(reinterpret_cast<intptr_t>(arg));for (int i = 0; i < count; i++) {printf("线程正在工作,第%d 次\n", i + 1);sleep(1);}printf("线程即将关闭.\n");finished = 1;pthread_exit(NULL); }int main() {// 定义一个线程属性pthread_attr_t thread_attr;// 初始化线程属性int res = pthread_attr_init(&thread_attr);if (res != 0) {perror("pthread_attr_init failed");exit(1);}// 设置线程的脱离状态属性res = pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);if (res != 0) {perror("Setting detached attribute failed");exit(EXIT_FAILURE);}// 设置调度策略// 1. SCHED_OTHER// 默认的调度策略// 由操作系统自动选择线程运行顺序。// 操作系统会根据系统的负载和线程的优先级自动调度线程的执行// 2. SCHED_FIFO// 使用先进先出策略,需要以超级用户的权限启动进程// 同优先级的实时进程,后进入的进程要等前一个进程释放了 CPU,才能够运行// 不同优先级的实时进程,高优先级的实时进程能够抢占低优先级的实时进程// 3. SCHED_RR// 轮转调度策略(Round-Robin)// 同优先级的实时进程中,每个进程又是通过获得时间片来分时运行// 不同优先级的实时进程,高优先级的实时进程能够抢占低优先级的实时进程res = pthread_attr_setschedpolicy(&thread_attr, SCHED_OTHER);if (res != 0) {perror("pthread_attr_setschedpolicy failed.");exit(1);}// 获取优先级范围int max_priority = sched_get_priority_max(SCHED_OTHER);int min_priority = sched_get_priority_min(SCHED_OTHER);// 设置优先级struct sched_param scheduling_value;scheduling_value.sched_priority = min_priority;res = pthread_attr_setschedparam(&thread_attr, &scheduling_value);if (res != 0) {perror("pthread_attr_setschedparam failed.");exit(1);}// 创建线程pthread_t a_thread;int n = 3;res = pthread_create(&a_thread, &thread_attr,thread_function, reinterpret_cast<void*>(static_cast<intptr_t>(n)));if (res != 0) {perror("Thread creation failed");exit(EXIT_FAILURE);}// 线程属性不再需要时,可以使用 pthread_attr_destroy 进行销毁// 线程属性销毁后,对使用这个属性创建的线程没有影响// 线程属性销毁后,可以重新进行初始化pthread_attr_destroy(&thread_attr);// 已经不能使用 pthread_join 来等待线程结束了// 这里使用共享变量来判断while (!finished) {printf("等待线程结束...\n");sleep(1);}printf("线程已结束\n");return 0; }
四、线程的取消
1. 概述
- 场景:线程 A 需让线程 B 停止运行时,向线程 B 发送取消请求,线程 B 根据自身 “取消状态” 和 “取消类型” 决定是否及如何取消执行。
2. 相关接口
接口 | 头文件 | 功能 | 参数 | 返回值 |
---|---|---|---|---|
pthread_setcancelstate | #include <pthread.h> | 设置线程的取消状态 | 参数 1:PTHREAD_CANCEL_ENABLE (允许接收取消请求)或PTHREAD_CANCEL_DISABLE (禁止接收);参数 2:获取之前的取消状态(可为 NULL) | 成功返回 0;失败返回错误码 |
pthread_setcanceltype | #include <pthread.h> | 设置线程的取消类型(允许取消时生效) | 参数 1:PTHREAD_CANCEL_DEFERRED (收到请求后,执行 “取消点” 函数时取消)或PTHREAD_CANCEL_ASYNCHRONOUS (收到请求后立即取消);参数 2:获取之前的取消类型 | 成功返回 0;失败返回错误码 |
pthread_cancel | #include <pthread.h> | 向指定线程发送取消请求 | 目标线程的标识符 | 成功返回 0;失败返回错误码 |