C++异步操作三种方式的区别
C++异步操作三种方式的区别
C++11提供了三种异步操作方式:std::async, std::packaged_task和std::promise。他们都定义在头文件<future>下,使用时都可以得到一个std::future对象,用于和异步操作进行通信。
这三者的区别:
- std::async最简洁,返回值就是future对象,任务可以在调用std::async结束后立即执行,也可以延后执行,使用future来控制
- std::packaged_task把future对象的创建和任务的执行分割开来,提升了自由度,任务可以放到指定线程中去运行
- std::promise灵活性最高,std::async和std::packaged_task都必须等到任务执行完毕后才能在future对象里拿到返回值,std::promise则可以在任务执行的任意时候都能拿到返回值,不必等到任务执行结束。
一 std::async
cppreference上定义如下,
The function template async runs the function f asynchronously (potentially in a separate thread which might be a part of a thread pool) and returns a std::future that will eventually hold the result of that function call.
这个potentially用的妙,std::async执行的任务可以运行在单独线程里,也可以不运行在单独线程里,如果是立即执行则会开一个线程去执行,如果是延后执行,则会在调用任务的线程里阻塞执行。
1. 立即执行
#include <future>
#include <iostream>
bool myfunc(int data1, int data2)
{
std::cout << "myfunc id: " << std::this_thread::get_id() << "\n";
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::cout << "main id: " << std::this_thread::get_id() << "\n";
std::future<bool> fu = std::async(myfunc, 100, 200); // 100和200是myfunc需要的参数
// 阻塞等待
fu.get();
return 0;
}
编译运行后,结果如下,
可以看出线程id不同,说明是单独开了一个线程去执行的。
2. 延后执行
如果传给std::async的第一个参数是std::launch::defered,那么任务就可以延后执行,什么时候执行呢?当调用future的get()方法时就会执行,但是不会开线程去执行。
#include <future>
#include <iostream>
bool myfunc(int data1, int data2)
{
std::cout << "myfunc id: " << std::this_thread::get_id() << "\n";
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::cout << "main id: " << std::this_thread::get_id() << "\n";
std::future<bool> fu = std::async(std::launch::deferred, myfunc, 100, 200); // 100和200是myfunc需要的参数
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
// 阻塞等待
fu.get();
return 0;
}
编译运行后,结果如下,
可以看出2个id值是一样的,说明是在同一个线程,不过这样的话感觉就不是异步执行了…
3. future对象的析构问题
如果是立即执行,那么还需要注意future对象的析构问题。
如果要析构std::async返回的future对象,那么future对象的析构函数会阻塞当前线程,直到任务执行结束。
#include <future>
#include <iostream>
bool myfunc(int data1, int data2)
{
std::cout << "myfunc id: " << std::this_thread::get_id() << "\n";
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::cout << "main id: " << std::this_thread::get_id() << "\n";
{ // 作用域开始
std::future<bool> fu = std::async(myfunc, 100, 200); // 100和200是myfunc需要的参数
} // 作用域结束
return 0;
}
这里使用花括号来定义一个作用域,这样当fu出了作用域后会被释放,就会去调用析构函数,但是myfunc里有个延时1秒的等待,这样fu就会阻塞main函数所在的线程,一直到myfunc执行结束。
二 std::packaged_task
相比于std::async,std::packaged_task自由度更高,而且也没有future对象析构阻塞的问题。
cppreference上定义如下,
The class template std::packaged_task wraps any Callable target (function, lambda expression, bind expression, or another function object) so that it can be invoked asynchronously. Its return value or exception thrown is stored in a shared state which can be accessed through std::future objects.
分割了future对象的创建和任务的执行,执行任务可以显示地指定某个线程去做,不像std::async是背后做的。
例子如下,
#include <future>
#include <iostream>
bool myfunc(int data1, int data2)
{
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::packaged_task<bool(int, int)> task(myfunc);
std::future<bool> fu = task.get_future();
// 必须要使用std::move把task转成右值
std::thread t(std::move(task), 100, 200); // 100和200是myfunc需要的参数
// 调用get进行阻塞等待
std::cout << fu.get() << "\n";
t.join();
return 0;
}
因为packaged_task是个wrapper,所以其类型就是被打包的类型。例子中myfunc的返回类型是bool,参数是2个int,那么packaged_task的类型就是bool(int, int)
。然后调用get_future()方法得到future对象,类型是被打包的对象的返回值。
三 std::promise
相比于前2个,std::promise不需要等到任务结束就可以拿到future对象里的值,而且自由度最高,可以根据需要来决定返回值类型,不必是任务结束时的返回值。
另外,std::promise只是个异步结果的提供者,不像前2者可以传递任务进来。
cppreference上定义如下,
The class template std::promise provides a facility to store a value or an exception that is later acquired asynchronously via a std::future object created by the std::promise object. Note that the std::promise object is meant to be used only once.
Each promise is associated with a shared state, which contains some state information and a result which may be not yet evaluated, evaluated to a value (possibly void) or evaluated to an exception.
简单例子
下面是个例子,
#include <future>
#include <iostream>
#include <string>
bool myfunc(std::promise<std::string> pro, int data1, int data2)
{
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
pro.set_value("hello");
// 等待2秒
std::this_thread::sleep_for(std::chrono::seconds(2));
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::promise<std::string> pro;
std::future<std::string> fu = pro.get_future();
// 必须使用std::move把pro转成右值,之后pro就不能再被使用
std::thread t(myfunc, std::move(pro), 100, 200);
std::cout << fu.get() << "\n";
t.join();
return 0;
}
例子中不关心myfunc的返回值,而是自定义的一个目标,类型是std::string。另外,需要把pro传给myfunc,这样myfunc的参数就需要修改下,增加std::promise类型的参数。
在myfunc中等待1s后调用std::promise的set_value()方法设置值(注意值类型要和std::promise参数的模板类型一样,这里是std::string),然后在main线程中,fu.get()就可以拿到这个值了,此时myfunc还没结束运行。
void类型
std::promise的模板类型也可以是void,这样std::promise就只起到一个通知作用,不需要关心返回值类型。下面是个例子,
#include <future>
#include <iostream>
#include <string>
bool myfunc(std::promise<void> pro, int data1, int data2)
{
// 等待1秒
std::this_thread::sleep_for(std::chrono::seconds(1));
pro.set_value();
// 等待2秒
std::this_thread::sleep_for(std::chrono::seconds(2));
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::promise<void> pro;
std::future<void> fu = pro.get_future();
std::thread t(myfunc, std::move(pro), 100, 200);
fu.get();
std::cout << "XXX\n";
t.join();
return 0;
}
例子中,std::promise和std::future都是void类型,在myfunc中等待1s后调用set_value()但是不需要传值进去,这样在主线程中fu.get()的阻塞就会打开,代码继续往后执行。
而且不能用std::cout去输出fu.get()了。
互换位置
有时可能想用主线程去控制其它线程,那么可以把std::promise和std::future所在的位置互换一下,例子如下,
#include <future>
#include <iostream>
#include <string>
bool myfunc(std::future<void> fu, int data1, int data2)
{
fu.get();
std::cout << "DDD\n";
if (data1+data2 > 100)
{
return true;
}
else
{
return false;
}
}
int main(void)
{
std::promise<void> pro;
std::future<void> fu = pro.get_future();
std::thread t(myfunc, std::move(fu), 100, 200);
// 等待1s
std::this_thread::sleep_for(std::chrono::seconds(1));
// 进行通知
pro.set_value();
t.join();
return 0;
}
这里把std::promise留在了main函数中,std::future传给了myfunc,然后myfunc会等待main的指令。
有点类似于条件变量…
四 总结
本文讲述了std::async, std::packaged_task和std::promise的使用方式以及差异,可以根据需要进行选择使用。
更多推荐
所有评论(0)