【问题标题】:When is it a good idea to use std::promise over the other std::thread mechanisms?什么时候最好使用 std::promise 而不是其他 std::thread 机制?
【发布时间】:2012-12-26 08:41:33
【问题描述】:

我正在尝试建立一些启发式方法来帮助我决定要使用的合适的 std::thread 类。

据我了解,从最高级别(使用最简单,但最不灵活)到最低级别,我们有:

  1. std::async with/std::future (std::shared_future)(当您想在一次性一次性生产者线程异步上执行时)
  2. std::packaged_task(当你想分配生产者,但推迟调用线程)
  3. std::promise (???)

我认为我对何时使用前两个有一定的了解,但我仍不清楚std::promise

std::futurestd::async 调用一起,有效地将产生回调/仿函数/lambda 转换为异步调用(根据定义,它立即返回)。单个消费者可以调用 std::future::get()(一个阻塞调用)来获取其结果。

std::shared_future 只是一个允许多个消费者的版本。

如果您想将 std::future 值与生产者回调绑定,但希望将实际调用推迟到稍后时间(当您将任务关联到生成线程时),@987654331 @是正确的选择。但是现在,由于std::package_task 对应的std::future 在一般情况下可以被多线程访问,我们可能不得不小心使用std::mutex。请注意,使用std::async,在第一种情况下,我们不必担心锁定。

阅读some interesting links on promise,我想我了解它的机制以及如何设置它们,但我的问题是,你什么时候会选择使用 Promise 而不是其他三个?

我正在寻找更多应用程序级别的答案,例如经验法则(在上面的 3. 中填写 ???),而不是链接中的答案(例如使用 std: :promise to implement some library mechanism),所以我可以更容易地向std::thread的初学者解释如何选择合适的类。

换句话说,如果有一个有用的例子来说明我可以用 std::promise 做什么,而 不能 用其他机制来做,那就太好了。

回答

std::future 是一头奇怪的野兽:一般情况下,你不能直接修改它的值。

三个可以修改其值的生产者是:

  1. std::async 通过异步回调,这将返回一个 std::future 实例。
  2. std::packaged_task,当传递给线程时,将调用其回调,从而更新与该std::packaged_task 关联的std::future 实例。这种机制允许早期绑定生产者,但稍后调用。
  3. std::promise,它允许通过其set_value() 调用修改其关联的std::future。通过这种对std::future 变异的直接控制,如果有多个生产者,我们必须确保设计是线程安全的(根据需要使用std::mutex)。

我认为SethCarnegie's answer

一种简单的思考方式是,您可以通过以下方式设定未来 返回一个值或使用一个承诺。 future 没有设置方法; 该功能由 Promise 提供。

有助于阐明何时使用承诺。但我们必须记住,std::mutex 可能是必要的,因为根据使用情况,promise 可能可以从不同的线程访问。

另外,David's Rodriguez's answer 也很棒:

通信通道的消费者端将使用 std::future 消费来自共享状态的数据,而生产者线程 将使用 std::promise 写入共享状态。

但作为一种替代方案,为什么不简单地在 stl 结果容器上使用std::mutex,并使用一个线程或生产者线程池对容器进行操作?使用std::promise,除了一些额外的可读性与结果的stl容器之外,还给我买了什么?

std::promise 版本的控制似乎更好:

  1. wait() 将阻塞给定的未来,直到产生结果
  2. 如果只有一个生产者线程,则不需要互斥锁

以下 google 测试通过了 helgrind 和 drd,确认使用单个生产者,并且使用 wait(),不需要互斥锁。

测试

static unsigned MapFunc( std::string const& str ) 
{ 
    if ( str=="one" ) return 1u; 
    if ( str=="two" ) return 2u; 
    return 0u;
}

TEST( Test_future, Try_promise )
{
    typedef std::map<std::string,std::promise<unsigned>>  MAP; 
    MAP          my_map;

    std::future<unsigned> f1 = my_map["one"].get_future();
    std::future<unsigned> f2 = my_map["two"].get_future();

    std::thread{ 
        [ ]( MAP& m )
        { 
            m["one"].set_value( MapFunc( "one" ));
            m["two"].set_value( MapFunc( "two" ));
        }, 
      std::ref( my_map ) 
    }.detach();

    f1.wait();
    f2.wait();

    EXPECT_EQ( 1u, f1.get() );
    EXPECT_EQ( 2u, f2.get() );
}

【问题讨论】:

  • std::promisestd::futurestd::thread配合使用; promise 由执行线程给出,future 由启动线程获取。
  • @SethCarnegie:我会留意这里的答案:P
  • 当然,std::promise 不需要互斥体来为std::future 提供结果,这就是重点,这就是它存在的原因,以替换显式与互斥锁的手动同步。同样,不要使用互斥锁来防止对std::future 的多次访问,而是使用std::shared_future
  • @JonathanWakely +1 thx 提示
  • 问题里怎么会有答案?

标签: c++ c++11 stdthread


【解决方案1】:

你不选择使用promise代替其他人,你使用promise实现future 与其他人。 cppreference.com 的代码示例给出了使用所有四个的示例:

#include <iostream>
#include <future>
#include <thread>
 
int main()
{
    // future from a packaged_task
    std::packaged_task<int()> task([](){ return 7; }); // wrap the function
    std::future<int> f1 = task.get_future();  // get a future
    std::thread(std::move(task)).detach(); // launch on a thread
 
    // future from an async()
    std::future<int> f2 = std::async(std::launch::async, [](){ return 8; });
 
    // future from a promise
    std::promise<int> p;
    std::future<int> f3 = p.get_future();
    std::thread( [](std::promise<int>& p){ p.set_value(9); }, 
                 std::ref(p) ).detach();
 
    std::cout << "Waiting...";
    f1.wait();
    f2.wait();
    f3.wait();
    std::cout << "Done!\nResults are: "
              << f1.get() << ' ' << f2.get() << ' ' << f3.get() << '\n';
}

打印

等待...完成!

结果是:7 8 9

期货与所有三个线程一起使用以获取它们的结果,promise 与第三个线程一起使用以通过返回值以外的方式实现future。此外,单个线程可以通过promises 实现多个具有不同值的futures,这是它​​无法做到的。

一种简单的想法是,您可以通过返回值或使用promise 来设置futurefuture 没有 set 方法;该功能由promise 提供。你根据情况选择你需要的。

【讨论】:

  • 是的,我明白这一切(如何使用承诺及其与其他类的关系),但是何时/为什么要使用它而不是其他更易于使用的机制?也就是说,在3中填写(???)。
  • @kfmfe04 a promise 可用于实现 future 而无需从线程返回值(从而停止其执行)。
  • @kfmfe04:承诺是更简单的事情。特别是std::packaged_task 可以从promise 中实现。您从未来的共享状态中读取一个值,并使用承诺将其写入该共享状态。
  • @kfmfe04 即使使用packaged_task,也只能返回一个值。例如,使用promise,您可以捕获 5 个promises,并使用它们将5 值与外界通信。
  • 这是一个有点旧的线程,但我认为有一个很好的例子promise 远好于packaged_task。假设你的工作线程做了一些任务并且有一些东西要返回,但是之后有很多 CLEAN UP 任务。我认为在这种情况下最好设置一个promise,它可能会解锁正在等待这个future的主线程,同时对工作线程进行清理。
【解决方案2】:

当你有两个级别的异步时,你需要使用一个 Promise。 例如:

void fun()
{
std::promise<int> p;
std::future<int> f = p.get_future();
std::future<void> f2;
auto f3 = std::async([&]{
   // Do some other computation
   f2 = std::async([&]{ p.set_value(42);});
   // Do some other work
});
   // Do some other work
   // Now wait for the result of async work;
std::cout << f.get();
   // Do some other work again 
// Wait for async threads to complete
f3.wait();
f2.wait();
}

【讨论】:

  • @SethCarnegie 如果你所做的只是等待值,你为什么要那样使用异步。让我编辑我的示例以使其清楚。
  • 你可以在外线程中做其他事情,我只是举例说明为什么你不需要promise
  • @SethCarnegie 编辑了代码,可以不使用promise吗?
  • 它现在只是重申我的答案,因为您正在将两个返回值传达给fun。如果您只传达了一个并在线程完成其他工作后传达了值,那么是的,这是可能的。 (顺便说一句,f2 是多余的,因为您已经通过执行 f.get() 等待内部线程。)
  • 但它确实比我更强调如何在线程结束之外的另一个时间传达一个值,所以 +1。
猜你喜欢
  • 2017-08-12
  • 1970-01-01
  • 2020-03-24
  • 2013-11-08
  • 2010-11-16
  • 2011-09-04
  • 2013-05-15
  • 2012-06-15
相关资源
最近更新 更多