Linux系统编程—条件变量
条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。
当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待。
简而言之,条件变量本身不是锁,但它也可以造成线程阻塞,通常与互斥锁配合使用,给多线程提供一个会合的场所。
条件变量的优点:
相较于mutex而言,条件变量可以减少竞争。如果仅仅是mutex,那么,不管共享资源里有没数据,生产者及所有消费都全一窝蜂的去抢锁,会造成资源的浪费。
如直接使用mutex,除了生产者、消费者之间要竞争互斥量以外,消费者之间也需要竞争互斥量,但如果汇聚(链表)中没有数据,消费者之间竞争互斥锁是无意义的。有了条件变量机制以后,只有生产者完成生产,才会引起消费者之间的竞争。提高了程序效率。
主要应用函数:
pthread_cond_init函数
pthread_cond_destroy函数
pthread_cond_wait函数
pthread_cond_timedwait函数
pthread_cond_signal函数
pthread_cond_broadcast函数
以上6 个函数的返回值都是:成功返回0, 失败直接返回错误号。
pthread_cond_t类型:用于定义条件变量,比如:pthread_cond_t cond;
pthread_cond_init函数
函数原型:
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
函数作用:
初始化一个条件变量
参数说明:
cond:条件变量,调用时应传&cond给该函数
attr:条件变量属性,通常传NULL,表示使用默认属性
也可以使用静态初始化的方法,初始化条件变量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_cond_destroy函数
函数原型:
int pthread_cond_destroy(pthread_cond_t *cond);
函数作用:
销毁一个条件变量
pthread_cond_wait函数
函数原型:
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函数作用:
阻塞等待一个条件变量。具体而言有以下三个作用:
- 阻塞等待条件变量cond(参1)满足;
- 释放已掌握的互斥锁mutex(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
- 当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁
其中1、2.两步为一个原子操作。
pthread_cond_timedwait函数
函数原型:
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
函数作用:
限时等待一个条件变量
参数说明:
前两个比较好理解,重点说明第三个参数。
这里有个struct timespec结构体,可以在man sem_timedwait中查看。结构体原型如下:
struct timespec {
time_t tv_sec; /* seconds */ 秒
long tv_nsec; /* nanosecondes*/ 纳秒
}
struct timespec定义的形参abstime是个绝对时间。注意,是绝对时间,不是相对时间。什么是绝对时间?2018年10月1日10:10:00,这就是一个绝对时间。什么是相对时间?给洗衣机定时30分钟洗衣服,就是一个相对时间,也就是说从当时时间开始计算30分钟,诸如此类。
如:time(NULL)返回的就是绝对时间。而alarm(1)是相对时间,相对当前时间定时1秒钟。
adstime所相对的时间是相对于1970年1月1日00:00:00,也就是UNIX计时元年。
下面给出一个错误用法:
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t);
这种用法只能定时到 1970年1月1日 00:00:01秒,想必这个时间大家都还没出生。
正确用法:
time_t cur = time(NULL); 获取当前时间。
struct timespec t; 定义timespec 结构体变量t
t.tv_sec = cur+1; 定时1秒
pthread_cond_timedwait (&cond, &mutex, &t); 传参
pthread_cond_signal函数
函数原型:
int pthread_cond_signal(pthread_cond_t *cond);
函数作用:
唤醒至少一个阻塞在条件变量上的线程
pthread_cond_broadcast函数
函数原型:
int pthread_cond_broadcast(pthread_cond_t *cond);
函数作用:
唤醒全部阻塞在条件变量上的线程
生产者消费者条件变量模型
不管是什么语言,只要提到线程同步,一个典型的案例就是生产者消费者模型。在Linux环境下,借助条件变量来实现这一模型,是比较常见的一种方法。
假定有两个线程,一个模拟生产者行为,一个模拟消费者行为。两个线程同时操作一个共享资源(一般称之为汇聚),生产向其中添加产品,消费者从中消费掉产品。
看如下示例,使用条件变量模拟生产者、消费者问题:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
typedef struct msg {
struct msg *next;
int num;
}msg_t;
msg_t *head = NULL;
msg_t *mp = NULL;
/* 静态初始化 一个条件变量 和 一个互斥量*/
pthread_cond_t has_product = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
void *th_producer(void *arg)
{
while (1) {
mp = malloc(sizeof(msg_t));
mp->num = rand() % 1000; //模拟生产一个产品
printf("--- produce: %d --------\n", mp->num);
pthread_mutex_lock(&mutex);
mp->next = head;
head = mp;
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&has_product); //唤醒线程去消费产品
sleep(rand() % 5);
}
return NULL;
}
void *th_consumer(void *arg)
{
while (1) {
pthread_mutex_lock(&mutex);
while (head == NULL) { //如果链表里没有产品,就没有抢锁的必要,一直阻塞等待
pthread_cond_wait(&has_product, &mutex);
}
mp = head;
head = mp->next; //模拟消费掉一个产品
pthread_mutex_unlock(&mutex);
printf("========= consume: %d ======\n", mp->num);
free(mp);
mp = NULL;
sleep(rand() % 5);
}
return NULL;
}
int main()
{
pthread_t pid, cid;
srand(time(NULL));
pthread_create(&pid, NULL, th_producer, NULL);
pthread_create(&cid, NULL, th_consumer, NULL);
pthread_join(pid, NULL);
pthread_join(cid, NULL);
return 0;
}
运行结果:
更多精彩内容,请关注公众号良许Linux,公众内回复1024可免费获得5T技术资料,包括:Linux,C/C++,Python,树莓派,嵌入式,Java,人工智能,等等。公众号内回复进群,邀请您进高手如云技术交流群。
公众号:良许Linux
有收获?希望老铁们来个三连击,给更多的人看到这篇文章
Linux系统编程—条件变量的更多相关文章
- 读书笔记之Linux系统编程与深入理解Linux内核
前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...
- linux系统编程之框架
linux系统编程之框架: 1. 进程 1.1 进程概念 1.1.1 PCB 1.1.2 环境变量 1.2 进程控制 1.3 进程间通信 1.3.1 管道 1.3.2 有名管道 1.3.3 共享内存 ...
- Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号
Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...
- Linux 系统编程 学习:04-进程间通信2:System V IPC(1)
Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...
- Linux 系统编程 学习:05-进程间通信2:System V IPC(2)
Linux 系统编程 学习:05-进程间通信2:System V IPC(2) 背景 上一讲 进程间通信:System V IPC(1)中,我们介绍了System IPC中有关消息队列.共享内存的概念 ...
- Linux 系统编程 学习:11-线程:线程同步
Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置.这一讲我们来看线程之间是如何同步的. 额外安装有关的man手册: sudo apt-get instal ...
- linux系统编程之错误处理
在linux系统编程中,当系统调用出现错误时,有一个整型变量会被设置,这个整型变量就是errno,这个变量的定义在/usr/include/errno.h文件中 #ifndef _ERRNO_H /* ...
- Linux系统编程@进程通信(一)
进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...
- Linux 系统编程
简介和主要概念 Linux 系统编程最突出的特点是要求系统程序员对它们工作的的系统的硬件和操作系统有深入和全面的了解,当然它们还有库和系统调用上的区别. 系统编程分为:驱动编程.用户空间编程和网络编程 ...
随机推荐
- 算法-图(2)Bellman-Ford算法求最短路径
template <class T,class E> void Bellman-Ford(Graph<T,E>&G, int v, E dist[], int path ...
- 使用Wasserstein GAN生成小狗图像
一.前期学习经过 GAN(Generative Adversarial Nets)是生成对抗网络的简称,由生成器和判别器组成,在训练过程中通过生成器和判别器的相互对抗,来相互的促进.提高.最近一段时间 ...
- springmvc以及springboot中的拦截器配置
拦截器两种实现 如果不同的controller中都需要拦截器,不能使用相同的拦截器,因为拦截器不能跨controller,这个时候只能为不同的controller配置不同的拦截器,每一个拦截器只能 ...
- typepra快捷键
- 总结java中文件拷贝剪切的5种方式-JAVA IO基础总结第五篇
本文是Java IO总结系列篇的第5篇,前篇的访问地址如下: 总结java中创建并写文件的5种方式-JAVA IO基础总结第一篇 总结java从文件中读取数据的6种方法-JAVA IO基础总结第二篇 ...
- android MVVM(1)用LiveData关联VM 与 V
1.官方文档 MVVM 官方文档: https://developer.android.com/jetpack/docs/guide ViewModel 文档: https://developer.a ...
- 非IT行业大企程序员讲述MIS系统开发案例
雪莉叹了一口气,调整了一下被汗水濡湿的刘海,然后向后靠在办公椅中,伸手在电脑键盘上输入了一些内容, 最后拿起印刷着房地产广告的扇子,边扇风边等待着. 她的工位在办公室的东侧角落,侧靠着窗.此时 ...
- 《Offer一箩筐》求职之前你必须知道的 4 件事!!
「MoreThanJava」 宣扬的是 「学习,不止 CODE」. 如果觉得 「不错」 的朋友,欢迎 「关注 + 留言 + 分享」,文末有完整的获取链接,您的支持是我前进的最大的动力! Hi~ 这里是 ...
- 解锁用户scott并授权
请输入用户名: system 输入口令: 连接到: Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - 64bit Producti ...
- pwnable——flag
分析 此题为reverse题目,首先放入ida查看: 程序函数太少,应该加过壳 在hex view中发现程序通过upx加壳,利用upx -d 指令解压得到程序,重新放入ida查看 程序非常简单,读取f ...