C++11 std::packaged_task的妙用
std::packaged_task简介
std::packaged_task是C++11开始加入到STL(Standard Template Libarary, 标准模板库)的并发编程工具,位于头文件 <future>中, 使用时只需要包含该头文件即可
使用场景
假设我们的可执行程序有如下要求:
某些第三方提供的API未使用加锁保护机制,但这些API调用却不允许在不同线程里并发的调用, 比如涉及到单端口通信的API以及操作UI控件的API, 同一时间段内仅能进行一次调用, 这种情况下, 就必须要保证该系列的API的调用必须在单线程中调用.
具体怎么做呢?
应对方案
方案1: 使用任务队列
开启独立线程, 将所有涉及到相关API调用的操作放入某个串行的任务队列中, 然后在开启的独立线程中循环调用
方案2: 使用事件循环
在某些流程中, 可能会使用到事件循环, 并提供了相应的事件触发接口将对应的事件加入到主事件循环队列中, 由事件响应者进行任务调用
方案分析
对于方案1, 我们的工作量相对比较少, 创建任务队列, 创建线程并循环执行队列中的任务
对于方案2, 我们的步骤相对繁琐一些, 针对该系列任务定制事件, 定义事件响应回调
综合分析
以上方案均能保证我们的API可以合理的放到某单个线程(主线程或新线程)中去, 但是一般情况下, 通过这种方式, 我们只能将任务放到单线程去执行, 而如果相获取结果的话, 在C++11之前还是相当麻烦的.不过自从C++11, 我们有了一个很厉害的神兵利器用来解决我们的这一需求:
发起异步任务, 等待执行结果
具体实现
-
将所有相关的API调用任务做一层通用的包装, 比如放到std::function<void()>中
-
使用现有的事件逻辑或者开启新的线程去执行对应包装的任务
-
使用std::packaged_task将任务和任务参数进行任务包装, 可以设置不同的返回值, 根据具体api的情况而定, 比如:
int do_something(int arg), 可以包装为std::packaged_task<int()>)
-
获取std::packaged_task的future, 用来获取包装起来的任务中的返回值
auto future = task.get_future();
- 将 包装好的task通过已有的事件触发接口或者自定义的推送到任务队列的接口将task放入std::function中推送到任务队列中
- 调用future.wait() 或者future.get()等方式来等待返回值, 如果不惜要返回值, 也可以直接wait()即可, 这样既保证了api的调用一定是在单线程, 并且发起调用的线程中还可以使用同步的方式等待结果
具体示例代码
#include <queue>
#include <future>
#include <functional>
#include <iostream>
#include <thread>
int main(int, char **)
{
int ticks = 0;
/*访问队列时的保护锁*/
std::mutex task_lock;
/*这是全局的任务队列*/
std::queue<std::function<void()>> task_list;
/*后台发起任务的调用*/
std::thread background([&]{
while(ticks < 100) {
auto task = std::make_shared<std::packaged_task<bool()>>(std::bind([=] (int ticks) -> bool {
std::cout << "[Main] execute task ticks: " << ticks << std::endl;
return true;
}, ticks));
auto future = task->get_future();
std::cout << "[Background] get task future" << std::endl;
{
std::lock_guard<std::mutex> lock(task_lock);
std::cout << "[Background] push task, ticks: " << ticks << std::endl;
task_list.push([=] {
if(task) (*task)();
});
}
std::this_thread::sleep_for(std::chrono::seconds(1));
ticks++;
}
});
/*主线程循环执行队列任务*/
std::queue<std::function<void()>> tasks;
while (ticks < 100) {
if(task_list.empty()) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
} else {
{
std::lock_guard<std::mutex> lock(task_lock);
tasks.swap(task_list);
}
while(!tasks.empty()) {
auto task = tasks.front();
if(task) {
task ();
}
tasks.pop();
}
}
}
background.join();
return 0;
}
当然, 这种情况下, 我们使用std::promise也是可以的, 比如
bool do_a_task_and_wait_result()
{
auto promise = std::make_shared<std::promise<bool>>();
auto future = promise->get_future();
some_async_func([=] {
/// do some work
if(some_codition) {
promise->set_value(true);
} else {
promise->set_value(false);
}
});
return future.get();
}