C++11多线程---互斥量、锁、条件变量的总结
关于互斥量std::mutex
的总结
互斥量用于组成代码的临界区。C++的多线程模型是基于内存的,或者说是基于代码片段的,这和我们操作系统学习的临界区概念基本一致,但是与Golang不同,Golang是基于消息模型的。
一个std::mutex
的lock()
和unlock()
之间的代码片段组成一个临界区,这个临界区内部同时最多只能有一个线程进行访问,可以理解为这个片段内部的代码是受到保护的,不会被多线程同时访问造成不可预知的问题。
std::mutex mtx;
mtx.lock();
// 这里的代码,同时最多只能有一个线程进行访问
mtx.unlokc();
当一个线程获取到一个std::mutex
并且调用lock()
后,必须由同一个线程调用unlock()
操作,因此一定要注意两个函数成对出现,否则会造成死锁。
当一个线程获取一个std::mutex
并调用lock()
函数后,其他线程的调用会失败。可以使用try_lock
进行判别,具体细节参考:https://en.cppreference.com/w/cpp/thread/mutex
关于std::lock_guard
和std::unique_lock
的总结
std::lock_guard
比较好理解,因为调用mutex
需要时刻记着解锁,所用这个类封装了一系列的操作,在一个模块中构造了一个std::lock_guard
后,相当于对该结构块加锁,当线程离开结构块后,std::lock_guard
自动析构,相当于解锁。它的结构简单、速度快,但是功能比较少。
std::unique_lock
是对std::lock_guard
功能的一个拓展,功能更多,但是速度会慢一些,具体参照:https://en.cppreference.com/w/cpp/thread/unique_lock
关于条件变量std::condition_variable
的总结
这个相当于操作系统中的wait
和signal
原语操作,需要结合一个std::unique_lock
组成的临界区共同完成功能。wait & signal
原语操作最典型的特点是 “阻塞自己,唤醒别人”。可以这么理解,如果当前满足特定条件不满足,那么就不能进入临界区,当前线程阻塞。当其他线程处理完后,使得条件满足了,线程会唤醒那些处于阻塞状态的线程,使之重新进入。直接通过下面的代码来说明,经典的生产者和消费者问题。
注意只有一个互斥量的时候,唤醒顺序的问题,参照官网:https://en.cppreference.com/w/cpp/thread/condition_variable
在这里给出一个更加简洁的例子:
void worker_thread()
{// Wait until main() sends datastd::unique_lock<std::mutex> lk(m);cv.wait(lk, pred});// 这里执行工作代码,注意下面两个语句的顺序lk.unlock();cv.notify_one();
}
上述代码中,先执行lk.unlock()
,说明当前线程放弃对临界区的所有权,此时再调用notify_one
会唤醒其他线程来对临界区执行操作。如果先唤醒其他线程,则可能unlock
未执行完毕,就有线程到临界区了,此时新来的线程又会阻塞了。
代码示例
下面代码总共是3个例子。第一个Application
是模拟一个事件处理系统的,但是没有使用条件变量,自己实现了一下,第二个Application
使用了条件变量。第三个ProduceAndConsume
是典型的生产者和消费者模型。
未使用条件变量,仅仅借助循环的加载数据应用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <chrono>
#include <random>
#include <cstdlib>const int MAXT = 1000;
std::uniform_int_distribution<int>dis(1, MAXT);
std::random_device rd;
std::mt19937 gen(rd());class Application {public:void mainTask() {std::cout << "Do some main task...\n";auto t = dis(gen); // 随机时间模拟主线任务std::this_thread::sleep_for(std::chrono::milliseconds(t));std::cout << "Finish main task in " << t << " ms\n";mtx.lock();while (!m_bDataLoaded) {mtx.unlock();std::this_thread::sleep_for(std::chrono::microseconds(100));mtx.lock();}mtx.unlock();std::cout << "Get loaded data\n";}void loadData() {std::cout << "Loading data...\n";auto t = dis(gen); // 随机时间模拟主线任务std::this_thread::sleep_for(std::chrono::milliseconds(t));std::lock_guard<std::mutex>lck(mtx);m_bDataLoaded = true;std::cout << "Finish loading data in " << t << " ms\n";}bool isDataLoaded()const {return m_bDataLoaded;}private:bool m_bDataLoaded{ false };std::mutex mtx;std::condition_variable m_convVar;
};int main() {Application app;std::thread t1(&Application::mainTask, &app);std::thread t2(&Application::loadData, &app);t1.join();t2.join();system("pause");return 0;
}
结果:
使用了条件变量的加载数据应用
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <chrono>
#include <random>
#include <cstdlib>const int MAXT = 1000;
std::uniform_int_distribution<int>dis(1, MAXT);
std::random_device rd;
std::mt19937 gen(rd());class Application {public:void mainTask() {std::cout << "Do some main task...\n";auto t = dis(gen); // 随机时间模拟主线任务std::this_thread::sleep_for(std::chrono::milliseconds(t));std::cout << "Finish main task in " << t << " ms\n";std::unique_lock<std::mutex> lck(mtx);m_convVar.wait(lck, std::bind(&Application::isDataLoaded, this));std::cout << "Get loaded data\n";}void loadData() {std::cout << "Loading data...\n";auto t = dis(gen); // 随机时间模拟加载数据任务std::this_thread::sleep_for(std::chrono::milliseconds(t));std::cout << "Finish loading task in " << t << " ms\n";std::unique_lock<std::mutex> lck(mtx);m_bDataLoaded = true;lck.unlock(); // 最好是添加上这一句,本例子无所谓m_convVar.notify_one();}bool isDataLoaded()const {return m_bDataLoaded;}private:bool m_bDataLoaded{ false };std::mutex mtx;std::condition_variable m_convVar;
};int main() {Application app;std::thread t1(&Application::mainTask, &app);std::thread t2(&Application::loadData, &app);t1.join();t2.join();system("pause");return 0;
}
运行结果:
生产者和消费者模型
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <chrono>
#include <random>
#include <functional>
#include <algorithm>
#include <queue>
#include <vector>
#include <cstdlib>const int MAXT = 1000;
const int MAXN = 5;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int>dis(MAXT / 10, MAXT);class ProducerAndConsumer {public:void prodece() {// 随机时间,模拟货物生产过程,生产过程本身不是在临界区std::this_thread::sleep_for(std::chrono::milliseconds(2 * dis(gen)));std::cout << "Prodece\n";std::unique_lock<std::mutex> lck(mtx);m_convPro.wait(lck, std::bind(&ProducerAndConsumer::notFull, this));m_qCargo.push(m_iCargoNum);++m_iCargoNum;m_convCon.notify_one();}void consume() {std::unique_lock<std::mutex> lck(mtx);m_convCon.wait(lck, std::bind(&ProducerAndConsumer::notEmpty, this));int n = m_qCargo.front();m_qCargo.pop();m_convPro.notify_one();lck.unlock(); // 一定要先解锁// 随机时间,模拟货物消费过程,消费过程本身不是在临界区!!!std::this_thread::sleep_for(std::chrono::milliseconds(dis(gen)));std::cout << "Consume\n";}inline bool notFull()const {return m_qCargo.size() < m_iMaxCargoNum;}inline bool notEmpty()const {return m_qCargo.size() > 0;}inline int getBufferSize()const {return m_qCargo.size();}inline int getCargoNum()const {return m_iCargoNum;}private:std::queue<int> m_qCargo; // 货物队列int m_iMaxCargoNum{ MAXN }; // 最大容量int m_iCargoNum{ 0 }; // 货物总数std::mutex mtx;std::condition_variable m_convPro, m_convCon;
};int main() {ProducerAndConsumer pac;auto N = std::thread::hardware_concurrency();std::cout << "Thread num: " << N << std::endl;std::vector<std::thread>producerThreads;std::vector<std::thread>consumerThreads;for (int i = 0; i < N; ++i) {producerThreads.emplace_back(std::thread(&ProducerAndConsumer::prodece, &pac));consumerThreads.emplace_back(std::thread(&ProducerAndConsumer::consume, &pac));}std::for_each(producerThreads.begin(), producerThreads.end(),std::mem_fn(&std::thread::join));std::for_each(consumerThreads.begin(), consumerThreads.end(),std::mem_fn(&std::thread::join));std::cout << "Cargo in buffer: " << pac.getBufferSize() << std::endl;std::cout << "Cargo count: " << pac.getCargoNum() << std::endl;system("pause");return 0;
}
运行结果:
C++11多线程---互斥量、锁、条件变量的总结相关推荐
- C++11学习笔记-----互斥量以及条件变量的使用
在多线程环境中,当多个线程同时访问共享资源时,由于操作系统CPU调度的缘故,经常会出现一个线程执行到一半突然切换到另一个线程的情况.以多个线程同时对一个共享变量做加法运算为例,自增的汇编指令大致如下, ...
- 互斥量、条件变量与pthread_cond_wait()函数的使用,详解(二)
互斥量.条件变量与pthread_cond_wait()函数的使用,详解(二) 1.Linux"线程" 进程与线程之间是有区别的,不过linux内核只提供了轻量进程的支持,未实现线 ...
- c++ 互斥量和条件变量
线程同步时会遇到互斥量和条件变量配合使用的情况,下面看一下C++版的. test.h #include <pthread.h> #include <iostream>class ...
- Linux下互斥量与条件变量详细解析
1. 首先pthread_cond_wait 的定义是这样的 The pthread_cond_wait() and pthread_cond_timedwait() functions are us ...
- 并发编程(一): POSIX 使用互斥量和条件变量实现生产者/消费者问题
boost的mutex,condition_variable非常好用.但是在Linux上,boost实际上做的是对pthread_mutex_t和pthread_cond_t的一系列的封装.因此通过对 ...
- c++11多线程编程同步——使用条件变量condition variable
简述 在多线程编程中,当多个线程之间需要进行某些同步机制时,如某个线程的执行需要另一个线程完成后才能进行,可以使用条件变量. c++11提供的 condition_variable 类是一个同步原语, ...
- 信号灯文件锁linux线程,linux——线程同步(互斥量、条件变量、信号灯、文件锁)...
一.说明 linux的线程同步涉及: 1.互斥量 2.条件变量 3.信号灯 4.文件读写锁 信号灯很多时候被称为信号量,但个人仍觉得叫做信号灯比较好,因为可以与"SYSTEM V IPC的信 ...
- 一个简单的互斥量与条件变量例子
#include <pthread.h> #include <stdio.h> #include <stdlib.h> //互斥变量和条件变量静态初始化 pthre ...
- c++11 多线程编程(六)------条件变量(Condition Variable)
互斥锁std::mutex是一种最常见的线程间同步的手段,但是在有些情况下不太高效. 假设想实现一个简单的消费者生产者模型,一个线程往队列中放入数据,一个线程往队列中取数据,取数据前需要判断一下队列中 ...
最新文章
- linux 修改java版本_Linux 有问必答:如何在 Linux 中改变默认的 Java 版本
- java包和继承的区别,子类和父类在同一个包中继承性
- Jvm垃圾回收器(终结篇)
- 源代码文档生成 Doxygen介绍(转载)
- 【Matlab】找到矩阵中每个连通域的最小值
- eclipse重置页面恢复到最初布局状态
- 会议交流 | IJCKG 2021:Keynotes released!欢迎注册参会
- 用汇编的眼光看C++(之嵌入汇编)
- 【NLP 算法岗】提前批暑期实习面(试)经(历)
- zipkin实战(python)
- “添加删除WIndows组件”中没有IIS时安装IIS方法
- 软件设计师教程 第5版 下载
- c 语言 sqlite,SQLite 的 C 语言编程
- Ring3与Ring0的通信
- Apache Flink_JZZ166_MBY
- MATLAB仪表表盘数字识别
- 大型医院HIS系统源码 优质源码 医院管理系统源码
- 二十三种设计模式 python实现
- Java Web 网络商城案例演示一、(环境搭建)
- 【邢不行|量化小讲堂系列24-Python量化入门】股票自动程序化下单交易 | 视频教程
热门文章
- 数据结构 1-0 绪论
- 【论文笔记】K-plet Recurrent Neural Networks for Sequential Recommendation
- 机器学习和传统编程有什么区别?✅
- ~~单调栈(数据结构)
- 如何在Ubuntu-16.04 / 18.04上为 RTX 2080 Ti GPU 安装Nvidia驱动和cuda-10.0
- sklearn模型保存
- QTextEdit显示中文乱码解决,中文GB2312转Unicode,QString、QByteArray 转换,16进制显示,toUtf8与toLocal8Bit区别
- 【Pytorch】nvidia-dali——一种加速数据增强的方法
- feign.RetryableException: Read timed out executing POST http://......
- Flume-概述-安装