当前位置: 首页 > news >正文

《C++并发编程实战》精读总结:第四章 并发操作的同步

之前我们实现的是对公共数据的保护,但是线程之间几乎没有其他任何的交互操作。像“某线程需要等待另一线程完成后,才能开始执行”这种行为,我们通常称为线程的同步。本章主要是来展示线程是如何进行同步(交互)的。

1. 条件等待——condition_variable

假设有两个线程,线程1准备数据并发出,线程2在线程1准备好数据发出之后,拿到数据再进行一定的操作。这里就符合线程2必须等待线程1将数据发出之后才能进行操作的逻辑。借助condition_variable可以实现这种有条件的等待。线程1在准备好所有之后,调用condition_variable的nofity_one或者notify_all函数。线程2则调用condition_variable的wait函数。代码如下:

mutex mutex1;
queue<Data> que; // 公共数据,需要被锁住的
condition_variable cond;
void thread1(){
    Data data1;
    // 一旦线程1准备好数据data1,就锁住互斥
    lock_guard<mutex> lk(mutex1);
    // 将数据压入公共队列que
    que.push(data1);
    // 完成压入后,调用cond的notify_one()函数,通知线程2
    cond.notify_one();
    // cond.notify_all();
}

void thread2(){
    // 线程2在等待期间,要解锁互斥,在等待结束后,要重新加锁,只有unique_lock能提供这种灵活性
    unique_lock<mutex> lk(mutex1);
    // 线程2在cond的wait函数上,传入锁lk和一个lambda函数
    // 如果函数成立,wait返回,否则wait解锁lk,并阻塞当前线程2
    cond.wait(lk,[]{return ! que.empty();});
    Data data2 = que.front();
    que.pop();
    lk.unlock(); // 数据2就绪,没必要继续锁住互斥,可以释放
    process(data2);
}

其中,如果有多个线程等待,就使用notify_all()函数,本例只有一个线程等待,所以thread1中用nofity_one()即可。因为有共享的队列,所以在执行过程中,需要用到互斥和加锁。thread2中的wait函数,一个参数是互斥锁,第二个是一个判断函数。如果函数成立,那么进行thread2中的后续操作,如果函数不成立,wait会释放互斥,让整个thread2进入阻塞或等待。为了灵活的加锁和释放锁,thread2中使用unique_lock来对互斥加锁。

2. 等待一次性时间发生——future

2.1 从后台任务返回值——async

如果需要等待的事件只会发生一次,可以将这个事件用future来表示。一旦等待的这个事件发生,future就会进入就绪状态,无法重置。简单的代码如下:

#include <future>
#include <iostream>
using namespace std;

int FindAnswer(){}
int main(){
    future<int> answer = async(launch::async,FindAnswer); // FindAnswer被异步执行,是否并发取决于编译器
    do_other_stuff();
    cout << "answer is: " << answer.get(); // get会阻塞当前线程,等待FindAnswer执行完成
}

需要注意的是,主线程是可以在FindAnswer异步执行的时候,去做自己的事儿(do_other_stuff),但是当调用future的get函数时,还是会阻塞当前主线程,等待FindAnwser执行完,或者已经执行完的话就直接返回结果另外async作为典型的异步执行的方式,第一个参数还以选择launch::deferred,这样就是不开启另外一个线程,等待主线程执行到调用future的get函数,再开始执行FindAnswer。

2.2 隐藏函数细节,打包future——packaged_task<>

将任务函数和future打包到一起,就是packaged_task。打包到一起的好处时可以隐藏函数的细节,直接通过管理task来管理不同的线程任务,这在多线程任务中比较方便。具体代码如下:

mutex m;
deque<packaged_task<void()>> tasks; // 共享的队列
template<typename Func>
void post_task(Func f){
    packaged_task<void()> task(f); // 根据传入的函数创建任务,将任务包装在task里
    // future<void> res=task.get_future(); // 调用成员函数取得对应的future
    lock_guard<mutex> lk(m);
    tasks.push_back(move(task));
}

void execute_task(){
    packaged_task<void()> task;
    lock_guard<mutex> lk(m);
    task=move(tasks.front());
    tasks.pop_front();

    task(); //相当于执行了future.get()
}

相关文章:

  • Webpack 和 Vite 的主要区别
  • JVM 的不同组成部分分别有什么作用?
  • Navicat SqlServer 设置自增主键
  • 堆的应用(堆排序TopK问题)
  • mysql存储引擎、索引、事务---java
  • 【工具】C#游戏防沉迷小工具
  • docker桌面版启动redis,解决无法连接
  • 大数据技术之Spark优化
  • SaaS 系统业务逻辑处理方式探讨
  • 【PyCharm2024】一些好用的小功能
  • 大模型学习笔记------Llama 3模型架构之旋转编码(RoPE)
  • Redis 源码分析-内部数据结构 quicklist
  • peach模糊测试工具中,stateModel模块中的type的作用
  • DeepLabv3+改进10:在主干网络中添加LSKBlock|动态调整其大型空间感受野,助力小目标识别
  • Ai文章改写出来的文章,怎么过Ai检测?控制指令,测试的一点心得,彻底疯了!
  • 14.使用各种读写包操作 Excel 文件:辅助模块
  • 蓝桥杯Python赛道备赛——Day3:排序算法(二)(归并排序、堆排序、桶排序)
  • 【解锁机器学习:探寻数学基石】
  • Springboot项目修改端口
  • kali之msf
  • 中国代表:美国才是南海安全稳定的最大威胁
  • 总数再更新!我国新增三项全球重要农业文化遗产
  • 王毅同丹麦外交大臣会谈,表示在格陵兰问题充分尊重丹麦主权和领土完整
  • 特朗普与普京就俄乌问题通话
  • 黄仁勋:新一代计算平台GB300三季度上市,AI计算能力每十年提升100万倍
  • 国家主席习近平任免驻外大使