Package_task 和 sync 之间的区别是什么

在使用 C + + 11的线程模型时,我注意到

std::packaged_task<int(int,int)> task([](int a, int b) { return a + b; });
auto f = task.get_future();
task(2,3);
std::cout << f.get() << '\n';

还有

auto f = std::async(std::launch::async,
[](int a, int b) { return a + b; }, 2, 3);
std::cout << f.get() << '\n';

似乎也是这么做的。我知道如果我用 std::launch::deferred运行 std::async可能会有很大的不同,但是在这种情况下有一个不同吗?

这两种方法之间的区别是什么,更重要的是,在什么用例中我应该使用一种方法而不是另一种方法?

38795 次浏览

实际上,您刚才给出的示例显示了如果使用一个相当长的函数,比如

//! sleeps for one second and returns 1
auto sleep = [](){
std::this_thread::sleep_for(std::chrono::seconds(1));
return 1;
};

打包任务

packaged_task不会自己启动,你必须调用它:

std::packaged_task<int()> task(sleep);


auto f = task.get_future();
task(); // invoke the function


// You have to wait until task returns. Since task calls sleep
// you will have to wait at least 1 second.
std::cout << "You can see this after 1 second\n";


// However, f.get() will be available, since task has already finished.
std::cout << f.get() << std::endl;

std::async

另一方面,std::asynclaunch::async将尝试在不同的线程中运行任务:

auto f = std::async(std::launch::async, sleep);
std::cout << "You can see this immediately!\n";


// However, the value of the future will be available after sleep has finished
// so f.get() can block up to 1 second.
std::cout << f.get() << "This will be shown after a second!\n";

缺点

但是在您尝试将 async用于所有事情之前,请记住返回的将来有一个特殊的共享状态,它要求 future::~future阻塞:

std::async(do_work1); // ~future blocks
std::async(do_work2); // ~future blocks


/* output: (assuming that do_work* log their progress)
do_work1() started;
do_work1() stopped;
do_work2() started;
do_work2() stopped;
*/

因此,如果你想要真正的异步,你需要保持返回的 future,或者如果你不关心结果,如果环境改变:

{
auto pizza = std::async(get_pizza);
/* ... */
if(need_to_go)
return;          // ~future will block
else
eat(pizza.get());
}

有关这方面的更多信息,请参见 Herb Sutter 的文章 ABC0和 ~future,它描述了这个问题,以及 Scott Meyer 的 ABC3中的 std::futures并不特别,它描述了这些见解。还要注意的是,这种行为 是在 C + + 14及以上语言中指定的,但也常常在 C + + 11中实现。

进一步的分歧

通过使用 std::async,您不能再在特定的线程上运行任务,在这里 std::packaged_task可以移动到其他线程。

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::thread myThread(std::move(task),2,3);


std::cout << f.get() << "\n";

另外,在调用 f.get()之前需要调用 packaged_task,否则程序将会冻结,因为未来永远不会准备好:

std::packaged_task<int(int,int)> task(...);
auto f = task.get_future();
std::cout << f.get() << "\n"; // oops!
task(2,3);

DR

如果您希望完成某些事情,并且不在乎它们什么时候完成,那么可以使用 std::async; 如果您希望将事情包装起来,以便将它们移动到其他线程或稍后调用它们,那么可以使用 std::packaged_task。或者,引用 克里斯蒂安的话:

最后,std::packaged_task只是实现 std::async的一个较低级别的特性(这就是为什么它可以比 std::async做得更多,如果与其他较低级别的东西一起使用,如 std::thread)。简单地说,std::packaged_task是连接到 std::futurestd::asyncstd::function,它包装并调用 std::packaged_task(可能在不同的线程中)。

”类模板 std: : pack _ task 包装任何可调用的目标 (函数、 lambda 表达式、 bind 表达式或其他函数 对象) ,以便可以异步调用它 引发的异常存储在可以访问的共享状态中 通过性传播疾病: : 未来的对象。”

”模板函数异步运行函数 f (可能在一个单独的线程中) ,并返回一个 std: : future 最终保存那个函数调用的结果。”

打包任务与异步

P > 打包任务包含一个任务 [function or function object]和将来/承诺对。当任务执行 return 语句时,它会导致 set_value(..)遵守 packaged_task的承诺。

给定未来、承诺和包任务,我们可以创建简单的任务,而不必太担心线程[线程只是我们用来运行任务的东西]。

然而,我们需要考虑要使用多少个线程,或者一个任务最好是在当前线程上运行,还是在另一个线程上运行,等等。这样的决策可以由一个名为 async()的线程启动程序来处理,它决定是创建一个新线程还是回收一个旧线程,或者只是在当前线程上运行任务。它会带来未来。

DR

std::packaged_task允许我们将 std::future“绑定”到某个 可召唤的,然后控制这个调用将在何时何地执行,而不需要将来的对象。

std::async启用第一个,但不启用第二个。也就是说,它允许我们获取某个可调用对象的未来,但是,如果没有那个未来对象,我们就无法控制它的执行。

实际例子

下面是一个实际的例子,这个问题可以用 std::packaged_task来解决,但是不能用 std::async来解决。

考虑您想要实现一个 线程池。它由固定数目的 工作螺纹共享队列组成。但共享什么队列呢?std::packaged_task在这里很适合。

template <typename T>
class ThreadPool {
public:
using task_type = std::packaged_task<T()>;


std::future<T> enqueue(task_type task) {
// could be passed by reference as well...
// ...or implemented with perfect forwarding
std::future<T> res = task.get_future();
{ std::lock_guard<std::mutex> lock(mutex_);
tasks_.push(std::move(task));
}
cv_.notify_one();
return res;
}


void worker() {
while (true) {  // supposed to be run forever for simplicity
task_type task;
{ std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this]{ return !this->tasks_.empty(); });
task = std::move(tasks_.top());
tasks_.pop();
}
task();
}
}
... // constructors, destructor,...
private:
std::vector<std::thread> workers_;
std::queue<task_type> tasks_;
std::mutex mutex_;
std::condition_variable cv_;
};

这种功能不能用 std::async实现。我们需要从 enqueue()返回一个 std::future。如果我们在那里调用 std::async(即使使用 延期策略)并返回 std::future,那么我们将没有选择如何在 worker()中执行可调用。请注意,您不能为相同的共享状态创建多个期货(期货是不可复制的)。