比较 magic statics、std::call_once 和 std::async 的异常行为

The Old New Thing (Raymond Chen) 工具

摘要

本文比较了 C++ 并发机制中的异常处理行为:magic statics、std::call_once 和 std::async,解释了它们的差异并提供了使用建议。

<p>我们一直在比较 <a title="Magic statics vs. std::call_once" href="https://devblogs.microsoft.com/oldnewthing/20260916-00/?p=112703"> magic statics, <code>std::<wbr />call_<wbr />once</code></a> 和 <a title="std::call_once vs. std::async" href="https://devblogs.microsoft.com/oldnewthing/20260917-00/?p=112706"> <code>std::<wbr />async</code></a>,但有一件事我们还没有考虑,那就是它们在异常事件中的行为。</p> <p>对于 magic statics,如果在静态初始化过程中发生异常,则静态被视为未初始化。异常会传播,下次调用该函数时,语言会尝试再次初始化该静态。</p> <p>对于 <code>call_<wbr />once</code>,如果在 lambda 执行过程中发生异常,则调用被视为未发生。异常会传播,因此下次使用相同的 <code>once_<wbr />flag</code> 调用 <code>call_<wbr />once</code> 时,它会尝试调用它。</p> <p>但 <code>std::<wbr />async</code> 不同。如果在执行 invocable 时发生异常,则异常会被保存,当你向 future 或 shared future 请求结果时,异常会重新抛出。它不会尝试再次执行 invocable。</p> <p>让我们用表格总结一下。</p> <table class="cp3" style="border-collapse: collapse;" border="1" cellspacing="0" cellpadding="3"> <tbody> <tr> <th> </th> <th>之前</th> <th>成功之后</th> <th>异常之后</th> </tr> <tr> <td>Magic static</td> <td>未初始化</td> <td>已初始化</td> <td>未初始化</td> </tr> <tr> <td><tt>std::<wbr />call_<wbr />once</tt></td> <td>未初始化</td> <td>已初始化</td> <td>未初始化</td> </tr> <tr> <td><tt>std::<wbr />async</tt></td> <td>未初始化</td> <td>已初始化</td> <td>失败</td> </tr> </tbody> </table> <p>或者我们可以用状态图来表示。</p> <table style="text-align: center;" title="From a box labeled Uninitialized, there is an arrow labeled success that goes to Initialized. There is an arrow labeled async fail that goes to Failed. And an arrow labeled magic static fail and call_once fail that goes from Uninitialized back to itself." border="0" cellspacing="0" cellpadding="3"> <tbody> <tr> <td>magic static 失败<br /> <tt>call_once</tt> 失败<br />⮏</td> <td valign="bottom"><tt>async</tt> 失败</td> <td>&nbsp;</td> </tr> <tr> <td style="border: solid 1px currentcolor;">未初始化</td> <td>→</td> <td style="border: solid 1px currentcolor;">失败</td> </tr> <tr> <td>↓ 成功</td> <td>&nbsp;</td> <td>&nbsp;</td> </tr> <tr> <td style="border: solid 1px currentcolor;">已初始化</td> </tr> </tbody> </table> <p>回到 <code>std::<wbr />call_<wbr />once</code> 和 <code>std::<wbr />async</code> 之间的选择,你必须考虑如果在尝试初始化变量时发生异常,你希望发生什么。如果你想再次尝试,那么使用 <code>std::<wbr />call_<wbr />once</code>。如果你想记住失败并不断重新抛出它,那么使用 <code>std::<wbr />async</code>。</p> <p>如果你无所谓,那么我建议使用 <code>std::<wbr />call_<wbr />once</code>,因为它轻量得多。</p> <p>文章 <a href="https://devblogs.microsoft.com/oldnewthing/20260918-00/?p=112709">比较 magic statics、&lt;CODE&gt;std::&lt;WBR&gt;call_&lt;WBR&gt;once&lt;/CODE&gt; 和 &lt;CODE&gt;std::&lt;WBR&gt;async&lt;/CODE&gt; 的异常行为</a> 最初发表在 <a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a> 上。</p>
查看原文
查看缓存全文

缓存时间: 2026/09/21 14:29

# 比较魔法静态变量、std::call_once 和 std::async 的异常行为 - 新旧事物谈 来源:https://devblogs.microsoft.com/oldnewthing/20260918-00?p=112709 我们已经比较了魔法静态变量、`std::call_once`(https://devblogs.microsoft.com/oldnewthing/20260916-00/?p=112703)和`std::async`(https://devblogs.microsoft.com/oldnewthing/20260917-00/?p=112706),但有一件事我们还没考虑,那就是它们在发生异常时的行为。 对于魔法静态变量,如果静态变量初始化期间发生异常,那么该静态变量就被视为未初始化。异常会继续传播,下次调用该函数时,语言会再次尝试初始化该静态变量。 对于`call_once`,如果执行 lambda 表达式时发生异常,那么这次调用就被视为未发生。异常会继续传播,因此下次使用相同的`once_flag`调用`call_once`时,它会再次尝试执行。 但`std::async`不同。如果在执行可调用对象期间发生异常,那么该异常会被保存,当你向 future 或 shared_future 请求结果时,异常会被重新抛出。它*不会*再次尝试执行可调用对象。 让我们用表格总结一下。 | 状态 | 初始 | 成功后 | 异常后 | | :--- | :--- | :--- | :--- | | 魔法静态变量 | 未初始化 | 已初始化 | 未初始化 | | std::call_once | 未初始化 | 已初始化 | 未初始化 | | std::async | 未初始化 | 已初始化 | 已失败 | 或者我们可以用状态图表示。 ``` 魔法静态变量 失败 ⮏ call_once 失败 ⮏ async 失败 ⮏ 未初始化 → 已失败 ↓ 成功 已初始化 ``` 回到`std::call_once`和`std::async`之间的选择,你必须考虑当初始化变量发生异常时你希望发生什么。如果你想再次尝试,那么使用`std::call_once`。如果你想记住失败并持续重新抛出该异常,那么使用`std::async`。 如果你对此无所谓,我建议使用`std::call_once`,因为它轻量得多。 ### 分类 ### 主题 ## 作者 Raymond Chen Raymond 参与了 Windows 的演进超过 30 年。2003 年,他开始了一个名为"新旧事物谈"的网站,其受欢迎程度远远超出了他的想象,这一发展至今仍让他感到毛骨悚然。该网站催生了一本书,书名同样为"新旧事物谈"(Addison Wesley 2007 年出版)。他偶尔会出现在 Windows 开发文档的 Twitter 账号上,讲述一些没什么实际信息的故事。

相似文章

std::call_once 与 std::async

The Old New Thing (Raymond Chen)

本文比较了 C++ 中用于延迟执行的 std::call_once 和 std::async,讨论了它们在实现、性能和异常行为方面的差异。

魔法静态变量 vs. std::call_once

The Old New Thing (Raymond Chen)

本文比较了两种 C++ 技术:魔法静态变量和 std::call_once,用于缓存计算,并解释了根据作用域和初始化等因素何时使用哪种技术。

C++ futures 第三部分:std::async 的心智模型

The Old New Thing (Raymond Chen)

Raymond Chen 解释 C++ std::async 的心智模型,说明 launch 策略的两个维度(立即/延迟、同线程/新线程)中哪些组合是标准允许的,哪些被禁止或由实现决定。

Tokio/Rayon 陷阱:为何 async/await 在并发中失灵

Hacker News Top

本文探讨了 async/await 语法虽然易于编写,但在生产环境中因将异步与并发混为一谈而引入了巨大复杂性,常需手动将 I/O 与计算任务分配到 Tokio 和 Rayon 等不同运行时,导致延迟飙升与系统不稳定。