等待一个时间或其他条件

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

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. 【NOIP2016提高A组模拟9.9】爬山

    题目 国家一级爬山运动员h10今天获得了一张有着密密麻麻标记的地图,在好奇心的驱使下,他又踏上了去爬山的路. 对于爬山,h10有一个原则,那就是不走回头路,于是他把地图上的所有边都标记成了有向边.他决 ...

  2. 获取当前国家与ip地址

    JS获取当前国家示例: <script src="http://int.dpool.sina.com.cn/iplookup/iplookup.php?format=js"& ...

  3. JAVA支持HTTP断点续传

    第一点:Java代码实现文件上传 FormFile file = manform.getFile(); String newfileName = null; String newpathname =  ...

  4. BZOJ 3545: [ONTAK2010]Peaks 启发式合并 + 离线 + Splay

    Description 在Bytemountains有N座山峰,每座山峰有他的高度h_i.有些山峰之间有双向道路相连,共M条路径,每条路径有一个困难值,这个值越大表示越难走,现在有Q组询问,每组询问询 ...

  5. win7如何设置以管理员身份运行

    一.对所有程序以管理员身份运行 1.右键单击桌面“计算机”,选择“管理” 2.在页面左侧,依此打开“计算机管理(本地)→ 系统工具→本地用户和组→用户”,在右侧找到“Administrator”,双击 ...

  6. Logstash的filter插件介绍

    一 官网说明 过滤器插件对事件执行中介处理.通常根据事件的特征有条件地应用过滤器. 以下过滤器插件在下面可用. Plugin Description Github repository aggrega ...

  7. Spring Cloud Stream教程(三)持续发布 - 订阅支持

    应用之间的通信遵循发布订阅模式,其中通过共享主题广播数据.这可以在下图中看到,它显示了一组交互式的Spring Cloud Stream应用程序的典型部署. 图6. Spring Cloud Stre ...

  8. Java连接Oracle数据库常用方法

    JDBC的六大步骤: 注册驱动 获取连接 获取执行sql语句对象 执行sql语句 处理结果集 关闭资源 oracle URL: jdbc:oracle:thin:@localhost:1521:SID ...

  9. 深入浅说服务如何以Jar包的方式发布

    序言 笔者前段时间在使用自研框架NF( 传送门 )开发一个自动模板生成工具之后,想将他发布到Linux下,之前一直使用IDE直接run as运行,在遇到发布的时候考虑过发布为war或者jar,在一番抉 ...

  10. Vue左滑组件slider的实现

    本文链接:https://blog.csdn.net/latency_cheng/article/details/82983000 slider组件与swiper组件不同,slider滑动时并不翻页, ...