解码线程同步与互斥
线程同步与互斥概述
线程是进程内的执行单元,同一进程的所有线程共享进程资源。线程并发执行时会出现资源争抢问题,导致共享数据不一致,需通过同步和互斥机制解决。
- 同步:控制线程执行顺序,让线程按预定次序执行。
- 互斥:禁止线程同时访问临界资源,确保同一时间只有一个线程操作资源。

互斥锁(Mutex)
定义
互斥锁是实现互斥的核心机制,本质是二值信号量(值为 0 或 1),用于保护临界资源,避免多线程同时访问。

核心函数
初始化互斥锁
#include <pthread.h>
// 动态初始化:通过函数初始化
/*
* @brief 初始化互斥锁对象
* @param mutex:指向互斥锁对象的指针(需提前定义pthread_mutex_t类型变量)
* @param attr:互斥锁属性,NULL表示使用默认属性(推荐)
* @return 成功返回0,失败返回非0错误码
* @note 未初始化的互斥锁不能使用;不可重复初始化已初始化的互斥锁
*/
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
// 静态初始化:直接赋值宏定义
/*
* @brief 静态初始化互斥锁,效果等同于动态初始化(attr=NULL)
* @note 无需调用pthread_mutex_init,直接定义变量并赋值即可
* 无错误检查,适用于全局或静态变量
*/
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
上锁与解锁
// 上锁:阻塞等待,直到获取锁
/*
* @brief 申请获取互斥锁,保护临界资源
* @param mutex:已初始化的互斥锁对象指针
* @return 成功返回0,失败返回非0错误码
* @note 若锁已被其他线程持有,当前线程阻塞;同一线程不可重复上锁(会导致死锁)
*/
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 尝试上锁:非阻塞,获取失败立即返回
/*
* @brief 尝试获取互斥锁,不阻塞
* @param mutex:已初始化的互斥锁对象指针
* @return 成功返回0,失败返回EAGAIN(锁已被持有)
* @note 适用于不希望线程长时间阻塞的场景
*/
int pthread_mutex_trylock(pthread_mutex_t *mutex);
/**
* 带超时机制的互斥锁加锁函数(POSIX标准接口,用于避免线程永久阻塞)
* @brief 尝试获取互斥锁所有权,若锁已被其他线程持有,
* 则阻塞等待至指定绝对时间;超时仍未获取则返回错误,
* 是预防死锁的核心接口之一
* @param mutex 指向pthread_mutex_t类型互斥锁对象的指针,
* 需提前通过pthread_mutex_init或PTHREAD_MUTEX_INITIALIZER初始化;
* 传入未初始化/已销毁的互斥锁会导致未定义行为
* @param abstime 指向struct timespec类型的绝对时间结构体指针,
* 指定最晚获取锁的时间点;
* 结构体成员tv_sec为秒(整数部分),
* tv_nsec为纳秒(0~999,999,999,小数部分),
* 需基于当前系统时间计算(非相对时间)
* @return int 返回值含义:
* - 0:成功获取互斥锁,当前线程成为锁的唯一持有者
* - ETIMEDOUT:超时错误,在abstime指定时间前未获取到锁,线程解除阻塞
* - EINVAL:参数无效,可能是mutex未初始化、
* abstime的tv_nsec超出合法范围(<0或>999,999,999)
* - EDEADLK:死锁风险,当前线程已持有该互斥锁(重复加锁),
* 或根据锁属性检测到循环等待
* - EBUSY:锁已被其他线程持有但未超时(部分实现返回,
* 优先级低于ETIMEDOUT处理)
* @note 时间类型注意:abstime是绝对时间(如“2024-10-01 12:34:56.123456789”),
* 需通过clock_gettime(CLOCK_REALTIME, &abstime)获取当前时间后,
* 叠加超时时长(如abstime.tv_sec += 3表示3秒后超时),
* 不可直接设为相对时长(如tv_sec=3)
* 时钟选择:推荐使用CLOCK_REALTIME(与系统时间同步),
* 若需避免系统时间调整影响,可使用CLOCK_MONOTONIC(单调递增时钟,
* 从系统启动计时),需确保系统支持该时钟类型
* 编译链接:使用该函数需链接pthread库,
* 编译命令需加“-pthread”选项(如gcc demo.c -o demo -pthread),
* 否则会报“未定义引用”错误
* 资源清理:若线程等待锁前已持有其他资源(如另一个锁、动态内存),
* 超时后需主动释放,避免资源泄漏
* 锁属性兼容:支持默认锁、递归锁、错误检查锁,
* 但递归锁场景下重复调用会返回EDEADLK(非超时),
* 需规避重复加锁逻辑
*/
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
// 解锁:释放锁
/*
* @brief 释放已获取的互斥锁,允许其他线程竞争
* @param mutex:已上锁的互斥锁对象指针
* @return 成功返回0,失败返回非0错误码
* @note 只能释放当前线程持有的锁;解锁未上锁的锁会导致未定义行为
*/
int pthread_mutex_unlock(pthread_mutex_t *mutex);
销毁互斥锁
/*
* @brief 销毁互斥锁,释放相关资源
* @param mutex:已初始化的互斥锁对象指针
* @return 成功返回0,失败返回非0错误码
* @note 只能销毁未上锁的互斥锁;销毁后可重新初始化使用
*/
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥锁实现线程互斥访问示例
#include <pthread.h>
#include <stdio.h>
// 全局临界资源(主线程和子线程共享)
int global_val = 0;
// 静态初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 子线程任务函数
void *child_task(void *arg) {
while (1) {
// 访问临界资源前上锁
pthread_mutex_lock(&mutex);
/* 临界区:操作共享资源 */
global_val = 10;
printf("子线程:global_val = %d\n", global_val);
/* 临界区结束 */
// 操作完成后解锁
pthread_mutex_unlock(&mutex);
sleep(1); // 模拟其他操作
}
return NULL;
}
int main() {
pthread_t tid;
// 创建子线程
pthread_create(&tid, NULL, child_task, NULL);
while (1) {
// 访问临界资源前上锁
pthread_mutex_lock(&mutex);
/* 临界区:操作共享资源 */
global_val = 20;
printf("主线程:global_val = %d\n", global_val);
/* 临界区结束 */
// 操作完成后解锁
pthread_mutex_unlock(&mutex);
sleep(1); // 模拟其他操作
}
// 销毁互斥锁(实际不会执行,因主线程和子线程均为死循环)
pthread_mutex_destroy(&mutex);
return 0;
}
超时机制上锁示例
#include <pthread.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
// 全局互斥锁:保护共享资源,静态初始化
pthread_mutex_t g_shared_mutex = PTHREAD_MUTEX_INITIALIZER;
// 共享资源:模拟需要互斥访问的数据
int g_shared_data = 0;
/**
* @brief 子线程函数:模拟长时间持有互斥锁,占用共享资源
* @param arg 线程参数(本示例未使用,设为NULL)
* @return 线程退出返回值(NULL)
*/
void* hold_lock_thread(void* arg) {
printf("[子线程] 尝试获取互斥锁...\n");
// 普通加锁(无超时,持有锁5秒)
int ret = pthread_mutex_lock(&g_shared_mutex);
if (ret != 0) {
perror("[子线程] pthread_mutex_lock 失败");
pthread_exit(NULL);
}
printf("[子线程] 成功获取互斥锁,开始占用共享资源\n");
// 模拟耗时操作:持有锁5秒,期间修改共享资源
g_shared_data = 100; // 修改共享资源
printf("[子线程] 当前共享资源值:%d,将持有锁5秒\n", g_shared_data);
sleep(5); // 关键:持有锁5秒,制造主线程超时场景
// 释放锁
pthread_mutex_unlock(&g_shared_mutex);
printf("[子线程] 释放互斥锁,退出子线程\n");
pthread_exit(NULL);
}
/**
* @brief 主线程函数:使用pthread_mutex_timedlock尝试超时获取互斥锁
* @return 程序退出码(0为正常)
*/
int main() {
pthread_t hold_tid;
int ret;
struct timespec g_timeout; // 超时时间结构体(绝对时间)
// 创建子线程:子线程会先获取锁并持有5秒
ret = pthread_create(&hold_tid, NULL, hold_lock_thread, NULL);
if (ret != 0) {
perror("pthread_create 失败");
exit(EXIT_FAILURE);
}
// 延迟1秒:确保子线程先获取到锁,制造主线程等待场景
sleep(1);
// 设置超时时间:当前系统时间 + 3秒(绝对时间)
// CLOCK_REALTIME:使用系统实时时间(与date命令同步)
ret = clock_gettime(CLOCK_REALTIME, &g_timeout);
if (ret == -1) {
perror("clock_gettime 失败");
// 清理:取消子线程并销毁锁
pthread_cancel(hold_tid);
pthread_mutex_destroy(&g_shared_mutex);
exit(EXIT_FAILURE);
}
g_timeout.tv_sec += 3; // 秒数+3:超时时间设为3秒后
g_timeout.tv_nsec = 0; // 纳秒部分设为0(简化示例,也可设为500000000表示3.5秒)
// 调用pthread_mutex_timedlock:超时获取互斥锁
printf("\n[主线程] 开始尝试获取互斥锁,超时时间:3秒\n");
ret = pthread_mutex_timedlock(&g_shared_mutex, &g_timeout);
// 根据返回值处理不同场景
switch (ret) {
case 0:
// 成功获取锁:操作共享资源
printf("[主线程] 成功获取互斥锁!\n");
g_shared_data += 50; // 修改共享资源
printf("[主线程] 共享资源更新后的值:%d\n", g_shared_data);
// 释放锁(必须释放,避免其他线程阻塞)
pthread_mutex_unlock(&g_shared_mutex);
printf("[主线程] 释放互斥锁\n");
break;
case ETIMEDOUT:
// 超时场景:3秒内未获取到锁,避免永久阻塞
printf("[主线程] 获取互斥锁超时(已等待3秒),放弃等待\n");
// 可在此处添加降级逻辑(如重试、提示用户等)
break;
case EINVAL:
// 参数无效:如锁未初始化、超时时间格式错误
printf("[主线程] 参数无效:互斥锁未初始化或超时时间格式错误\n");
break;
case EDEADLK:
// 死锁风险:当前线程已持有该锁(重复加锁)或循环等待
printf("[主线程] 检测到死锁风险:可能重复加锁或循环等待\n");
break;
default:
// 其他错误
printf("[主线程] 获取互斥锁失败,错误码:%d,错误信息:%s\n", ret, strerror(ret));
break;
}
// 资源清理:等待子线程退出,销毁互斥锁
printf("\n[主线程] 等待子线程退出...\n");
ret = pthread_join(hold_tid, NULL);
if (ret != 0) {
perror("pthread_join 失败");
}
// 销毁互斥锁(必须在所有线程释放锁后调用)
ret = pthread_mutex_destroy(&g_shared_mutex);
if (ret != 0) {
perror("pthread_mutex_destroy 失败");
exit(EXIT_FAILURE);
}
printf("[主线程] 资源清理完成,程序正常退出\n");
return 0;
}
colck_gettime()
#include <time.h>
/**
* 高精度系统时间获取函数(POSIX标准接口,支持纳秒级精度)
* @brief 用于获取指定类型系统时钟的当前时间,提供秒+纳秒级高精度时间数据,
* 核心用于设置绝对超时时间(如pthread_mutex_timedlock)、计算时间间隔等场景
* @param clk_id 时钟类型ID,指定要获取的系统时钟类型,常用取值及说明:
* - CLOCK_REALTIME:系统实时时间(墙上时间),与date命令同步,
* 可被手动修改(如date -s),适用于需与系统时间对齐的场景;
* - CLOCK_MONOTONIC:单调递增时钟,从系统启动时开始计时,
* 不受系统时间修改影响(不会回退),适用于时间间隔计算;
* - CLOCK_PROCESS_CPUTIME_ID:当前进程占用CPU的累计时间,
* 仅统计进程实际运行在CPU上的时间(不含阻塞时间);
* - CLOCK_THREAD_CPUTIME_ID:当前线程占用CPU的累计时间,
* 仅统计线程实际运行在CPU上的时间(不含阻塞时间);
* - CLOCK_REALTIME_COARSE:低精度实时时间,效率高于CLOCK_REALTIME,
* 适用于对精度要求不高但需快速获取时间的场景
* @param tp 指向struct timespec类型的指针,用于存储获取到的高精度时间(输出参数),
* 结构体定义及成员说明:
* struct timespec {
* time_t tv_sec; // 时间的秒数部分(整数,如1696123456秒)
* long tv_nsec; // 时间的纳秒部分(小数,范围:0 ~ 999,999,999)
* };
* 示例:时间1696123456.789012345秒 → tv_sec=1696123456,tv_nsec=789012345
* @return int 返回值含义:
* - 0:成功获取时间,tp指向的结构体已填充有效数据;
* - -1:获取失败,错误原因存储在errno中,常见错误码:
* EINVAL:clk_id指定的时钟类型不支持;
* EFAULT:tp指针为NULL或指向非法内存地址
* @note tv_nsec范围限制:必须在0~999,999,999之间,不可超过1秒(10^9纳秒),
* 处理时需避免溢出(如计算后tv_nsec≥1e9,需转换为tv_sec+1);
* 时钟类型选择:计算时间间隔优先用CLOCK_MONOTONIC(避免系统时间修改影响),
* 需与外部时间对齐用CLOCK_REALTIME;
* 编译兼容性:大部分Linux系统默认支持,部分嵌入式系统可能需链接实时库(编译加-lrt);
* tp参数要求:必须提前分配内存(栈/堆均可),不可为NULL,否则返回EFAULT错误;
* 精度说明:实际精度取决于系统硬件和内核,不一定能达到理论上的纳秒级(通常为微秒级)
*/
int clock_gettime(clockid_t clk_id, struct timespec *tp);
注意事项
- 上锁后必须解锁,避免其他线程永久阻塞。
- 临界区代码应尽量简短,减少锁持有时间。
- 禁止同一线程对同一互斥锁重复上锁(死锁风险)。
读写锁(RWLock)
定义
读写锁是优化版互斥锁,区分读操作和写操作:读操作可并发执行(共享锁),写操作互斥执行(排他锁),适用于 “读多写少” 场景,提升程序效率。
核心函数
初始化读写锁
#include <pthread.h>
// 动态初始化
/*
* @brief 初始化读写锁对象
* @param rwlock:指向读写锁对象的指针
* @param attr:读写锁属性,NULL表示默认属性
* @return 成功返回0,失败返回非0错误码
* @note 未初始化的读写锁不可使用;不可重复初始化
*/
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
// 静态初始化
/*
* @brief 静态初始化读写锁,效果等同于动态初始化(attr=NULL)
* @note 适用于全局或静态变量,无错误检查
*/
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
加锁与解锁
// 读锁:共享锁,多个线程可同时获取
/*
* @brief 申请读锁,用于读取共享资源
* @param rwlock:已初始化的读写锁对象指针
* @return 成功返回0,失败返回非0错误码
* @note 若已有写锁持有,当前线程阻塞;已有读锁时,新线程可直接获取读锁
*/
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
// 写锁:排他锁,同一时间只能有一个线程获取
/*
* @brief 申请写锁,用于修改共享资源
* @param rwlock:已初始化的读写锁对象指针
* @return 成功返回0,失败返回非0错误码
* @note 若已有读锁或写锁持有,当前线程阻塞;写锁优先级通常高于读锁
*/
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
// 尝试获取读锁:非阻塞
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
// 尝试获取写锁:非阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//带超时机制的读锁加锁函数(POSIX标准接口,用于避免线程永久阻塞)
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,
const struct timespec *restrict abstime);
//带超时机制的写锁加锁函数(POSIX标准接口,用于避免线程永久阻塞)
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
const struct timespec *restrict abstime);
// 解锁:释放读锁或写锁
/*
* @brief 释放已获取的读锁或写锁
* @param rwlock:已加锁的读写锁对象指针
* @return 成功返回0,失败返回非0错误码
* @note 读锁解锁:若释放后无其他读锁,锁变为未锁定状态;
* 写锁解锁:直接释放锁,允许其他线程竞争
*/
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
销毁读写锁
/*
* @brief 销毁读写锁,释放相关资源
* @param rwlock:已初始化的读写锁对象指针
* @return 成功返回0,失败返回非0错误码
* @note 只能销毁未上锁的读写锁;销毁后可重新初始化
*/
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
读写锁实现读写分离示例
#include <pthread.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
// 共享资源:存储时间的文本内容
char time_buf[64] = {0};
// 静态初始化读写锁
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
// 子线程B:读取系统时间(hh:mm:ss)
void *task_B(void *arg) {
while (1) {
// 获取读锁
pthread_rwlock_rdlock(&rwlock);
/* 读临界区:读取时间并提取时分秒 */
char hhmmss[16] = {0};
strncpy(hhmmss, time_buf, 8); // 假设time_buf格式为"hh:mm:ss yyyy-mm-dd"
printf("线程B(时间):%s\n", hhmmss);
/* 读临界区结束 */
// 释放读锁
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
// 子线程C:读取系统日期(yyyy-mm-dd)
void *task_C(void *arg) {
while (1) {
// 获取读锁
pthread_rwlock_rdlock(&rwlock);
/* 读临界区:读取时间并提取日期 */
char yyyymmdd[16] = {0};
strncpy(yyyymmdd, time_buf + 9, 10);
printf("线程C(日期):%s\n", yyyymmdd);
/* 读临界区结束 */
// 释放读锁
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
return NULL;
}
// 主线程:每隔5秒写入系统时间到共享缓冲区
int main() {
pthread_t tid_B, tid_C;
// 初始化读写锁(静态初始化可省略此步骤)
// pthread_rwlock_init(&rwlock, NULL);
// 创建子线程
pthread_create(&tid_B, NULL, task_B, NULL);
pthread_create(&tid_C, NULL, task_C, NULL);
while (1) {
sleep(5); // 每隔5秒写入一次
// 获取写锁
pthread_rwlock_wrlock(&rwlock);
/* 写临界区:获取系统时间并写入缓冲区 */
time_t now = time(NULL);
struct tm *tm_now = localtime(&now);
strftime(time_buf, sizeof(time_buf), "%H:%M:%S %Y-%m-%d", tm_now);
printf("主线程(写入):%s\n", time_buf);
/* 写临界区结束 */
// 释放写锁
pthread_rwlock_unlock(&rwlock);
}
// 销毁读写锁(实际不会执行)
pthread_rwlock_destroy(&rwlock);
return 0;
}
实现写优先策略示例
函数解析
/**
* 初始化读写锁属性对象(POSIX标准接口)
* @brief 为读写锁属性分配资源并初始化默认值,是设置读写锁特性(如写优先)的前提
* @param attr 指向pthread_rwlockattr_t类型属性对象的指针,需提前定义(栈/全局变量均可)
* @return int 返回值含义:
* - 0:初始化成功,属性对象处于可用状态;
* - 非0:初始化失败(如系统资源不足),错误码通过返回值返回
* @note 必须在设置属性(如写优先)和初始化读写锁前调用;
* 未初始化的属性对象不可用于后续接口(如pthread_rwlockattr_setkind_np);
* 初始化后的属性对象默认值:读优先(PTHREAD_RWLOCK_DEFAULT_NP)、进程内共享;
* 用完后需调用pthread_rwlockattr_destroy销毁,避免资源泄漏
*/
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
/**
* 设置读写锁的调度策略(GNU扩展接口,Linux专属)
* @brief 核心接口:指定读写锁的优先级策略(读优先/写优先),实现写优先的关键
* @param attr 指向已初始化的pthread_rwlockattr_t属性对象的指针
* @param pref 优先级策略取值,仅支持3种(GNU扩展定义):
* - PTHREAD_RWLOCK_PREFER_READER_NP:读优先(默认),新读锁可插队写锁等待队列,可能导致写饥饿;
* - PTHREAD_RWLOCK_PREFER_WRITER_NP:写优先,写锁申请后阻塞新读锁,确保写线程优先执行;
* - PTHREAD_RWLOCK_DEFAULT_NP:等同于读优先,兼容早期版本
* @return int 返回值含义:
* - 0:属性设置成功,策略已生效;
* - EINVAL:参数无效(如attr未初始化、pref取值非法);
* - 其他非0:系统不支持该策略(极少出现)
* @note 兼容性:仅Linux系统支持(GNU C库扩展,NP=Non-Portable),其他系统(macOS/FreeBSD)需用对应平台接口;
* 调用时机:必须在pthread_rwlock_init(初始化读写锁)之前,否则属性对已创建的锁无效;
* 写优先特性:写锁等待时,新读锁无法插队,仅允许当前持有读锁的线程释放后,写锁获取;
* 不可重复设置:多次调用以最后一次设置的策略为准
*/
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
/**
* 销毁读写锁属性对象(POSIX标准接口)
* @brief 释放属性对象占用的系统资源,属性对象使用完毕后的清理操作
* @param attr 指向已初始化的pthread_rwlockattr_t属性对象的指针
* @return int 返回值含义:
* - 0:销毁成功,属性对象不可再使用;
* - EINVAL:参数无效(如attr未初始化、已销毁)
* @note 1. 调用时机:读写锁初始化(pthread_rwlock_init)完成后即可调用,属性对象仅用于锁初始化;
* 2. 不可重复销毁:对已销毁的属性对象再次调用会返回错误;
* 3. 资源释放:销毁后属性对象占用的内存等资源被释放,避免内存泄漏
*/
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
实现
/**
* 读写锁属性设置:实现写优先策略(GNU扩展,Linux系统适用)
* @brief 通过设置读写锁属性,让写锁申请时优先于后续读锁获取,避免写线程饥饿
* 核心原理:当有写锁等待时,新的读锁申请会被阻塞,直到所有等待的写锁完成操作
*
* 关键属性接口:pthread_rwlockattr_setkind_np(NP=Non-Portable,非POSIX标准,GNU扩展)
* 属性取值说明:
* - PTHREAD_RWLOCK_PREFER_READER_NP:默认读优先(新读锁可插队写锁等待队列)
* - PTHREAD_RWLOCK_PREFER_WRITER_NP:写优先(写锁等待时,阻塞新读锁申请)
* - PTHREAD_RWLOCK_DEFAULT_NP:等同于读优先
*/
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
// 全局读写锁(将通过写优先属性初始化)
pthread_rwlock_t g_rwlock;
// 共享资源:模拟读多写少的数据源
int g_shared_data = 0;
// 控制线程退出的标志
int g_exit_flag = 0;
/**
* @brief 读线程函数:循环获取读锁,读取共享资源
* @param arg 线程ID(用于打印区分)
* @return NULL
*/
void* reader_thread(void* arg) {
int tid = *(int*)arg;
free(arg); // 释放传入的动态分配ID
while (!g_exit_flag) {
// 申请读锁(写优先模式下,若有写锁等待,此处会阻塞)
int ret = pthread_rwlock_rdlock(&g_rwlock);
if (ret != 0) {
perror("pthread_rwlock_rdlock failed");
break;
}
// 读临界区:读取共享资源(模拟耗时读操作)
printf("[读线程%d] 成功获取读锁,共享数据:%d\n", tid, g_shared_data);
usleep(500000); // 模拟读操作耗时0.5秒
// 释放读锁
pthread_rwlock_unlock(&g_rwlock);
usleep(100000); // 读线程间隔0.1秒再次申请读锁(制造高并发读场景)
}
printf("[读线程%d] 退出\n", tid);
pthread_exit(NULL);
}
/**
* @brief 写线程函数:延迟后申请写锁,修改共享资源
* @param arg 线程ID(用于打印区分)
* @return NULL
*/
void* writer_thread(void* arg) {
int tid = *(int*)arg;
free(arg); // 释放传入的动态分配ID
// 延迟2秒启动:让读线程先占据读锁,模拟写锁等待场景
sleep(2);
printf("\n[写线程%d] 开始申请写锁...\n", tid);
// 申请写锁(写优先模式下,会阻塞直到当前读锁释放,且后续读锁无法插队)
int ret = pthread_rwlock_wrlock(&g_rwlock);
if (ret != 0) {
perror("pthread_rwlock_wrlock failed");
pthread_exit(NULL);
}
// 写临界区:修改共享资源(模拟耗时写操作)
g_shared_data += 10;
printf("[写线程%d] 成功获取写锁,共享数据更新为:%d\n", tid, g_shared_data);
usleep(1000000); // 模拟写操作耗时1秒
// 释放写锁
pthread_rwlock_unlock(&g_rwlock);
printf("[写线程%d] 释放写锁\n\n", tid);
// 写操作完成后,设置退出标志,让读线程退出
g_exit_flag = 1;
pthread_exit(NULL);
}
int main() {
pthread_t readers[3]; // 3个读线程(模拟高并发读)
pthread_t writer; // 1个写线程(模拟写请求)
pthread_rwlockattr_t rwlock_attr; // 读写锁属性对象
int ret, i;
// 初始化读写锁属性
ret = pthread_rwlockattr_init(&rwlock_attr);
if (ret != 0) {
perror("pthread_rwlockattr_init failed");
exit(EXIT_FAILURE);
}
// 设置写优先属性(核心步骤)
// 注意:PTHREAD_RWLOCK_PREFER_WRITER_NP是GNU扩展,仅Linux系统支持
ret = pthread_rwlockattr_setkind_np(&rwlock_attr, PTHREAD_RWLOCK_PREFER_WRITER_NP);
if (ret != 0) {
perror("pthread_rwlockattr_setkind_np failed");
pthread_rwlockattr_destroy(&rwlock_attr);
exit(EXIT_FAILURE);
}
// 用写优先属性初始化读写锁
ret = pthread_rwlock_init(&g_rwlock, &rwlock_attr);
if (ret != 0) {
perror("pthread_rwlock_init failed");
pthread_rwlockattr_destroy(&rwlock_attr);
exit(EXIT_FAILURE);
}
// 销毁属性对象(初始化读写锁后,属性对象可释放)
pthread_rwlockattr_destroy(&rwlock_attr);
// 创建3个读线程
for (i = 0; i < 3; i++) {
int* tid = malloc(sizeof(int));
*tid = i + 1;
ret = pthread_create(&readers[i], NULL, reader_thread, tid);
if (ret != 0) {
perror("pthread_create reader failed");
g_exit_flag = 1;
exit(EXIT_FAILURE);
}
}
// 创建1个写线程
int* writer_tid = malloc(sizeof(int));
*writer_tid = 1;
ret = pthread_create(&writer, NULL, writer_thread, writer_tid);
if (ret != 0) {
perror("pthread_create writer failed");
g_exit_flag = 1;
exit(EXIT_FAILURE);
}
// 等待所有线程退出
for (i = 0; i < 3; i++) {
pthread_join(readers[i], NULL);
}
pthread_join(writer, NULL);
// 销毁读写锁
pthread_rwlock_destroy(&g_rwlock);
printf("主线程:所有线程退出,资源清理完成\n");
return 0;
}
注意事项
- 读锁共享:多个线程可同时持有读锁,互不阻塞。
- 写锁排他:写锁与读锁、写锁与写锁均互斥。
- 适用场景:读操作频率远高于写操作(如配置文件读取、数据查询)。
POSIX 信号量
定义
信号量是用于同步的计数器,描述共享资源的可用数量,支持线程间或进程间同步。POSIX 信号量分为匿名信号量(线程间)和具名信号量(进程间)。
匿名信号量(线程间同步)
核心函数
#include <semaphore.h>
// 初始化匿名信号量
/*
* @brief 初始化匿名信号量
* @param sem:指向信号量对象的指针
* @param pshared:0表示线程间共享(匿名信号量默认用法),非0表示进程间共享(需配合共享内存)
* @param value:信号量初始值(资源可用数量)
* @return 成功返回0,失败返回-1(并设置errno)
* @note 匿名信号量需定义为全局变量或堆变量(确保所有线程可见);不可重复初始化
*/
int sem_init(sem_t *sem, int pshared, unsigned int value);
// P操作:申请资源(信号量减1)
/*
* @brief 申请资源,信号量值减1
* @param sem:已初始化的信号量对象指针
* @return 成功返回0,失败返回-1
* @note 若信号量值为0,当前线程阻塞,直到信号量值>0
*/
int sem_wait(sem_t *sem);
// 尝试P操作:非阻塞
/*
* @brief 尝试申请资源,信号量值减1
* @param sem:已初始化的信号量对象指针
* @return 成功返回0,失败返回-1(errno=EAGAIN表示资源不可用)
*/
int sem_trywait(sem_t *sem);
/**
* 带超时的信号量P操作函数(POSIX标准接口)
* @brief 尝试获取信号量资源(信号量值减1),若资源不可用(值为0)则阻塞等待,
* 超时仍未获取则自动解除阻塞,避免线程永久等待,支持线程间/进程间同步
* @param sem 指向已初始化的sem_t类型信号量的指针,
* 匿名信号量通过sem_init初始化(线程间共享),
* 具名信号量通过sem_open创建(进程间共享),未初始化则返回EINVAL
* @param abstime 指向struct timespec类型的绝对超时时间结构体指针,
* 指定“最晚获取资源的时间”,结构体成员:
* - tv_sec:秒(整数部分)
* - tv_nsec:纳秒(0~999,999,999,小数部分)
* 需基于当前系统时间计算(如当前时间+3秒),非相对时间
* @return int 返回值含义:
* - 0:成功获取信号量,信号量值减1;
* - ETIMEDOUT:超时错误,在abstime指定时间前未获取到资源,线程解除阻塞;
* - EINVAL:参数无效(sem未初始化、abstime的tv_nsec超出合法范围);
* - EDEADLK:检测到死锁风险(极少出现,部分系统支持);
* - EINTR:等待过程中被信号中断(需根据业务决定是否重试);
* - EBUSY:信号量正被其他进程/线程占用(未超时,部分实现返回)
* @note 信号量特性:适用于同步(初始值0)或互斥(初始值1)场景,
* 调用成功后信号量值原子减1,释放时需调用sem_post(值加1);
* 绝对时间设置:需通过clock_gettime(CLOCK_REALTIME, &abstime)获取当前时间,
* 叠加超时时长(如abstime.tv_sec += 3表示3秒后超时),
* 不可直接设为相对时长(如tv_sec=3);
* 编译链接:使用需链接pthread库(部分系统需额外加-lrt),编译命令加“-pthread”;
* 信号量类型兼容:支持匿名信号量(线程间)和具名信号量(进程间),
* 初始化时通过sem_init的pshared参数区分(0=线程间,非0=进程间);
* 资源释放:若超时后不再需要信号量,需调用sem_destroy(匿名)或sem_close+sem_unlink(具名)
*/
int pthread_semtimedwait(sem_t *sem, const struct timespec *abstime);
// V操作:释放资源(信号量加1)
/*
* @brief 释放资源,信号量值加1
* @param sem:已初始化的信号量对象指针
* @return 成功返回0,失败返回-1
* @note 若有线程阻塞在sem_wait,会唤醒一个线程
*/
int sem_post(sem_t *sem);
// 销毁匿名信号量
/*
* @brief 销毁匿名信号量,释放资源
* @param sem:已初始化的信号量对象指针
* @return 成功返回0,失败返回-1
* @note 只能销毁未被使用的信号量;销毁后不可再使用
*/
int sem_destroy(sem_t *sem);
代码示例:匿名信号量实现线程同步
#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <string.h>
// 共享缓冲区:存储键盘输入
char buf[128] = {0};
// 匿名信号量:初始值0(子线程等待主线程输入)
sem_t sem;
// 子线程:等待主线程输入后输出内容
void *child_task(void *arg) {
while (1) {
// P操作:等待主线程释放资源(输入完成)
sem_wait(&sem);
/* 临界区:输出缓冲区内容 */
printf("子线程输出:%s\n", buf);
memset(buf, 0, sizeof(buf)); // 清空缓冲区
/* 临界区结束 */
// 此处可根据需求决定是否执行V操作(本示例无需重复申请)
}
return NULL;
}
int main() {
pthread_t tid;
// 初始化匿名信号量:线程间共享,初始值0
sem_init(&sem, 0, 0);
// 创建子线程
pthread_create(&tid, NULL, child_task, NULL);
// 主线程:读取键盘输入并通知子线程
while (1) {
printf("请输入字符串:");
scanf("%s", buf);
// V操作:释放资源(通知子线程可以读取)
sem_post(&sem);
}
// 销毁信号量(实际不会执行)
sem_destroy(&sem);
return 0;
}
具名信号量(进程间同步)
核心函数
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
// 创建/打开具名信号量
/*
* @brief 创建或打开具名信号量
* @param name:信号量名称(格式为"/sem_name",需以/开头)
* @param oflag:操作标志,O_CREAT表示创建,O_EXCL表示排他创建(避免重复)
* @param mode:信号量权限(如0666表示所有用户可读可写),O_CREAT时必须指定
* @param value:信号量初始值,O_CREAT时必须指定
* @return 成功返回信号量指针,失败返回SEM_FAILED(并设置errno)
* @note 具名信号量存储在/dev/shm目录下,进程结束后不会自动删除
*/
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
// P操作、尝试P操作、V操作:与匿名信号量相同
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int pthread_semtimedwait(sem_t *sem, const struct timespec *abstime);
int sem_post(sem_t *sem);
// 关闭具名信号量
int sem_close(sem_t *sem);
/*
* @brief 关闭具名信号量,释放当前进程的信号量句柄
* @param sem:sem_open返回的信号量指针
* @return 成功返回0,失败返回-1
* @note 关闭后信号量仍存在,其他进程可继续使用
*/
// 删除具名信号量
int sem_unlink(const char *name);
/*
* @brief 永久删除具名信号量文件
* @param name:sem_open指定的信号量名称
* @return 成功返回0,失败返回-1
* @note 需所有进程关闭信号量后,删除才生效
*/
代码示例:具名信号量实现进程同步(两个程序)
shm_open()
/**
* 创建/打开POSIX共享内存对象(POSIX标准接口)
* @brief 用于在文件系统命名空间中创建或打开一个共享内存对象,
* 返回文件描述符供后续操作(如ftruncate设置大小、mmap映射),
* 是现代进程间共享内存通信的首选接口
* @param name 共享内存对象的名称(非普通文件路径),必须严格满足:
* - 以 '/' 开头(唯一允许的 '/' 位置,如 "/my_shm");
* - 路径中不能包含多个 '/'(包括连续 '/' 如 "//my_shm"、多级目录 '/' 如 "/dir/my_shm",均非法);
* - 名称主体为单级字符串('/' 后直接跟名称,无子目录结构,如 "/moduleA_shm" 合法,"/dir/my_shm" 非法);
* - 属于系统级命名空间,对应 /dev/shm 目录下的虚拟文件(可通过 ls /dev/shm 查看);
* - 不同进程通过相同名称可访问同一共享内存,名称长度通常不超过 NAME_MAX(约255字节)
* @param oflag 打开/创建模式标志,核心取值组合:
* - O_RDONLY:只读模式打开已存在的共享内存;
* - O_RDWR:读写模式打开/创建共享内存;
* - O_CREAT:若共享内存不存在则创建(需配合 mode 参数指定权限);
* - O_EXCL:与 O_CREAT 搭配使用,若共享内存已存在则返回 EEXIST(避免覆盖);
* - O_TRUNC:打开已存在的共享内存时,将其大小截断为0(谨慎使用,会清空数据)
* @param mode 共享内存的访问权限(仅 O_CREAT 时有效),格式与文件权限一致:
* - 常用值:0666(所有用户可读可写)、0644(所有者读写,其他用户只读);
* - 权限受 umask 影响(实际权限 = mode & ~umask),需确保权限满足进程访问需求
* @return int 返回值含义:
* - 非负整数:成功,返回共享内存的文件描述符(fd),用于后续 ftruncate、mmap 等操作;
* - -1:失败,错误原因存储在 errno 中,常见错误码:
* EEXIST:O_CREAT + O_EXCL 时,共享内存已存在;
* ENOENT:未指定 O_CREAT,且共享内存不存在;
* EACCES:权限不足(如只读模式打开需写权限,或 /dev/shm 目录无访问权);
* EINVAL:name 格式非法(未以 '/' 开头、含多个 '/'、多级目录等);
* ENOMEM:系统资源不足,无法创建共享内存;
* EROFS:/dev/shm 所在文件系统为只读,无法创建
* @note 初始大小:创建的共享内存初始大小为 0,必须调用 ftruncate 设置实际大小后,才能通过 mmap 映射;
* 文件描述符管理:使用完毕后需调用 shm_close(fd) 关闭,避免文件描述符泄漏;
* 生命周期:共享内存默认随系统重启消失,进程关闭 fd 后不会立即释放,需调用 shm_unlink(name) 永久删除(所有进程关闭 fd 后生效);
* 兼容性:POSIX 标准接口,支持 Linux、macOS、FreeBSD 等现代系统,部分嵌入式系统可能不支持;
* 与 System V 共享内存(shmget)的区别:通过名称(而非 key_t)标识、返回文件描述符(兼容 POSIX I/O 接口)、支持动态调整大小(ftruncate)、管理更直观;
* 编译链接:Linux 系统下部分版本需链接 rt 库(编译加 -lrt),推荐直接加 -pthread 兼容更多场景;
* 名称冲突避免:可在名称中加入程序标识(如 "/app1_moduleA_shm"),避免与其他程序的共享内存重名
*/
int shm_open(const char *name, int oflag, mode_t mode);
ftruncate(f:是 file(文件)的缩写,truncate:英文原义是 “截断、缩短”,此处扩展为 “调整大小”)
/**
* 调整文件/共享内存大小函数(POSIX标准接口)
* @brief 用于修改已打开文件或共享内存对象的大小,核心场景是为共享内存(shm_open创建)
* 分配实际存储空间,或截断/扩展普通文件,调整后文件偏移量不变
* @param fd 已打开的文件描述符,需满足:
* - 以可写模式打开(如O_RDWR、O_WRONLY),只读模式(O_RDONLY)会返回EINVAL;
* - 对应对象支持大小调整(普通文件、共享内存均支持,管道、套接字不支持)
* @param length 目标大小(单位:字节),取值规则:
* - length > 当前大小:文件/共享内存扩展,新增部分填充0;
* - length < 当前大小:文件/共享内存截断,超出length的部分数据丢失;
* - length = 当前大小:无操作,返回成功
* @return int 返回值含义:
* - 0:大小调整成功;
* - -1:调整失败,错误原因存储在errno中,常见错误码:
* EBADF:fd不是有效的文件描述符;
* EINVAL:fd对应对象不支持大小调整,或length为负数,或fd以只读模式打开;
* EROFS:fd对应文件/共享内存位于只读文件系统(如/proc);
* ENOSPC:文件系统剩余空间不足,无法扩展大小
* @note 共享内存必备操作:通过shm_open创建的共享内存初始大小为0,
* 必须调用ftruncate设置大小后,才能通过mmap映射到进程地址空间;
* 文件偏移量影响:ftruncate不改变文件描述符的当前偏移量(由lseek控制);
* 普通文件扩展:扩展后的新空间内容为0,即使原文件末尾有数据,新增部分也不会继承;
* 兼容性:POSIX标准接口,Linux、macOS、FreeBSD等系统均支持;
* 与truncate区别:truncate接收文件路径,ftruncate接收文件描述符,功能完全一致
*/
int ftruncate(int fd, off_t length);
程序 A(写入 PID 到共享内存)
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#define SEM_NAME "/my_sem"
#define SHM_NAME "/my_shm"
#define SHM_SIZE sizeof(pid_t)
int main() {
sem_t *sem;
int shm_fd;
pid_t *shm_ptr;
// 创建/打开具名信号量:初始值0
sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0666, 0);
if (sem == SEM_FAILED) {
perror("sem_open");
return 1;
}
// 创建共享内存
shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, SHM_SIZE);
shm_ptr = (pid_t *)mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);
// 写入当前进程PID到共享内存
*shm_ptr = getpid();
printf("程序A:PID = %d 已写入共享内存\n", *shm_ptr);
// V操作:通知程序B可以读取
sem_post(sem); sleep(5); // 睡眠5s,让进程B等待信号量释放
// 等待一段时间后清理资源
sleep(10);
sem_close(sem);
sem_unlink(SEM_NAME);
munmap(shm_ptr, SHM_SIZE);
shm_unlink(SHM_NAME);
return 0;
}
程序 B(读取共享内存中的 PID)
#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#define SEM_NAME "/my_sem"
#define SHM_NAME "/my_shm"
#define SHM_SIZE sizeof(pid_t)
int main() {
sem_t *sem;
int shm_fd;
pid_t *shm_ptr;
// 打开具名信号量(程序A已创建)
sem = sem_open(SEM_NAME, 0);
if (sem == SEM_FAILED) {
perror("sem_open");
return 1;
}
// 打开共享内存
shm_fd = shm_open(SHM_NAME, O_RDONLY, 0666);
shm_ptr = (pid_t *)mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);
// P操作:等待程序A写入完成
sem_wait(sem);
// 读取共享内存中的PID
printf("程序B:从共享内存读取PID = %d\n", *shm_ptr);
// 清理资源
sem_close(sem);
munmap(shm_ptr, SHM_SIZE);
return 0;
}
注意事项
- 匿名信号量仅适用于同一进程的线程间,具名信号量适用于不同进程间。
- 信号量初始值:同步场景设为 0(等待通知),互斥场景设为 1(模拟互斥锁)。
- 具名信号量需手动删除(sem_unlink),否则会残留文件。
条件量(Condition Variable)
定义
条件量用于线程间 “等待 - 通知” 机制,让线程在条件不满足时阻塞,条件满足时被唤醒。需与互斥锁配合使用,确保条件判断的原子性。

核心函数
初始化条件量
#include <pthread.h>// 动态初始化
/*
* @brief 初始化条件量对象
* @param cond:指向条件量对象的指针
* @param attr:条件量属性,NULL表示默认属性
* @return 成功返回0,失败返回非0错误码
* @note 不可重复初始化已初始化的条件量
*/
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
// 静态初始化
/*
* @brief 静态初始化条件量,效果等同于动态初始化(attr=NULL)
* @note 适用于全局或静态变量,无错误检查
*/
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
等待与唤醒
// 阻塞等待条件满足
/*
* @brief 阻塞等待条件量通知,同时释放互斥锁
* @param cond:已初始化的条件量对象指针
* @param mutex:已上锁的互斥锁对象指针
* @return 成功返回0,失败返回非0错误码
* @note 原子操作:释放锁 + 阻塞等待,避免错过通知;
* 唤醒后会自动重新获取锁;需配合while循环判断条件(避免虚假唤醒)
*/
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
// 限时等待条件满足
/*
* @brief 限时等待条件量通知,超时后返回
* @param cond:条件量对象指针
* @param mutex:互斥锁对象指针
* @param abstime:绝对超时时间(如当前时间+3秒)
* @return 成功返回0,超时返回ETIMEDOUT,失败返回非0错误码
*/
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
// 唤醒一个等待的线程
/*
* @brief 唤醒条件量上阻塞的一个线程(调度策略决定唤醒哪个)
* @param cond:已初始化的条件量对象指针
* @return 成功返回0,失败返回非0错误码
* @note 若多个线程等待,仅唤醒一个;无等待线程时,该操作无效
*/
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒所有等待的线程
/*
* @brief 唤醒条件量上阻塞的所有线程
* @param cond:已初始化的条件量对象指针
* @return 成功返回0,失败返回非0错误码
* @note 适用于需要所有线程响应条件的场景
*/
int pthread_cond_broadcast(pthread_cond_t *cond);
销毁条件量
/*
* @brief 销毁条件量,释放相关资源
* @param cond:已初始化的条件量对象指针
* @return 成功返回0,失败返回非0错误码
* @note 只能销毁无线程等待的条件量;销毁后可重新初始化
*/
int pthread_cond_destroy(pthread_cond_t *cond);
代码示例:条件量与互斥锁配合实现同步
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 临界资源:标志位
int flag = 0;
// 互斥锁(保护条件判断和临界资源)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 条件量(实现等待-通知)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
// 子线程A:模拟获取触摸屏坐标,满足条件则唤醒子线程B
void *task_A(void *arg) {
while (1) {
// 模拟获取触摸屏坐标(假设左上角坐标范围:x<100 && y<100)
int x = rand() % 200;
int y = rand() % 200;
printf("线程A:触摸屏坐标 (%d, %d)\n", x, y);
// 上锁:保护条件判断和flag
pthread_mutex_lock(&mutex);
if (x < 100 && y < 100) {
flag = 1; // 条件满足,设置标志位
printf("线程A:坐标在左上角,唤醒线程B\n");
pthread_cond_signal(&cond); // 唤醒线程B
}
pthread_mutex_unlock(&mutex); // 解锁
sleep(2);
}
return NULL;
}
// 子线程B:等待条件满足后输出字符串
void *task_B(void *arg) {
while (1) {
// 上锁:保护条件判断
pthread_mutex_lock(&mutex);
// 用while循环判断条件(避免虚假唤醒)
while (flag <= 0) {
pthread_cond_wait(&cond, &mutex); // 条件不满足,阻塞等待
}
/* 临界区:条件满足,执行操作 */
printf("线程B:flag = %d,输出目标字符串!\n", flag);
flag = 0; // 重置标志位
/* 临界区结束 */
pthread_mutex_unlock(&mutex); // 解锁
}
return NULL;
}
int main() {
pthread_t tid_A, tid_B;
// 创建子线程
pthread_create(&tid_A, NULL, task_A, NULL);
pthread_create(&tid_B, NULL, task_B, NULL);
// 主线程等待子线程(实际不会退出)
pthread_join(tid_A, NULL);
pthread_join(tid_B, NULL);
// 销毁资源(实际不会执行)
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
return 0;
}
注意事项
- 必须与互斥锁配合使用:条件判断和条件修改需在互斥锁保护下进行。
- 避免虚假唤醒:用 while 循环判断条件,而非 if(系统可能无理由唤醒线程)。
- 唤醒时机:修改条件后立即调用 pthread_cond_signal/broadcast,避免线程永久阻塞。

浙公网安备 33010602011771号