目录
在上篇:C++11多线程---async、future、package_task、promise_阿巴乾的博客-CSDN博客
已经介绍了future的一些成员函数,比如get。在这篇继续介绍future的一些其他成员函数,以及shared_future、atomic。
一、std::future的其他成员函数
//
// Created by yangjq on 22-7-8.
//
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread() {
cout << "mythread started !" << " thread_id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
cout<<"mythread end !" << " thread_id = " << std::this_thread::get_id() << endl;
return 5;
}
int main() {
cout << "main" << " thread_id = " << std::this_thread::get_id() << endl;
std::future<int> result = std::async(std::launch::deferred,mythread);
cout << "continue ..... " << endl;
//枚举类型
std::future_status status = result.wait_for(std::chrono::seconds(6));//等待1秒
if(status == std::future_status::timeout){
//线程超时,等待了一秒,但是线程要五秒
cout << "超时了,线程没执行完" << endl;
}
else if(status == std::future_status::ready){
cout << "线程成功执行完了" << endl;
cout << result.get() << endl;
}
else if(status == std::future_status::deferred){//延迟
//如果async的第一参数被设置为std::launch::deferred,则本条件成立
cout << "线程被延迟执行" << endl;
cout << result.get() << endl;
}
cout << "main end !" << endl;
return 0;
}
二、std::shared_future
在上篇讲到了get,但是对于结果result只能get一次,再次get会出错,这是因为get函数的设计是一个移动语义。如果对于多个地方需要得到返回值的话,get就不够用了,这时候就需要std::shared_future。
//
// Created by yangjq on 22-7-8.
//
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int mythread(int mypar) {
cout << "mythread" << " thread_id = " << std::this_thread::get_id() << endl;
std::chrono::milliseconds dura(5000);
std::this_thread::sleep_for(dura);
return 5;
}
void mythread2(std::shared_future<int> &tmpf){
cout << "mythread2" << " thread_id = " << std::this_thread::get_id() << endl;
auto result = tmpf.get();
cout << "mythread2 result = " << result <<endl;
return;
}
int main() {
cout << "main" << " thread_id = " << std::this_thread::get_id() << endl;
std::packaged_task<int(int)> mypt(mythread);//把函数mythread通过packaged_task包装起来
std::thread t1(std::ref(mypt), 1);
cout << "continue ..... " << endl;
t1.join();
std::shared_future<int> result_s(mypt.get_future());//result会变成空,值会到result_s里面去
std::thread t2(mythread2, std::ref(result_s));
t2.join();
cout << "main end !" << endl;
return 0;
}
三、原子操作std::atmic
互斥量:多线程编程中,保护共享数据。
问题:有两个线程,只对一个变量进行操作,一个进行读操作,一个进行写操作。程序结果会出现问题,这个问题主要在于线程与线程之间对一个变量的操作会导致冲突,语句无法正常执行。比如一个赋值语句可以拆分成三四句汇编语句,当另一个线程来对变量进行操作时,可能这个线程还未将赋值进行完毕。
以下面这段代码为例:
//
// Created by yangjq on 22-7-11.
//
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
#include <future>
using namespace std;
int g_mycont = 0;
void mythread(){
for(int i = 0; i < 1000000; i++){
g_mycont++;
}
return;
}
int main(){
thread mytobj1(mythread);
thread mytobj2(mythread);
mytobj1.join();
mytobj2.join();
cout << "两个线程执行完毕!g_mycont = " << g_mycont << endl;
}
这段代码从编写思路上来讲,应该是期待全局变量g_mycont输出为2000000,但是实际输出确每次都不到2000000,并且每次都不一样。因此,代码是不稳定的。如果想让代码稳定,想法当然是进行加锁。如下代码:
for(int i = 0; i < 1000000; i++){
g_my_mutex.lock();
g_mycont++;
g_my_mutex.unlock();
}
在循环进行加锁解锁,但是这种加锁解锁也会浪费很多时间,因此就出现了原子操作这种更好的方式来解决这个问题。原子操作可以理解为一种不需要用到互斥量加锁技术的多线程并发编程方式。原子操作是在多线程中不会被打断的程序执行片段,在效率上比互斥量更胜一筹。互斥量的加锁一般是针对一个代码段,而原子操作针对的一般都是一个变量,而不是一个代码段。
//
// Created by yangjq on 22-7-11.
//
#include <iostream>
#include <thread>
#include <vector>
#include <list>
#include <mutex>
#include <future>
using namespace std;
std::atomic<int> g_mycont;
void mythread(){
for(int i = 0; i < 1000000; i++){
g_mycont++;
}
return;
}
int main(){
g_mycont = 0;
thread mytobj1(mythread);
thread mytobj2(mythread);
mytobj1.join();
mytobj2.join();
cout << "两个线程执行完毕!g_mycont = " << g_mycont << endl;
}
以上代码是原子操作的用法范例。
在这个范例中使用++操作符,输出是没有任何问题的。但是像下面代码简单修改一下就会出错。
void mythread(){
for(int i = 0; i < 1000000; i++){
g_mycont = g_mycont + 1;
}
return;
}
按以上代码修改后运行结果不是2000000,主要原因还是atomic不支持这样的操作,一般atomic支持的操作有++,--,+=,-=,&=,|=,^=,其他的可能不支持。
总结:原子操作可以理解为在现实世界中,把原子理解为最小的物质组成单位(虽然还有更小的电子等等);在代码中,把一个赋值或其他语句作为不可分割的语句,只有两种状态,要么赋值完成,要么赋值失败。这样就不会出现多线程时,读写冲突的问题,并且效率会比使用互斥量更高。