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

huangapple go评论94阅读模式
英文:

Calling std::async twice without storing the returned std::future

问题

根据C++17标准,此程序的输出是无法确定的。

英文:

According to the C++17 standard, what is the output of this program?

#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;
}

The program is guaranteed to output: z?

答案1

得分: 17

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

如果从std::async获取的std::future未被移动或绑定到引用,std::future的析构函数将在完整表达式结束时阻塞,直到异步操作完成,实际上使得以下代码同步执行:

std::async(std::launch::async, []{ f(); }); // 临时对象的析构函数等待f()
std::async(std::launch::async, []{ g(); }); // 直到f()完成才启动

因此,您的代码保证打印z - 没有数据竞争。

英文:

C++ reference explicitly mentions the behavior of this code:
> If the std::future obtained from std::async is not moved from or bound to a reference, the destructor of the std::future will block at the end of the full expression until the asynchronous operation completes, essentially making code such as the following synchronous:

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

So your code is guaranteed to print z - there are no data races.

答案2

得分: 6

我不相信在这种情况下cppreference是完全准确的。

标准规定,std::future 的析构函数会释放任何共享状态(§[futures.unique_future]/9):

~future();
效果

  • 释放任何共享状态(31.6.5);
  • 销毁 *this

释放共享状态的描述如下(§[futures.state]/5):

当异步返回对象或异步提供程序说要释放其共享状态时,意味着:

  • 如果返回对象或提供程序持有其共享状态的最后一个引用,则销毁共享状态;以及
  • 返回对象或提供程序放弃其对共享状态的引用;以及
  • 这些操作不会阻塞等待共享状态准备就绪,除非以下所有条件都为真:共享状态是通过调用 std::async 创建的,共享状态尚未准备就绪,而这是对共享状态的最后引用。

[强调部分]

总结

实质上,代码具有未定义的行为。虽然一个实现允许生成阻塞等待共享状态准备就绪的代码,但不要求这样做,甚至不要求文档化是否会这样做。因此,您可能会得到您期望的结果,但不是必须的。

参考

我引用的是 N4713,据我记得,这几乎就是C++17标准。看起来,这个措辞至少一直保持到N4950(这几乎就是C++23)。

英文:

I don't believe that cppreference is entirely accurate in this case.

The standard says that the dtor for std::future releases any shared state (§[futures.unique_future]/9):

> ~future();<br>
> Effects:
> - Releases any shared state (31.6.5);
> - destroys *this.

The description of releasing the shared state says (§[futures.state]/5):

> When an asynchronous return object or an asynchronous provider is said to release its shared state, it means:
> - if the return object or provider holds the last reference to its shared state, the shared state is destroyed; and
> - the return object or provider gives up its reference to its shared state; and
> - these actions will not block for the shared state to become ready, except that it may block if all of the following are true: the shared state was created by a call to std::async, the shared state is not yet ready, and this was the last reference to the shared state.

[emphasis added]

Summary

In essence, the code has undefined behavior. While an implementation is allowed generate code to block for the shared state to become ready, it is not required to do so, and is not even required to document whether it will do so or not. As such, what you have is pretty much the typical situation for undefined behavior: you may get what you expect, but it isn't required.

Reference

I quoted from N4713, which (if memory serves) is pretty much the C++17 standard. It looks like the wording remains the same up through at least N4950 (which is pretty much C++23.).

huangapple
  • 本文由 发表于 2023年6月5日 09:51:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/76403109.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定