条件变量是用来等待线程而不是上锁的,条件变量通常和互斥锁一起使用。条件变量之所以要和互斥锁一起使用,主要是因为互斥锁的一个明显的特点就是它只有两种状态:锁定和非锁定,而条件变量可以通过允许线程阻塞和等待另一个线程发送信号来弥补互斥锁的不足,所以互斥锁和条件变量通常一起使用。

当条件满足的时候,线程通常解锁并等待该条件发生变化,一旦另一个线程修改了环境变量,就会通知相应的环境变量唤醒一个或者多个被这个条件变量阻塞的线程。这些被唤醒的线程将重新上锁,并测试条件是否满足。一般来说条件变量被用于线程间的同步;当条件不满足的时候,允许其中的一个执行流挂起和等待。

简而言之,条件变量本身不是锁,但它也可以造成线程阻塞,通常与互斥锁配合使用,给多线程提供一个会合的场所。

条件变量的优点:

相较于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);

函数作用:

阻塞等待一个条件变量。具体而言有以下三个作用:

  1. 阻塞等待条件变量cond(参1)满足;
  2. 释放已掌握的互斥锁mutex(解锁互斥量)相当于pthread_mutex_unlock(&mutex);
  3. 当被唤醒,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系统编程—条件变量的更多相关文章

  1. 读书笔记之Linux系统编程与深入理解Linux内核

    前言 本人再看深入理解Linux内核的时候发现比较难懂,看了Linux系统编程一说后,觉得Linux系统编程还是简单易懂些,并且两本书都是讲Linux比较底层的东西,只不过侧重点不同,本文就以Linu ...

  2. 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 共享内存 ...

  3. Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号

    Linux 系统编程 学习:03-进程间通信1:Unix IPC(2)信号 背景 上一讲我们介绍了Unix IPC中的2种管道. 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道 ...

  4. Linux 系统编程 学习:04-进程间通信2:System V IPC(1)

    Linux 系统编程 学习:04-进程间通信2:System V IPC(1) 背景 上一讲 进程间通信:Unix IPC-信号中,我们介绍了Unix IPC中有关信号的概念,以及如何使用. IPC的 ...

  5. Linux 系统编程 学习:05-进程间通信2:System V IPC(2)

    Linux 系统编程 学习:05-进程间通信2:System V IPC(2) 背景 上一讲 进程间通信:System V IPC(1)中,我们介绍了System IPC中有关消息队列.共享内存的概念 ...

  6. Linux 系统编程 学习:11-线程:线程同步

    Linux 系统编程 学习:11-线程:线程同步 背景 上一讲 我们介绍了线程的属性 有关设置.这一讲我们来看线程之间是如何同步的. 额外安装有关的man手册: sudo apt-get instal ...

  7. linux系统编程之错误处理

    在linux系统编程中,当系统调用出现错误时,有一个整型变量会被设置,这个整型变量就是errno,这个变量的定义在/usr/include/errno.h文件中 #ifndef _ERRNO_H /* ...

  8. Linux系统编程@进程通信(一)

    进程间通信概述 需要进程通信的原因: 数据传输 资源共享 通知事件 进程控制 Linux进程间通信(IPC)发展由来 Unix进程间通信 基于System V进程间通信(System V:UNIX系统 ...

  9. Linux 系统编程

    简介和主要概念 Linux 系统编程最突出的特点是要求系统程序员对它们工作的的系统的硬件和操作系统有深入和全面的了解,当然它们还有库和系统调用上的区别. 系统编程分为:驱动编程.用户空间编程和网络编程 ...

随机推荐

  1. 算法-搜索(4)ISAM算法

    ISAM技术是一种典型的多叉搜索树结构,它使用了3级索引结构:主索引.柱面索引.磁道索引 所有数据记录在基本区按关键码升序排序,后一磁道所有关键码均大于前一磁道.在某一磁道插入新记录时,如果原来该磁道 ...

  2. 第一篇 Scrum冲刺博客

    一.Alpha任务认领 冯荣新 任务 预计时间 搜索框 0.5h 首页轮播图 0.5h 分类导航 2h 商品列表 2h 商品详情轮播图 0.5h 商品底部工具栏 1h 购物车列表 1.5h 购物车工具 ...

  3. 小白一样能建站——winser2012 IIS8.0搭建基本的网站

    在window server 2012环境下,搭建一个基本的 网站.能够使用即可. 打开服务器管理器 添加角色和功能 默认下一步 下一步, 下一步,选择web服务器 添加功能 下一步, 下一步,不安装 ...

  4. Chrome 发一个请求,后台Controller 执行2次

    chrome 每发一次请求,都会执行2次controller,换成其他浏览器就不会. 最后发现是这个插件导致的,果断删除.

  5. Python和Nose实现移动应用的自动化测试

    今天跟大家聊的是Python和Nose实现移动应用的自动化测试,希望对你们有帮助,有说的不好的地方,还请多多指教! 采用Appium进行自动化的功能性测试最酷的一点是,你可以使用具有最适合你的测试工具 ...

  6. type类型为number的input标签可以输入字母e

    主要原因是:e在数学上代表的是无理数,是一个无限不循环的小数,其值约为2.7182818284,所以在输入e的时候,输入框会把e当成一个数字看待. 可以采用下面的方式来避免这个BUG,在input标签 ...

  7. 【JAVA】生成一个32位的随机数。防止重复,保留唯一性

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985, QQ986945193 微博:http://weibo.com/mcxiaobing import ...

  8. 我竟然才知道slf4j里还有个MDC

    大家好久不见,我是walking.今天给大家带来一个日志方面的知识——MDC,不知道大家认识不,反正我是最近刚知道的 初见MDC 前两天看项目中的代码,无意中看到一个自定义的线程池 MDCThread ...

  9. 19_Python算法

    1.冒泡算法 list = [1, 5, 2, 6, 9, 3, 4, 0] print(len(list)) # conunt = 1 while conunt < len(list): fo ...

  10. 跟着兄弟连系统学习Linux-【day01】

    day01-20200527 p1.unix发展历史         (1960,有一个实验室,三个团队组成,开发了Unix雏形,但是因为没有办法发版,所以就荒废了.这个小组里面有一个人,打游戏的时候 ...