调用 std::async 两次而不存储返回的 std::future

Yas*_*suo 13 c++ multithreading asynchronous language-lawyer c++17

根据C++17标准,这个程序的输出是什么?

#include <iostream>
#include <string>
#include <future>

int main() {
  std::string x = "x";

  std::async(std::launch::async, [&x]() {
    x = "y";
  });
  std::async(std::launch::async, [&x]() {
    x = "z";
  });

  std::cout << x;
}
Run Code Online (Sandbox Code Playgroud)

程序保证输出:z?

Evg*_*Evg 17

C++ 参考明确提到了此代码的行为:

如果std::future获取的 fromstd::async未从引用移动或绑定到引用,则 the 的析构函数std::future将在完整表达式的末尾阻塞,直到异步操作完成,本质上使代码如以下同步:

std::async(std::launch::async, []{ f(); }); // temporary's dtor waits for f()
std::async(std::launch::async, []{ g(); }); // does not start until f() completes
Run Code Online (Sandbox Code Playgroud)

因此您的代码可以保证打印z- 不存在数据争用。


Jer*_*fin 6

我不认为 cppreference 在这种情况下完全准确。

\n

标准规定 dtor 会std::future释放任何共享状态 (\xc2\xa7[futures.unique_future]/9):

\n
\n

〜未来();
\n效果:

\n
    \n
  • 释放任何共享状态(31.6.5);
  • \n
  • 破坏*this.
  • \n
\n
\n

释放共享状态的描述是这样的(\xc2\xa7[futures.state]/5):

\n
\n

当异步返回对象或异步提供者被称为释放其共享状态时,这意味着:

\n
    \n
  • 如果返回对象或提供者持有对其共享状态的最后一个引用,则共享状态将被销毁;和
  • \n
  • 返回对象或提供者放弃对其共享状态的引用;和
  • \n
  • 这些操作不会阻止共享状态变为就绪状态,但如果满足以下所有条件,则可能会阻塞:共享状态是通过调用创建的std::async,共享状态尚未就绪,并且这是最后一个引用到共享状态。
  • \n
\n
\n

[强调]

\n

概括

\n

本质上,代码具有未定义的行为。虽然允许实现生成代码来阻塞共享状态以准备就绪,但不需要这样做,甚至不需要记录它是否会这样做。因此,您所遇到的情况几乎是未定义行为的典型情况:您可能会得到您所期望的结果,但这不是必需的。

\n

参考

\n

我引用了 N4713,它(如果没记错的话)几乎是 C++17 标准。看起来至少在 N4950(几乎是 C++23)之前,措辞都保持不变。

\n

  • [\[futures.async\]/5.4](https://timsong-cpp.github.io/cppwp/std17/futures.async#5.4) 是需要阻塞的规范写法。 (4认同)
  • @JerryCoffin 英语:_“与”同步_实际上意味着阻塞。`std::async` 很特殊,因为 `~future()` (释放共享状态)会阻塞,直到线程完成。 (2认同)