繁体   English   中英

std :: promise set_value和线程安全

[英]std::promise set_value and thread safety

我对std::promise::set_value()上的线程安全性要求有点困惑。

标准说

效果:以原子方式将值r存储在共享状态中并使该状态准备就绪

但是,它还说promise::set_value()只能用于设置一次值。 如果多次调用它,则抛出std::future_error 因此,您只能设置一次承诺的值。

实际上,几乎每个教程,在线代码示例或std::promise实际用例都涉及两个线程之间的通信通道,其中一个线程调用std::future::get() ,另一个线程调用std::promise::set_value()

我从来没有见过多个线程可能调用std::promise::set_value() ,即使他们这样做,除了一个之外的所有线程都会导致抛出std::future_error异常。

那么为什么调用std::promise::set_value()的标准命令是原子的呢? 从多个线程同时调用std::promise::set_value()的用例是什么?


编辑:

由于这里的最高投票答案并没有真正回答我的问题,我认为我所要求的不清楚。 所以,澄清一下:我知道未来和承诺是什么以及它们如何运作。 我的问题是,为什么标准坚持认为std::promise::set_value()必须是原子的? 这是一个比“为什么在调用promise::set_value()和调用future::get() ”之间没有竞争的问题更为微妙的问题?

事实上,这里的许多答案(错误地)都会回答原因是因为如果std::promise::set_value()不是原子的,那么std::future::get()可能会导致竞争条件。 但是这是错误的。

避免竞争条件的唯一要求是std::promise::set_value()必须与std::future::get()有一个before-before关系 - 换句话说,它必须保证当std::future::wait()返回, std::promise::set_value()已完成。

这与std::promise::set_value()本身完全正交是原子的还是非原子的。 在使用条件变量的典型实现中, std::future::get()/wait()将等待条件变量。 然后, std::promise::set_value()可以非原子地执行任何任意复杂的计算来设置实际值。 然后它将通知共享条件变量(暗示具有释放语义的内存栅栏),并且std::future::get()将唤醒并安全地读取结果。

因此, std::promise::set_value()本身不需要是原子的来避免竞争条件 - 它只需要满足与std::future::get() 之前发生的关系。

所以再一次,我的问题是: 为什么 C ++标准坚持认为std::promise::set_value()实际上必须是一个原子操作,就好像对一个互斥锁完全执行std::promise::set_value()的调用一样锁? 我认为没有理由存在这个要求,除非有多个原因或用例同时调用std::promise::set_value()多个线程。 我想不出这样一个用例,因此这个问题。

如果它不是原子存储,那么两个线程可以同时调用promise::set_value ,它执行以下操作:

  1. 检查未来是否准备就绪(即存储值或异常)
  2. 存储价值
    • 标志着国家准备好了
    • 释放阻止共享状态的任何内容

通过使该序列原子,第一个线程来执行(1)获取一路走过来(3),以及任何其他线程调用promise::set_value在同一时间将在(1),提高故障future_errorpromise_already_satisfied

如果没有原子性,两个线程可能会存储它们的值,然后一个会成功标记状态就绪,另一个会引发一个异常,即相同的结果, 除了它可能是线程中看到异常的值通过了。

在许多情况下,哪个线程“赢”可能并不重要,但是当它确实重要时,如果没有原子性保证,则需要在promise::set_value调用周围包装另一个互斥锁。 其他方法(例如比较和交换)不起作用,因为您无法检查未来(除非它是shared_future )以查看您的价值是否获胜。

当哪个线程'获胜'无关紧要时,你可以为每个线程提供自己的未来,并使用std::experimental::when_any来收集碰巧可用的第一个结果。


经过一些历史研究后编辑:

虽然上面(使用相同的promise对象的两个线程)似乎不是一个好的用例,但当然有一篇当代论文设想了C ++的futureN2744 本文提出了一些用例,这些用例具有调用set_value相互冲突的线程,我将在这里引用它们:

其次,考虑并行执行两个或多个异步操作并“竞争”以满足承诺的用例。 一些例子包括:

  • 一系列网络操作(例如,请求网页)与定时器上的等待一起执行。
  • 可以从多个服务器检索值。 为了冗余,尝试了所有服务器,但只需要获得第一个值。

在这两个示例中,要完成的第一个异步操作是满足承诺的操作。 由于任一操作都可以完成第二次,因此必须编写两者的代码以期望对set_value()调用可能失败。

我从来没有见过多个线程可能调用std :: promise :: set_value()的用例,即使他们这样做,除了一个之外的所有线程都会导致抛出std :: future_error异常。

你错过了承诺和未来的整个想法。

通常,我们有一对承诺和未来。 promise是推送异步结果或异常的对象,而future是取异步结果或异常的对象。

在大多数情况下,future和promise对不会驻留在同一个线程上(否则我们会使用一个简单的指针)。 所以,您可以将promise传递给某个线程,线程池或某些第三个库异步函数,并从那里设置结果,并将结果拉入调用者线程。

使用std::promise::set_value设置结果必须是原子的,不是因为许多promises设置了结果,而是因为驻留在另一个线程上的对象(未来)必须读取结果,并且以非原子方式执行它是未定义的行为,所以设置值并拉动它(通过调用std::future::getstd::future::then )必须以原子方式发生

请记住,每个future和promise都有一个共享状态 ,设置一个线程的结果更新共享状态,并从共享状态获取结果读取。 就像C ++中的每个共享状态/内存一样,当它从多个线程完成时,更新/读取必须在锁定下进行。 否则它是未定义的行为。

这些都是很好的答案,但还有一点是必不可少的。 如果没有设定值的原子性,读取该值可能会受到可观察性副作用的影响。

例如,在一个天真的实现中:

void thread1()
{
    // do something. Maybe read from disk, or perform computation to populate value
    v = value;
    flag = true;
}

void thread2()
{
    if(flag)
    {
        v2 = v;//Here we have a read problem.
    }
}

std::promise<>中的原子性允许您避免在一个线程中写入值和在另一个线程中读取之间的非常基本的竞争条件。 当然,如果flag是std::atomic<>并且使用了正确的fence标志,则不再有任何副作用, std::promise保证这一点。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM