linux c 编程 --- 条件变量
原理
条件变量内有等待队列,可以广播唤醒等待队列中的所有线程,可以用于“一个事件发生,多个线程唤醒”的场景。有点类似于 FreeeRTOS 的事件组。
使用场景
在多线程编程中仅使用互斥锁来完成互斥是不够用的, 如以下情形:
假设有两个线程 t1 和 t2, 需要这个两个线程循环对一个共享变量 sum 进行自增操作,那么 t1 和 t2 只需要使用互斥量即可保证操作正确完成,线程执行代码如所示:
pthread_mutex_t sumlock= PTHREAD_MUTEX_INITIALIZER;
void * t1t2(void) { pthread_mutex_lock(&sumlock); sum++; pthread_mutex_unlock(&sumlock); }
如果这时需要增加另一个线程 t3,需要 t3 在 count 大于 100 时将 count 值重新置 0 值,那么可以 t3 可以实现如下:
void * t3 (void) { pthread_mutex_lock(&sumlock); if (sum >= 100) { sum = 0; pthread_mutex_unlock(&sumlock); } else { pthread_mutex_unlock(&sumlock); usleep(100); } }
以上代码存在以下问题:
1) sum 在大多数情况下小于 100, 那么对 t3 的代码来说,大多数情况下, 走的是 else 分支, 只是 lock 和 unlock,然后 sleep()。 这浪费了 CPU 处理时间。
2) 为了节省 CPU 处理时间, t3 会在探测到 sum 没到达 100 的时候 usleep()一段时间。这样却又带来另外一个问题, 亦即 t3 响应速度下降。 可能在 sum 到达 200 的时候, t3 才会醒过来。
这样时间与效率出现了矛盾,而条件变量就是解决这个问题的好方法。
创建与销毁
1. 创建条件变量
Pthreads 用 pthread_cond_t 类型的变量来表示条件变量。程序必须在使用 pthread_cond_t变量之前对其进行初始化。
(1) 静态初始化
对于静态分配的变量可以简单地将 PTHREAD_COND_INITIALIZER 赋值给变量来初始化默认行为的条件变量。
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
(2)动态初始化
对动态分配或者不使用默认属性的条件变量来说可以使用 pthread _cond_init()来初始化。函数原型如下:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
参数 cond 是一个指向需要初始化 pthread_cond_t 变量的指针,参数 attr 传递 NULL 值时, pthread_cond_init()将 cond 初始化为默认属性的条件变量。
函数成功将返回 0;否则返回一个非 0 的错误码。
静态初始化程序通常比调用 pthread_cond_init()更有效,而且在任何线程开始执行之前,确保变量被执行一次。
以下代码示例了条件变量的初始化。
pthread_cond_t cond; int error; if (error = pthread_cond_init(&cond, NULL)); fprintf(stderr, "Failed to initialize cond : %s\n", strerror(error));
2. 销毁条件变量
函数 pthread_cond_destroy()用来销毁它参数所指出的条件变量,函数原型如下:
int pthread_cond_destroy(pthread_cond_t *cond);
函数成功调用返回 0,否则返回一个非 0 的错误码。以下代码演示了如何销毁一个条件变量。
pthread_cond_t cond; int error; if (error = pthread_cond_destroy(&cond)) fprintf(stderr, "Failed to destroy cond : %s\n", strerror(error));
等待与通知
等待
条件变量是与条件测试一起使用的,通常线程会对一个条件进行测试,如果条件不满足就会调用条件等待函数来等待条件满足。
条件等待函数有 pthread_cond_wait()pthread_cond_timedwait()和两个,函数原型如下:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex); int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
pthread_cond_wait()函数在条件不满足时将一直等待, 而 pthread_cond_timedwait()将只等待一段时间。
参数 cond 是一个指向条件变量的指针,参数 mutex 是一个指向互斥量的指针,线程在调用前应该拥有这个互斥量,当线程要加入条件变量的等待队列时,等待操作会使线程释放这个互斥量。 pthread_timedwait()的第三个参数 abstime 是一个指向返回时间的指针,如果条件变量通知信号没有在此等待时间之前出现,等待将超时退出, abstime 是个绝对时间,而不是时间间隔。
以上函数成功调用返回 0,否则返回非 0 的错误码,其中 pthread_cond_timedwait() 函数如果 abstime 指定的时间到期,错误码为 ETIMEOUT。
以下代码使得线程进入等待,直到收到通知并且满足 a 大于等于 b 的条件。
pthread_mutex_lock(&mutex) while(a < b) pthread_cond_wait(&cond, &mutex) pthread_mutex_unlock(&mutex)
通知
当另一个线程修改了某参数可能使得条件变量所关联的条件变成真时,它应该通知一个或者多个在条件变量等待队列中的线程。
条件通知函数有 pthread_cond_signal()和 pthread_cond_broadcast()函数,其中 pthread_cond_signal 函数可以唤醒一个在条件变量等待队列等待的线程,而 pthread_cond_broadcast 函数可以唤醒所有在条件变量等待队列等待的线程。函数原型如下:
int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);
参数 cond 是一个指向条件变量的指针。函数成功返回 0,否则返回一个非 0 的错误码。
线程范例
#include<stdio.h> #include<string.h> #include<pthread.h> #include<stdlib.h> #include<unistd.h> pthread_t tid[3]; int sum = 0; pthread_mutex_t sumlock = PTHREAD_MUTEX_INITIALIZER; /* 静态初始化互斥量 */ pthread_cond_t cond_sum_ready = PTHREAD_COND_INITIALIZER; /* 静态初始化条件变量 */
void * t1t2(void *arg) { int i; long id = (long)arg; for (i = 0; i < 60; i++) { pthread_mutex_lock(&sumlock); /* 使用互斥量保护临界变量 */ sum++; printf("t%ld: read sum value = %d\n", id + 1 , sum); pthread_mutex_unlock(&sumlock); if (sum >= 100) pthread_cond_signal(&cond_sum_ready); /* 发送条件通知,唤醒等待线程 */ } return NULL; }
void * t3(void *arg) { pthread_mutex_lock(&sumlock); while(sum < 100) /* 不满足条件将一直等待 */ pthread_cond_wait(&cond_sum_ready, &sumlock); /* 等待条件满足 */ sum = 0; printf("t3: clear sum value\n"); pthread_mutex_unlock(&sumlock); return NULL; }
int main(void) { int err; long i; for (i = 0; i < 2; i++) { err = pthread_create(&(tid[i]), NULL, &t1t2, (void *)i); /* 创建线程 1 线程 2 */ if (err != 0) { printf("Can't create thread :[%s]", strerror(err)); } } err = pthread_create(&(tid[2]), NULL, &t3, NULL); /* 创建线程 3 */ if (err != 0) printf("Can't create thread :[%s]", strerror(err)); for (i = 0; i < 3; i++) pthread_join(tid[i], NULL); return 0; }
运行结果如下所示, sum 累加到 100 时发送条件通知,但程序结果中 sum 计算到 103 时, t3 才被调用,这是因为 signal 与 wait 调用之间有间隙存在。
t1: read sum value = 1 t1: read sum value = 2 ... t2: read sum value = 100 t1: read sum value = 101 t1: read sum value = 102 t1: read sum value = 103 t3: clear sum value t2: read sum value = 1 ....... t2: read sum value = 17
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
2018-09-03 STM32F407 ------ 使用定时器实现精确延时
2018-09-03 超声波测距模块