c++ - std::promise set_value 和线程安全

标签 c++ multithreading c++11 language-lawyer

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

standard says :

Effects: Atomically stores the value r in the shared state and makes that state ready

但是,它也说 promise::set_value() 只能用于设置一次值。如果多次调用,则会抛出 std::future_error。所以你只能设置一次 Promise 的值。

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

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

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


编辑:

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

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

避免竞争条件的唯一要求是 std::promise::set_value() 必须有 happens-beforestd::future::get() 的关系——换句话说,必须保证当 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() 本身不需要是原子的来避免这里的竞争条件 - 它只需要满足 happens-beforestd::future::get()的关系。

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

最佳答案

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

  1. 检查 future 是否准备就绪(即有存储值或异常)
  2. 存储值
    • 标记状态就绪
    • 释放任何阻碍共享状态变为就绪的内容

通过使这个序列原子化,第一个执行的线程 (1) 一直到 (3),任何其他同时调用 promise::set_value 的线程都将失败(1) 并使用 promise_already_satisfied 引发 future_error

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

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

当哪个线程“获胜”无关紧要时,您可以给每个线程自己的 future ,并使用 std::experimental::when_any收集碰巧可用的第一个结果。


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

虽然上述(两个线程使用同一个 promise 对象)看起来不是一个好的用例,但它肯定是由当代论文之一将 future 引入 C++ : N2744 .本文提出了几个用例,它们有这样的冲突线程调用 set_value,我将在这里引用它们:

Second, consider use cases where two or more asynchronous operations are performed in parallel and "compete" to satisfy the promise. Some examples include:

  • A sequence of network operations (e.g. request a web page) is performed in conjunction with a wait on a timer.
  • A value may be retrieved from multiple servers. For redundancy, all servers are tried but only the first value obtained is needed.

In both examples, the first asynchronous operation to complete is the one that satisfies the promise. Since either operation may complete second, the code for both must be written to expect that calls to set_value() may fail.

关于c++ - std::promise set_value 和线程安全,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45626919/

相关文章:

c++ - stringstream 和 nullptr 的子类

c++ - 如何实现跨平台的C++亚洲字符?

c++ - 在 C++ 线程中嵌套函数调用

c++ - 为什么 std::lock() 在使用我自己的 unique_lock 对象时会导致无限循环?

c++ - boost::bind 无法绑定(bind)到纯虚基类中定义的非静态函数模板成员类型

c++ - 模板 'using' 声明不起作用

C++ 映射迭代器?

c++ - 在 emplace() 中创建对象时复制省略

java.lang.NoClassDefFoundError : occurs after multiple uses of a program

c++ - x86 放松排序性能?