踩坑一处(GCC)STL `std::async` 实现BUG导致的crash问题

💡 原文中文,约16800字,阅读约需40分钟。
📝

内容提要

最近发现使用新版本的opentelemetry-cpp时出现崩溃问题。崩溃位置在std::future析构的地方,由std::async创建。经分析发现是GCC STL实现的一个BUG,已通过修复PR解决。建议绕过该BUG,避免使用该功能。

🔎

延伸解读

问题根源:GCC STL 的 std::async 实现缺陷

文章指出,崩溃源于 GCC 4.8.2 的 libstdc++ 中 std::async 的实现缺陷。在 std::future 析构时,_Async_state_common 的析构函数会调用 _M_join(),而 _M_join() 本应通过 std::call_once 保证线程只 join 一次。但崩溃栈显示,join 时线程对象已为空,导致抛出异常并终止程序。这表明 GCC 的实现在线程安全边界条件下存在竞态,使得 join 被意外重复触发。

触发条件:超时机制与线程状态竞争

崩溃发生在 opentelemetry-cpp 的 PeriodicExportingMetricReader::CollectAndExportOnce 中。该函数使用 std::async 启动子线程执行指标导出,并通过 future.wait_for 实现超时控制。当超时发生时,会设置 cancel_export_for_timeout 标志并跳出循环,随后 future 析构。此时若子线程尚未完成,析构会尝试 join,但可能因 GCC 实现的竞态导致线程对象已被置空,从而触发异常。

影响范围:偶现崩溃与特定版本

该问题并非必现,而是偶发崩溃,因此难以稳定复现。从崩溃栈看,涉及 GCC 4.8.2 的 libstdc++,且与 opentelemetry-cpp v1.15.0 的指标导出逻辑相关。文章提到,类似问题可能与其他线程安全边界条件有关,但未深入分析。对于使用较旧 GCC 版本并依赖 std::async 超时机制的项目,需警惕此类风险。

应对措施:绕过与修复

作者建议暂时绕过该功能,避免使用 std::async 的超时等待模式。同时,opentelemetry-cpp 社区已通过 PR 修复此问题(issue #2982),项目组也通过 cmake-toolset 接入了修复版本。对于无法立即升级的项目,可考虑改用其他线程管理方式,如显式使用 std::thread 和条件变量,以规避 GCC STL 的实现缺陷。

❓

Q&A

使用opentelemetry-cpp时崩溃的原因是什么?

崩溃是由于std::async创建的std::future在析构时触发的GCC STL实现的一个BUG。

如何解决opentelemetry-cpp中的崩溃问题?

建议绕过该BUG,避免使用相关功能,并使用已修复的版本。

崩溃发生在哪个代码位置?

崩溃发生在std::future析构时,具体是在尝试join的线程已经为空的情况下。

这个BUG的修复进展如何?

相关的修复PR已经推送并合入,项目组已接入修复版本。

崩溃问题可能与哪些因素有关?

问题可能与线程安全的边界条件有关,需要进一步分析。

在使用std::async时需要注意什么?

在使用std::async时,需注意避免使用可能导致崩溃的功能,尤其是在GCC STL实现中。

🏷️

标签

➡️

继续阅读