第十一节 std::atomic续谈、std::async深入谈
一、原子操作std::atomic续谈?
上一篇文章里,我们使用原子操作使得线程内的g_mycout自增++不会被其他线程打断,进而获取正确的结果:
#include<iostream>
#include<vector>
#include<thread>
#include<string>
#include<list>
#include<mutex>
#include <future>
using namespace std;
std::atomic<int> g_mycout(0);//封装了一个类型为int的对象,这是一个可以执行原子操作的整型变量
void mythread(){
for(int i=0;i<10000000;i++){
g_mycout++;
}
return;
}
int main(){
thread mytobj1(mythread);
thread mytobj2(mythread);
mytobj1.join();
mytobj2.join();
cout<<"程序执行完毕, result = "<<g_mycout<<endl;
return 0;
}
程序执行完毕, result = 20000000
如果将g_mycout++改为g_mycout=gmycout+1结果是否相同?
void mythread(){
for(int i=0;i<10000000;i++){
g_mycout=g_mycout+ 1;
//g_mycout++;
}
return;
}
程序执行完毕, result = 19897970
此时结果是错误的。一般的原子操作,针对++,–,+=,&=,|=,*=,-=是支持的,其他的可能不支持。如果使用时不确定是否支持,则需要自己像上面一样进行测试。
二、std::async深入谈
2.1 std::async参数详述,async用来创建一个异步任务
int mythread(){
cout<<"thread ID = "<<std::this_thread::get_id()<<"结束"<<endl;
return 1;
}
int main(){
cout<<"MAIN thread ID = "<<std::this_thread::get_id()<<endl;
std::future<int> result=std::async(mythread);
cout<<result.get()<<endl;
return 0;
}
之前使用过的参数,std::launch::deferred【延迟调用】,以及std::launch::async【强制创建一个线程】;
std::thread()如果系统资源紧张,可能创建线程会失败,那么执行std::thread()时整个线程会崩溃。
std::async()我们一般不叫创建线程,一般叫做创建一个异步任务;
std::async和std::thread明显的不同就是async有时候并不创建线程;
- 使用
std::launch::deferred来调用async
std::future<int> result=std::async(std::launch::deferred,mythread);
int main(){
cout<<"MAIN thread ID = "<<std::this_thread::get_id()<<endl;
std::future<int> result=std::async(std::launch::deferred,mythread);
cout<<result.get()<<endl;
return 0;
}
MAIN thread ID = 140737348093760
thread ID = 140737348093760结束
1
此时没有创建新线程。deferred延迟调用,并且不创建新线程,延迟到future对象调用get()或者wait()时才执行mythread();
2. 如果使用的是std::launch::async,强调这个异步任务在新线程上运行,则必须要创建出一个新线程。
std::future<int> result=std::async(std::launch::async,mythread);
MAIN thread ID = 140737348093760
thread ID = 140737348089600结束
1
- 同时使用
std::launch::deferred|std::launch::async
std::future<int> result=std::async(std::launch::async|std::launch::deferred,mythread);
MAIN thread ID = 140737348093760
thread ID = 140737348089600结束
1
看上去和std::launch::async差不多,其实不一样。这个 | 意味着调用async的行为可能是创建新线程并立即执行,也可能是不创建新线程并延迟调到调用future类的get()才执行。具有不确定性。
- 不带参数,只给std::async()一个入口函数名
std::future<int> result=std::async(mythread);
第九节中,有错误,当不带参数时,他说默认值是std::launch::async 是错误的,默认值应该是std::launch::deferred|std::launch::async ,换句话说系统会自行决定是异步(创建新线程)还是同步运行(不创建新线程)。那么如何自行决定?看下面
2.2 std::async与std::thread的区别
std::thread创建线程,如果系统资源紧张,创建线程失败。整个程序就会崩溃。std::thread创建线程的方式,如果想接受线程返回值,只能用全局变量。
std::async创建异步任务,可以创建新线程也可以不创建。并且async调用方法很容易拿到线程入口函数的返回值。
由于系统资源限制:
如果用std::thread创建的线程太多,再用std::thread可能会失败,系统报告异常崩溃。
如果用std::async,一般就不会报异常崩溃,因为系统资源紧张时无法创建新线程时,std::async这种不加额外参数的调用,就不会创建新线程,而是后续谁调用future类的get()或者wait(),谁就执行那个入口函数,即异步任务就运行在执行get()的语句的线程上。
如果强制std::async一定要创建新线程,那么就必须使用std::launch::async参数,代价就是系统资源紧张时可能崩溃。
经验:一个程序里,线程数量不宜超过100-200。
2.3 std::async的不确定性的解决
不加额外参数的std::async调用,让系统自行决定是否创建新线程,问题在于std::launch::deferred | std::launch::async 这种写法,这个异步任务到底有没有被推迟执行,还是创建了新线程。可以使用条件判断的方法来确定。
int mythread(){
//执行一些操作
cout<<"thread ID = "<<std::this_thread::get_id()<<"结束"<<endl;
return 1;
}
int main(){
cout<<"MAIN thread ID = "<<std::this_thread::get_id()<<endl;
std::future<int> result=std::async(std::launch::async|std::launch::deferred,mythread);
std::future_status status= result.wait_for(std::chrono::seconds(0));
if(status==std::future_status::deferred)
{
//线程被推迟执行了,系统资源紧张了,采用延迟调用
cout<<result.get()<<endl;
}
else if(status==std::future_status::ready){
//任务没有被推迟执行,创建新线程
//且线程执行成功并返回
cout<<result.get()<<endl;//获取线程返回值
}
else if(status==std::future_status::timeout){
//超时线程还没执行完(如果在线程入口函数里执行的时间较长)
cout<<"超时线程还没执行完"<<endl;
cout<<result.get()<<endl;
}
return 0;
}
本文详细探讨了C++中的原子操作std::atomic及其在多线程环境中的应用,通过示例展示了std::atomic在确保线程安全上的重要性。同时,深入介绍了std::async的异步任务创建,包括std::launch::deferred和std::launch::async的使用,以及如何根据系统资源状况决定任务执行方式。文章还提到了std::async的不确定性,并提供了通过检查future状态来确定任务执行情况的方法。
1552

被折叠的 条评论
为什么被折叠?



