C++11并发学习之四:线程同步(续)

2024-04-27 23:38

本文主要是介绍C++11并发学习之四:线程同步(续),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

C++11并发学习之四:线程同步(续)

 

 https://blog.csdn.net/caoshangpa/article/details/52878122

有时候,在第一个线程完成前,可能需要等待另一个线程执行完成。C++标准库提供了一些工具可用于这种同步操作,形式上表现为条件变量(condition variable)和期望(future)。

 

一.条件变量(condition variable)
C++标准库对条件变量有两套实现:std::condition_variable和std::condition_variable_any。这两个实现都包含在<condition_variable> 头文件的声明中。两者都需要与一个互斥量一起才能工作(互斥量是为了同步);前者仅限于与std::mutex一起工作,而后者可以和任何满足最低标准的互斥量一起工作,从而加上了_any的后缀。因为std::condition_variable_any更加通用,这就可能从体积、性能,以及系统资源的使用方面产生额外的开销,所以std::condition_variable一般作为首选的类型,当对灵活性有硬性要求时,我们才会去考std::condition_variable_any。

下例使用std::condition_variable去处理之前提到的情况——当有数据需要处理时,如何唤醒休眠中的线程对其进行处理。

 
  1. #include <thread>

  2. #include <iostream>

  3. #include <string>

  4. #include <chrono>

  5. #include <assert.h>

  6. #include <mutex>

  7. #include <condition_variable>

  8. #include <queue>

  9. int counter=0;

  10. std::mutex mtx;

  11. std::queue<int> dataQuene;

  12. std::condition_variable dataCondition;

  13. void func_preparation()

  14. {

  15. for (int i=0; i<10; ++i)

  16. {

  17. std::unique_lock<std::mutex> lck(mtx);

  18. ++counter;

  19. dataQuene.push(counter);

  20. dataCondition.notify_one();

  21. }

  22. }

  23.  
  24. void func_processing()

  25. {

  26. while (true)

  27. {

  28. std::unique_lock<std::mutex> lck(mtx);

  29. dataCondition.wait(lck,[]{return !dataQuene.empty();});

  30. int num=dataQuene.front();

  31. std::cout<<num<<std::endl;

  32. dataQuene.pop();

  33. lck.unlock();

  34. }

  35. }

  36.  
  37. int main()

  38. {

  39. std::thread workerThreadPreparation(func_preparation);

  40. workerThreadPreparation.detach();

  41. std::thread workerThreadProcessing(func_processing);

  42. workerThreadProcessing.detach();

  43.  
  44. system("pause");

  45. return 0;

  46. }

workerThreadPreparation线程将数据存储到队列中,然后调用std::condition_variable的notify_one()成员函数,对等待的线程(这里为workerThreadProcessing线程)进行通知。这里为workerThreadProcessing线程收到通知后,会调用std::condition_variable的成员函数wait(),判断队列是否为空,如果队列不为空,则取出数据并打印。

需要注意的是,wait的第二个参数使用了lambda表达式,关于lambda详见:C++11新特性之五:Lambda

运行效果如下图所示。


 

二.期望(future)

C++标准库模型将某种一次性事件称为“期望” (future)。当一个线程需要等待一个特定的一次性事件时,在某种程度上来说它就需要知道这个事件在未来的表现形式。之后,这个线程会周期性(较短的周期)的等待或检查,事件是否触发;在检查期间也会执行其他任务。另外,在等待任务期间它可以先执行另外一些任务,直到对应的任务触发,而后等待期望的状态会变为“就绪”(ready)。当事件发生时(并且期望状态为就绪),这个“期望”就不能被重置。在C++标准库中,有两种“期望”,使用两种类型模板实现,声明在头文件中: 唯一期望(unique futures)( std::future<> )和共享期望(shared futures)( std::shared_future<> )。这是仿照 std::unique_ptr 和 std::shared_ptr 。 std::future 的实例只能与一个指定事件相关联,而 std::shared_future 的实例就能关联多个事件。虽然,我希望用于线程间的通讯,但是“期望”对象本身并不提供同步访问。当多个线程需要访问一个独立“期望”对象时,他们必须使用互斥量或类似同步机制对访问进行保护。

假如有一个耗时的计算,可以启动一个新线程来执行这个计算,但是这就意味着必须关注如何传回计算的结果。因为std::thread并不提供直接接收返回值的机制,这里就需要std::async函数模板了。当任务的结果你不着急要时,你可以使用 std::async 启动一个异步任务。与 std::thread 对象等待运行方式的不同, std::async 会返回一个 std::future 对象,这个对象持有最终计算出来的结果。当你需要这个值时,你只需要调用这个对象的get()成员函数;并且直到“期望”状态为就绪的情况下,线程才会阻塞;之后,返回计算结果。

std::future 通常由某个Provider创建,你可以把Provider想象成一个异步任务的提供者,Provider在某个线程中设置共享状态的值,与该共享状态相关联的std::future对象调用 get(通常在另外一个线程中) 获取该值,如果共享状态的标志不为ready,则调用std::future::get会阻塞当前的调用者,直到Provider设置了共享状态的值(此时共享状态的标志变为 ready),std::future::get返回异步任务的值或异常(如果发生了异常)。
一个有效(valid)的std::future对象通常由以下三种Provider创建,并和某个共享状态相关联。Provider可以是函数或者类,他们分别是:
☆std::async 函数。
☆std::promise::get_future,get_future 为promise类的成员函数。
☆std::packaged_task::get_future,此时get_future为packaged_task的成员函数。

 
  1. #include <thread>

  2. #include <iostream>

  3. #include <string.h>

  4. #include <chrono>

  5. #include <assert.h>

  6. #include <future>

  7.  
  8. //求校验和

  9. int getCheckSum( char *data, int data_length )

  10. {

  11. int check_sum = 0;

  12. while ( --data_length >= 0 )

  13. {

  14. check_sum += *data++;

  15. }

  16. return check_sum;

  17. }

  18.  
  19. int time_consuming_task()

  20. {

  21. std::cout<<"worker thread ID:"<<std::this_thread::get_id()<<std::endl;

  22. char * str="can ge ge blog";

  23. return getCheckSum(str,strlen(str));

  24. }

  25.  
  26. void do_other_task()

  27. {

  28. std::cout<<"I am other task"<<std::endl;

  29. }

  30.  
  31. int main()

  32. {

  33. std::cout<<"main thread ID:"<<std::this_thread::get_id()<<std::endl;

  34. std::future<int> result=std::async(time_consuming_task);

  35. do_other_task();

  36. std::cout<<"The result is "<<result.get()<<std::endl;

  37. //防止窗口一闪而过

  38. system("pause");

  39. return 0;

  40. }

运行结果如下图所示。

虽然能得到正确结果,但是从线程ID可以看出time_consuming_task并未在新线程中运行。

现在来看看std::async的原型async(std::launch::async | std::launch::deferred, f, args...),第一个参数是线程的创建策略,有两种策略:
std::launch::async:在调用async就开始创建线程。
std::launch::deferred:延迟加载方式创建线程。调用async时不创建线程,直到调用了future的get或者wait时才创建线程。
第二个参数是线程函数,第三个参数是线程函数的参数。

因此只需将上述代码中的

std::future<int> result=std::async(time_consuming_task)

改为

std::future<int> result=std::async(std::launch::async,time_consuming_task)

运行结果如下所示。


此时,time_consuming_task在新线程中运行了。


三.承诺(promise)

promise对象可以保存某一类型T的值,该值可被future对象读取(可能在另外一个线程中),因此promise也提供了一种线程同步的手段。在promise对象构造时可以和一个共享状态(通常是std::future)相关联,并可以在相关联的共享状态(std::future)上保存一个类型为T的值。可以通过get_future来获取与该promise对象相关联的future对象,调用该函数之后,两个对象共享相同的共享状态(shared state)
☆promise对象是异步Provider,它可以在某一时刻设置共享状态的值。
☆future对象可以异步返回共享状态的值,或者在必要的情况下阻塞调用者并等待共享状态标志变为ready,然后才能获取共享状态的值。

 
  1. #include <thread>

  2. #include <iostream>

  3. #include <string.h>

  4. #include <chrono>

  5. #include <assert.h>

  6. #include <future>

  7.  
  8. int print_int(std::future<int>& fut)

  9. {

  10. std::cout<<"worker thread ID:"<<std::this_thread::get_id()<<std::endl;

  11. int x = fut.get();//获取共享状态的值.

  12. std::cout << "value: " << x << std::endl;//打印value:10

  13. }

  14.  
  15. int main()

  16. {

  17. std::cout<<"main thread ID:"<<std::this_thread::get_id()<<std::endl;

  18. std::promise<int> prom;//生成一个std::promise<int>对象

  19. std::future<int> fut = prom.get_future();//和future关联

  20. std::thread t(print_int, std::ref(fut)); //将future交给另外一个线程t

  21. prom.set_value(10);//设置共享状态的值,此处和线程t保持同步

  22. t.join();

  23. return 0;

  24. }



四.包装任务(packaged_task)

 

std::packaged_task包装一个可调用的对象,并且允许异步获取该可调用对象产生的结果。td::packaged_task将其包装的可调用对象的执行结果传递给一个std::future对象(该对象通常在另外一个线程中获取 std::packaged_task 任务的执行结果)。
td::packaged_task对象内部包含了两个最基本元素,一、被包装的任务(stored task),任务(task)是一个可调用的对象,如函数指针、成员函数指针或者函数对象,二、共享状态(shared state),用于保存任务的返回值,可以通过 std::future对象来达到异步访问共享状态的效果。
可以通过 std::packged_task::get_future来获取与共享状态相关联的 std::future对象。在调用该函数之后,两个对象共享相同的共享状态,具体解释如下:
☆std::packaged_task对象是异步 Provider,它在某一时刻通过调用被包装的任务来设置共享状态的值。
☆std::future对象是一个异步返回对象,通过它可以获得共享状态的值,当然在必要的时候需要等待共享状态标志变为ready。
std::packaged_task的共享状态的生命周期一直持续到最后一个与之相关联的对象被释放或者销毁为止。

 
  1. #include <thread>

  2. #include <iostream>

  3. #include <string.h>

  4. #include <chrono>

  5. #include <assert.h>

  6. #include <future>

  7.  
  8. int print_int(int from,int to)

  9. {

  10. std::cout<<"worker thread ID:"<<std::this_thread::get_id()<<std::endl;

  11. for(int i=from;i<to;i++)

  12. {

  13. std::cout << i << std::endl;

  14. std::this_thread::sleep_for(std::chrono::seconds(1));

  15. }

  16. std::cout << "Finished!" <<std::endl;

  17. return to - from;

  18. }

  19.  
  20. int main()

  21. {

  22. std::packaged_task<int(int,int)> task(print_int);//设置packaged_task

  23. std::future<int> fu = task.get_future();//获得与packaged_task共享状态相关联的future对象

  24. std::thread t(std::move(task), 0, 10);//创建一个新线程完成计数任务,std::move用于无条件的将其参数转换为右值

  25. int value = fu.get();//等待任务完成并获取结果

  26. std::cout << "The print_int lasted for " << value << " seconds"<<std::endl;

  27. t.join();

  28. return 0;

  29. }

这篇关于C++11并发学习之四:线程同步(续)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/941803

相关文章

关于C++中的虚拟继承的一些总结(虚拟继承,覆盖,派生,隐藏)

1.为什么要引入虚拟继承 虚拟继承是多重继承中特有的概念。虚拟基类是为解决多重继承而出现的。如:类D继承自类B1、B2,而类B1、B2都继承自类A,因此在类D中两次出现类A中的变量和函数。为了节省内存空间,可以将B1、B2对A的继承定义为虚拟继承,而A就成了虚拟基类。实现的代码如下: class A class B1:public virtual A; class B2:pu

C++对象布局及多态实现探索之内存布局(整理的很多链接)

本文通过观察对象的内存布局,跟踪函数调用的汇编代码。分析了C++对象内存的布局情况,虚函数的执行方式,以及虚继承,等等 文章链接:http://dev.yesky.com/254/2191254.shtml      论C/C++函数间动态内存的传递 (2005-07-30)   当你涉及到C/C++的核心编程的时候,你会无止境地与内存管理打交道。 文章链接:http://dev.yesky

51单片机学习记录———定时器

文章目录 前言一、定时器介绍二、STC89C52定时器资源三、定时器框图四、定时器模式五、定时器相关寄存器六、定时器练习 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出~ 提示:以下是本篇文章正文内容,下面案例可供参考 一、定时器介绍 定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。 定时器作用: 1.用于计数系统,可

C++的模板(八):子系统

平常所见的大部分模板代码,模板所传的参数类型,到了模板里面,或实例化为对象,或嵌入模板内部结构中,或在模板内又派生了子类。不管怎样,最终他们在模板内,直接或间接,都实例化成对象了。 但这不是唯一的用法。试想一下。如果在模板内限制调用参数类型的构造函数会发生什么?参数类的对象在模板内无法构造。他们只能从模板的成员函数传入。模板不保存这些对象或者只保存他们的指针。因为构造函数被分离,这些指针在模板外

问题:第一次世界大战的起止时间是 #其他#学习方法#微信

问题:第一次世界大战的起止时间是 A.1913 ~1918 年 B.1913 ~1918 年 C.1914 ~1918 年 D.1914 ~1919 年 参考答案如图所示

[word] word设置上标快捷键 #学习方法#其他#媒体

word设置上标快捷键 办公中,少不了使用word,这个是大家必备的软件,今天给大家分享word设置上标快捷键,希望在办公中能帮到您! 1、添加上标 在录入一些公式,或者是化学产品时,需要添加上标内容,按下快捷键Ctrl+shift++就能将需要的内容设置为上标符号。 word设置上标快捷键的方法就是以上内容了,需要的小伙伴都可以试一试呢!

AssetBundle学习笔记

AssetBundle是unity自定义的资源格式,通过调用引擎的资源打包接口对资源进行打包成.assetbundle格式的资源包。本文介绍了AssetBundle的生成,使用,加载,卸载以及Unity资源更新的一个基本步骤。 目录 1.定义: 2.AssetBundle的生成: 1)设置AssetBundle包的属性——通过编辑器界面 补充:分组策略 2)调用引擎接口API

C++工程编译链接错误汇总VisualStudio

目录 一些小的知识点 make工具 可以使用windows下的事件查看器崩溃的地方 dumpbin工具查看dll是32位还是64位的 _MSC_VER .cc 和.cpp 【VC++目录中的包含目录】 vs 【C/C++常规中的附加包含目录】——头文件所在目录如何怎么添加,添加了以后搜索头文件就会到这些个路径下搜索了 include<> 和 include"" WinMain 和

Javascript高级程序设计(第四版)--学习记录之变量、内存

原始值与引用值 原始值:简单的数据即基础数据类型,按值访问。 引用值:由多个值构成的对象即复杂数据类型,按引用访问。 动态属性 对于引用值而言,可以随时添加、修改和删除其属性和方法。 let person = new Object();person.name = 'Jason';person.age = 42;console.log(person.name,person.age);//'J

大学湖北中医药大学法医学试题及答案,分享几个实用搜题和学习工具 #微信#学习方法#职场发展

今天分享拥有拍照搜题、文字搜题、语音搜题、多重搜题等搜题模式,可以快速查找问题解析,加深对题目答案的理解。 1.快练题 这是一个网站 找题的网站海量题库,在线搜题,快速刷题~为您提供百万优质题库,直接搜索题库名称,支持多种刷题模式:顺序练习、语音听题、本地搜题、顺序阅读、模拟考试、组卷考试、赶快下载吧! 2.彩虹搜题 这是个老公众号了 支持手写输入,截图搜题,详细步骤,解题必备