等待一个时间或其他条件

在一个线程等待完成任务时,会有很多选择:

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_variablestd::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)的更多相关文章

  1. c++多线程并发学习笔记(0)

    多进程并发:将应用程序分为多个独立的进程,它们在同一时刻运行.如图所示,独立的进程可以通过进程间常规的通信渠道传递讯息(信号.套接字..文件.管道等等). 优点:1.操作系统在进程间提供附附加的保护操 ...

  2. c++多线程并发学习笔记(1)

    共享数据带来的问题:条件竞争 避免恶性条件竞争的方法: 1. 对数据结构采用某种保护机制,确保只有进行修改的线程才能看到修改时的中间状态.从其他访问线程的角度来看,修改不是已经完成了,就是还没开始. ...

  3. 多线程编程学习笔记——使用异步IO(一)

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  4. 多线程编程学习笔记——使用异步IO

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  5. Java多线程技术学习笔记(二)

    目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...

  6. 多线程编程学习笔记——async和await(一)

    接上文 多线程编程学习笔记——任务并行库(一) 接上文 多线程编程学习笔记——任务并行库(二) 接上文 多线程编程学习笔记——任务并行库(三) 接上文 多线程编程学习笔记——任务并行库(四) 通过前面 ...

  7. 多线程编程学习笔记——async和await(二)

    接上文 多线程编程学习笔记——async和await(一) 三.   对连续的异步任务使用await操作符 本示例学习如何阅读有多个await方法方法时,程序的实际流程是怎么样的,理解await的异步 ...

  8. 多线程编程学习笔记——async和await(三)

    接上文 多线程编程学习笔记——async和await(一) 接上文 多线程编程学习笔记——async和await(二) 五.   处理异步操作中的异常 本示例学习如何在异步函数中处理异常,学习如何对多 ...

  9. 多线程编程学习笔记——编写一个异步的HTTP服务器和客户端

    接上文 多线程编程学习笔记——使用异步IO 二.   编写一个异步的HTTP服务器和客户端 本节展示了如何编写一个简单的异步HTTP服务器. 1.程序代码如下. using System; using ...

随机推荐

  1. 【leetcode】LCP 1. Guess Numbers

    题目如下: 小A 和 小B 在玩猜数字.小B 每次从 1, 2, 3 中随机选择一个,小A 每次也从 1, 2, 3 中选择一个猜.他们一共进行三次这个游戏,请返回 小A 猜对了几次? 输入的gues ...

  2. postman—随机数和Monitors

    postman做重复测试时,随机数就有很大的作用,不用每次都输入 在postman的Params中,输入一个左大括号,会显示三种随机数: 也可以在body中设置 随机数如下: {{$guid}}:添加 ...

  3. 【织梦手机端仿站】和PC一个后台

    卸载插件,织梦默认带手机站,无需复杂使用插件以后不方便升级.织梦2015年6月8日更新后,就添加了很多针对手机移动端的设计,最大的设计就是添加了生成二维码的织梦标签和织梦手机模板功能,织梦更新后,默认 ...

  4. CSS设置背景色

    最好用background-color:rgba(37,77,113,1); 因为用opacity会让所有自标签都改变

  5. keras学习笔记-bili莫烦

    一.keras的backend设置 有两种方式: 1.修改JSON配置文件 修改~/.keras/keras.json文件内容为: { "iamge_dim_ordering":& ...

  6. HDU 6651 Final Exam

    hdu题面 Time limit 2000 ms Memory limit 524288 kB OS Windows 吐槽 比赛时候晕死了-- 解题思路 先留坑 公式法 https://blog.cs ...

  7. 【BZOJ2022】Pku1837 Balance

    Description Gigel has a strange "balance" and he wants to poise it. Actually, the device i ...

  8. Linux系统安装时分区的介绍

    一般来说,在linux系统中都有最少两个挂载点,分别是/ (根目录)及 swap(交换分区),其中,/ 是必须的: 建议挂载的几大目录: /-------根目录,唯一必须挂载的目录.不要有任何的犹豫, ...

  9. 用JavaServiceWrapper将JAVA程序发布成Windows服务

    怎么把jar文件做成系统服务,比较多的解决方案是使用 wrapper-windows 这个软件包.这个软件包的强大之处是能把jre环境也给打进去,这个服务可以正常运行在根本没有jre环境即就没有安装J ...

  10. Python爬取中文页面的时候出现的乱码问题(续)

    我在上一篇博客中说明了在爬取数据的时候,把数据写入到文件的乱码问题 在这一篇里面我做一个总结: 1.首先应该看一个案例 我把数据写在.py文件中: #coding:utf-8 s = 'hehe测试中 ...