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

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

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

条件变量的优点:

相较于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. day38:MySQL数据库之约束&索引&外键&存储引擎

    目录 part1:数据类型 part2:约束 part3:主键索引 PRI &唯一索引 UNI &普通索引 MUL part4:外键:foreign key part5:在外键中设置联 ...

  2. Windows server 2008 搭建DNS服务

    现在用Windows搭建DNS的已经很少了,感觉也只有一些公司的某块部分能用上,最近在捣鼓这个,索性直接写下来,以后可以看了直接用. 开始: ★★★配置静态IP地址 老样子,有关服务器功能的建立,都是 ...

  3. 经典DP动规 0-1背包问题 二维与一维

    先上代码 b站讲解视频 灯神讲背包 #include <iostream> #include <cstring> #include <algorithm> usin ...

  4. e3mall商城的归纳总结8之solr集群、activemq的搭建和使用

    由于本节内容比较分散,因此专门为这两个技术进行开展了帖子. solr集群的搭建 solr集群solrJ的测试 activemq的搭建 activemq的使用 引入activemq.jar包 我们先来说 ...

  5. 多商铺平台手机电脑自适应宣传展示平台店铺javassh项目代码线上

    部署运行:eclipse 建议使用jdk 1.8 + ,Tomcat 8.0 + 系统介绍: 1.网站后台采用主流的 SSH框架 jsp JSTL,网站前台采用Angular jS框架 2.网站前端采 ...

  6. linux命令的学习随笔

    getconf PAGE_SIZE //获取内存分页的大小alias vi='vim'//临时生效vi /root/.bashrcwhereis ls输出重定向> >> 2> ...

  7. vue <compent> 结合is实现动态切换子组件

    这里就很自然的产生一个新问题:如何给相应的子组件传props?

  8. about blog

    前言 今天无意中发现了一个小姐姐自己设计的的博客,感觉非常的nice,就随手copy一下,完了感觉效果还蛮好的 end 附上小姐姐的博客以及教程

  9. oracle之三RMAN概述

    RMAN概述 6.1 rman的定义和功能: 1) Recovery Manager 2)建立备份和恢复的server process,在oracle server上做备份和恢复 3)rman 备份d ...

  10. 浅说iOS二维码的那些事儿

    二维码需要用到 Quartz 2D 一般是三步走~1导入CoreImage框架,编写字符串转二维码图;2渲染二维码;3显示二维码. 导入头文件 #import <CoreImage/CoreIm ...