一、条件变量的使用

Linux下条件变量相关的API如下:
pthread_cond_init:用于初始化条件变量。
pthread_cond_destory:销毁条件变量。
pthread_cond_broadcast:以广播的方式唤醒所有等待目标条件变量的线程。
pthread_cond_wait:用于等待目标条件变量。该函数调用时需要传入mutex参数(加锁的互斥锁),函数执行时,先把调用线程放入条件变量的请求队列,然后将互斥锁mutex解锁,当函数成功返回为0时,表示重新抢到了互斥锁,互斥锁会再次被锁上,也就是说函数内部会有一次解锁和加锁操作。

条件变量的基本使用方法如下:

// 1. 生产者使用条件变量的方法
......
pthread_mutex_lock(&mutex);
// 这里将产生任务,供消费者线程进行处理
pthread_mutex_unlock(&mutex);
pthread_cond_signal(&cond); // 唤醒一个消费者线程处理
......// 2. 消费者线程使用条件变量的方法
......
pthread_mutex_lock(&mutex);
while(线程执行的目标条件是否满足){pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);
......

二、条件变量深究与陷阱

2.1 条件变量的加锁问题

1.pthread_cond_wait(&cond, &mutex)做了哪些事情?
答:该函数执行时会将线程放在条件变量的请求队列后,内部解锁线程等待被pthread_cond_broadcast信号唤醒或者pthread_cond_signal信号唤醒,唤醒后去竞争锁,若竞争到互斥锁,内部再次加锁。

2.消费者线程使用前要加锁的原因?
答:因为多线程访问,为了避免资源竞争,所以要加锁,使得每个线程互斥的访问公有资源。

3.pthread_cond_wait内部为什么要解锁?
答:使用while判断的时候,满足执行条件,线程便会调pthread_cond_wait阻塞自己,此时它还在持有锁,如果他不解锁,那么其他线程将会无法访问公有资源。具体到pthread_cond_wait的内部实现,当pthread_cond_wait被调用线程阻塞的时候,pthread_cond_wait会自动释放互斥锁。

4.为什么要把调用线程放入条件变量的请求队列再解锁?
答:线程是并发执行的,如果在把调用线程A放在等待队列之前,就释放了互斥锁,这就意味着其他线程比如线程B可以获得互斥锁去访问公有资源,这时候线程A所等待的条件改变了,但是它没有被放在等待队列上,导致A忽略了等待条件被满足的信号。倘若在线程A调用pthread_cond_wait开始,到把A放在等待队列的过程中,都持有互斥锁,其他线程无法得到互斥锁,就不能改变公有资源。

5.为什么pthread_cond_wait函数最后还要加锁?
答:将线程放在条件变量的请求队列后,将其解锁,此时等待被唤醒,若成功竞争到互斥锁,再次加锁,则该消费者线程获得公共资源的消费权。

2.2 消费线程的虚假唤醒

为什么判断线程执行的条件用while而不是if?

答:一般来说,在多线程资源竞争的时候,消费者线程判断资源是否可用,不可用,便调用pthread_cond_wait,生产者线程如果判断资源可用的话,则调用pthread_cond_signal发送一个资源可用信号。

然而,在pthread_cond_wait成功之后,消费者线程就一定可以消费资源么?答案是否定的,如果同时有两个或者两个以上的线程正在等待此资源,wait返回后,资源可能已经被使用了,于是被唤醒但却没有获得消费权利的情况就叫做虚假唤醒

再具体点,有可能多个线程都在等待这个资源可用的信号,信号发出后只有一个资源可用,但是有A,B两个消费者线程都在等待,B线程的比较速度快,获得互斥锁,然后加锁,消耗资源,然后解锁,之后A获得互斥锁,但A回去发现资源已经被使用了,它便有两个选择,一个是去访问不存在的资源,另一个就是继续等待,那么继续等待下去的条件就是使用while,要不然使用if的话pthread_cond_wait返回后,就会顺序执行下去。

所以,在这种多消费者情况下,应该使用while而不是if,如果只有一个消费者,那么使用if是可以的。

三、基于条件变量实现的生产者与消费者模型

文章写到这里,相信大家对条件变量的理解已经到位了,接下来咱直接使用条件变量来实现一个基本的生产者与消费者模型,c++代码如下:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <list>using namespace std;// 任务
class task {public:task(int id){this->taskId = id;}void doTask(){cout << "  dotask id = " << taskId << endl;}private:int taskId;
};list<task*> taskList;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;// 生产者线程
void *producerThreadFunc(void *param){int taskId = 0;task *t = nullptr;while (true){t = new task(taskId);pthread_mutex_lock(&mutex);taskList.emplace_back(t);cout << "Produce task, id = " << taskId << endl;pthread_mutex_unlock(&mutex);pthread_cond_signal(&cond); // 唤醒一个消费者线程处理taskId++;sleep(1);}return nullptr;
}// 消费者线程
void *consumerThreadFunc(void *param){task *t = nullptr;while (true){pthread_mutex_lock(&mutex);while(taskList.empty()){pthread_cond_wait(&cond, &mutex);}t = taskList.front();taskList.pop_front();pthread_mutex_unlock(&mutex);cout << "ThreadID = " << pthread_self();t->doTask();delete t;}return nullptr;
}int main(){pthread_t producerThread;pthread_t consumerThread[10]; // 创建10个消费线程pthread_create(&producerThread, NULL, producerThreadFunc, NULL);for(int i = 0; i < 10; i++){pthread_create(&consumerThread[i], NULL, consumerThreadFunc, NULL);}pthread_join(producerThread, NULL);for(int i = 0; i < 10; ++i){pthread_join(consumerThread[i], NULL);}pthread_cond_destroy(&cond);pthread_mutex_destroy(&mutex);return 0;
}

执行结果如下:

参考博客如下:
最新版Web服务器项目详解 - 09 日志系统(上):https://mp.weixin.qq.com/s/IWAlPzVDkR2ZRI5iirEfCg

条件变量存在的陷阱、生产者与消费者模型相关推荐

  1. Linux系统编程---17(条件变量及其函数,生产者消费者条件变量模型,生产者与消费者模型(线程安全队列),条件变量优点,信号量及其主要函数,信号量与条件变量的区别,)

    条件变量 条件变量本身不是锁!但它也可以造成线程阻塞.通常与互斥锁配合使用.给多线程提供一个会合的场所. 主要应用函数: pthread_cond_init 函数 pthread_cond_destr ...

  2. Go语言编程:使用条件变量Cond和channel通道实现多个生产者和消费者模型

    如题,使用条件变量Cond和channel通道实现多个生产者和消费者模型.Go语言天生带有C语言的基因,很多东西和C与很像,但是用起来 绝对比C语言方便.今天用Go语言来实现下多消费者和生产者模型.如 ...

  3. 3.12-3.14生产者和消费者模型、条件变量、信号量

    目录 1.生产者消费者模型 2.条件变量 3.信号量 1.生产者消费者模型 2.条件变量 3.信号量 初始化的值:生产者-目前可以生产几个 -n   消费者-目前可以消费几个 -0

  4. c/c++:线程同步(互斥锁、死锁、读写锁、条件变量、生产者和消费者模型、信号量)

    目录 1. 概念 2. 互斥锁 3. 死锁 4. 读写锁 5. 条件变量 5.1 生产者和消费者模型 6. 信号量 1. 概念 线程同步: > 当有一个线程在对内存进行操作时,其他线程都不可以对 ...

  5. Linux 生产者与消费者模型C++实现

    生产者与消费者模型 本篇博客代码实现都是在linux环境下跑的 通过条件变量实现 应用场景:针对大量数据的产生与处理的场景 生产与处理是放到不同执行流中完成的,中间会增加一个数据缓冲区,作为中间的数据 ...

  6. 例说生产者和消费者模型

    什么是生产者和消费者模型 什么是生产者消费者模型?生产者和消费是操作系统中一种重要的模型,它描述的是一种等待和通知的机制,如下图. 生产者和消费者模型必须具有的条件 用一句话概括,生产者消费者模型必须 ...

  7. Linux下生产者与消费者模型

    1. 概念   有一个或多个生产者生产某种类型的数据,并放在缓冲区里(生产者),有一个消费者从缓冲区中取数据,每次取一项(消费者).系统保证任何时候只有一个主题可以访问缓存区.所以当生产满时,生产者不 ...

  8. python 进程间同步_python之路29 -- 多进程与进程同步(进程锁、信号量、事件)与进程间的通讯(队列和管道、生产者与消费者模型)与进程池...

    所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了.至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠 ...

  9. C++实现生产者和消费者模型

    C++实现生产者和消费者模型 C++实现生产者和消费者模型 1.实现细节 2.单生产者-单消费者模型 3.单生产者-多消费者模型 4.多生产者-单消费者模型 5.多生产者-多消费者模型 参考 C++实 ...

最新文章

  1. php时区问题导致php页面显示不正常
  2. cad自动填写页码lisp_你被论文格式搞崩过几次心态,解决目录和页码,其它都不叫事...
  3. 克隆指定的分支:git clone -b 分支名仓库地址
  4. python3 线程间通讯(借助queue)
  5. 在OR项目中使用火焰图
  6. 20135202闫佳歆-第二章家庭作业-2.69
  7. Android 开源项目android-open-project工具库解析之(一) 依赖注入,图片缓存,网络相关,数据库orm工具包,Android公共库...
  8. php第三方登录代码,thinkPHP5项目中实现QQ第三方登录功能
  9. c语言指针自定义函数,c语言函数指针定义,指针函数和函数指针的区别
  10. 39.数组中数值和下标相等的元素
  11. 前端的葵花宝典 - 红宝书《JavaScript高级程序设计(第4版)》学习笔记
  12. HTTP/1.1 Range和Content-Range
  13. 二开版彩虹易支付全开源10套模板带风控实名系统源码
  14. eclipse 优化。
  15. 黄聪:UEditor如何在wordpress中调用
  16. CEF与JavaScript交互读取电脑信息
  17. TCP and UDP
  18. Android App Bundle出来了,App加壳技术不能用了怎么办?
  19. 只有1kb的清理软件_教您清理1kb快捷方式病毒
  20. [人工智能-深度学习-23]:卷积神经网络CNN - 卷积核的本质是多维输入的神经元

热门文章

  1. AI作画升级,OpenVINO™ 和英特尔独立显卡助你快速生成视频
  2. Android学习(一)
  3. 网络对抗技术实验报告一
  4. Date ----数码时钟
  5. Django的MVT、MVC模式
  6. 2022年全球市场儿童三轮车总体规模、主要生产商、主要地区、产品和应用细分研究报告
  7. tf47:SeqGAN
  8. lejos+ev3+win7环境搭建
  9. 计算机总线接口规范协议,MIL-STD-1553B航空总线协议开发手册
  10. ubuntu20.04安装