c++多线程并发学习笔记(2)
等待一个时间或其他条件
在一个线程等待完成任务时,会有很多选择:
1. 它可以持续的检查共享数据标志(用于做保护工作的互斥量),直到另一个线程完成工作时对这个标志进行重设。缺点:资源浪费,开销大
2. 在等待线程的检查间隙,使用std::this_thread::sleep_for()进行周期性的间歇。 缺点:休眠时间抉择困难
bool flag;
std::mutex m; void wait_for_flag()
{
std::unique_lock<std::mutex> lk(m);
while(!flag)
{
lk.unlock(); // 1 解锁互斥量
std::this_thread::sleep_for(std::chrono::milliseconds()); // 2 休眠100ms
lk.lock(); // 3 再锁互斥量
}
}
3. 使用C++标准库提供的工具去等待事件的发生。通过另一线程触发等待事件的机制是最基本的唤醒方式,这种机制就称为“条件变量”。
C++标准库对条件变量有两套实现:std::condition_variable
和std::condition_variable_any
。这两个实现都包含在<condition_variable>
头文件的声明中。两者都需要与一个互斥量一起才能工作(互斥量是为了同步)
std::condition_variable:只能与std::mutex一起工作,开销少
std::condition_variable_any:可以和任何满足最低标准的互斥量一起工作,开销大
std::condition_variable 提供两个重要的接口:notify_one()
和wait()。
wait()
可以让线程陷入休眠状态,notify_one()
就是唤醒处于wait
中的其中一个条件变量(可能当时有很多条件变量都处于wait
状态)。
template<typename Predicate>
wait(std::unique_lock<std::mutex>& lk, Predicate pred)
wait()会去检查这些条件(通过调用所提供的函数),当条件满足(调用所提供的函数返回true)时返回。如果条件不满足(调用所提供的函数返回false),wait()函数将解锁互斥量,并且将这个线程置于阻塞或等待状态。另外一个线程调用notify_one()通知条件变量时,线程从睡眠状态中苏醒,重新获取互斥锁,并且再次检查条件是否满足。
std::condition_variable::wait的一个最小化实现:
template<typename Predicate>
void minimal_wait(std::unique_lock<std::mutex>& lk,Predicate pred){
while(!pred()){
lk.unlock();
lk.lock();
}
}
考虑一个生产者消费者模型:一个线程往队列中放入数据,一个线程往队列中取数据,取数据前需要判断一下队列中确实有数据,由于这个队列是线程间共享的,所以,需要使用互斥锁进行保护,一个线程在往队列添加数据的时候,另一个线程不能取,反之亦然。用互斥锁实现如下:
#include <iostream>
#include <deque>
#include <thread>
#include <mutex> std::deque<int> q;
std::mutex mu; void function_1() {
int count = ;
while (count > ) {
std::unique_lock<std::mutex> locker(mu);
q.push_front(count);
locker.unlock();
std::this_thread::sleep_for(std::chrono::seconds());
count--;
}
} void function_2() {
int data = ;
while ( data != ) {
std::unique_lock<std::mutex> locker(mu);
if (!q.empty()) {
data = q.back();
q.pop_back();
locker.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
} else {
locker.unlock();
}
}
}
int main() {
std::thread t1(function_1);
std::thread t2(function_2);
t1.join();
t2.join();
return ;
}
问题在于,如果生产者的速度比较慢,代码中每隔1s才会有一次数据生产,这时消费者都要去获取锁-->判断队列里是否有数据-->释放锁,这个过程就是资源的浪费,无用功使得cpu占用率很高。
使用std::this_thread::sleep_for()来对代码进行改造:
void function_2() {
int data = ;
while ( data != ) {
std::unique_lock<std::mutex> locker(mu);
if (!q.empty()) {
data = q.back();
q.pop_back();
locker.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
} else {
locker.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds());
}
}
}
这样可以减低cpu占用率,但问题在于在实际操作中如何选择休眠时间,太长或者太短都不好。
最后可以使用条件变量来对这个代码进行改造:
#include <iostream>
#include <deque>
#include <thread>
#include <mutex>
#include <condition_variable> std::deque<int> q;
std::mutex mu;
std::condition_variable cond; void function_1() {
int count = ;
while (count > ) {
std::unique_lock<std::mutex> locker(mu);
q.push_front(count);
locker.unlock();
cond.notify_one(); // Notify one waiting thread, if there is one.
std::this_thread::sleep_for(std::chrono::seconds());
count--;
}
} void function_2() {
int data = ;
while ( data != ) {
std::unique_lock<std::mutex> locker(mu);
cond.wait(locker, [](){ return !q.empty();} ); // Unlock mu and wait to be notified
data = q.back();
q.pop_back();
locker.unlock();
std::cout << "t2 got a value from t1: " << data << std::endl;
}
}
int main() {
std::thread t1(function_1);
std::thread t2(function_2);
t1.join();
t2.join();
return ;
}
需要注意的几点:
在配合条件变量使用锁时,使用std::unique_lock比std::lock_guard合适,因为在wait内部有对锁的unlock和lock操作
使用细粒度锁,尽量减小锁的范围,在notify_one()
的时候,不需要处于互斥锁的保护范围内,所以在唤醒条件变量之前可以将锁unlock()
。
参考资料:
https://www.jianshu.com/p/c1dfa1d40f53
https://chenxiaowei.gitbook.io/c-concurrency-in-action-second-edition-2019/4.0-chinese/4.1-chinese
c++多线程并发学习笔记(2)的更多相关文章
- c++多线程并发学习笔记(0)
多进程并发:将应用程序分为多个独立的进程,它们在同一时刻运行.如图所示,独立的进程可以通过进程间常规的通信渠道传递讯息(信号.套接字..文件.管道等等). 优点:1.操作系统在进程间提供附附加的保护操 ...
- c++多线程并发学习笔记(1)
共享数据带来的问题:条件竞争 避免恶性条件竞争的方法: 1. 对数据结构采用某种保护机制,确保只有进行修改的线程才能看到修改时的中间状态.从其他访问线程的角度来看,修改不是已经完成了,就是还没开始. ...
- 多线程编程学习笔记——使用异步IO(一)
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- 多线程编程学习笔记——使用异步IO
接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...
- Java多线程技术学习笔记(二)
目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...
- 多线程编程学习笔记——async和await(一)
接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...
- 多线程编程学习笔记——async和await(二)
接上文 多线程编程学习笔记——async和await(一) 三. 对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...
- 多线程编程学习笔记——async和await(三)
接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五. 处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...
- 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端
接上文 多线程编程学习笔记——使用异步IO 二. 编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...
随机推荐
- 对TypeScript进行研究
1.npm install -g typescript 在编辑器,将下面的代码输入到greeter.ts文件里: function greeter(person) { return "Hel ...
- Devexpress MVC Gridview
1. 根据选中的KeyValue 来获取其他field的value // Gridview settings settings.CustomJSProperties = (s, e) => { ...
- 【Dart学习】-- Dart之消息循环机制[翻译]
概述 异步任务在Dart中随处可见,例如许多库的方法调用都会返回Future对象来实现异步处理,我们也可以注册Handler来响应一些事件,如:鼠标点击事件,I/O流结束和定时器到期. 这篇文章主要介 ...
- CCPC哈尔滨E题
一堆序列拼接起来,找出现次数大于n/2的数 假设一个数出现次数大于n/2 那么它减去其他数出现的次数一定非负: = c) { cnt += t[i]; } } } } //cout<<c& ...
- WebClient上传下载文件,小白篇
WebClient的上传文件一直报错,各种百度各种稀奇古怪的东西,终于百度到一篇小白学习篇 转自: https://www.cnblogs.com/cncc/p/5722231.html 使用C#We ...
- python中加入中文注释报错处理
python中加入中文注释,运行报错如下 解决方法: 在py文件的第一行加入 #coding:utf-8 即可
- js运行原理
https://www.youtube.com/watch?v=8aGhZQkoFbQ
- TiDB官方文档
TiDB官方文档: https://github.com/pingcap/docs-cn TiDB 整体架构 TiDB 集群主要包括三个核心组件:TiDB Server,PD Server 和 TiK ...
- java 解析上传的Excel文件
java poi解析上传的Excel文件 package com.zhl.push.Utils; /** * @Author TAO * @ClassName ExcelData * @Descrip ...
- jest 事件测试
概述 最近玩 Jest,测试 Vue 组件上的事件,有一些心得,记录下来供以后开发时参考,相信对其他人也有用. 事件测试 对于 Vue 组件上的事件,分为 2 种,一种是子组件 Emit 的事件,另一 ...