Catch2 线程安全指南:在派生线程中使用断言与消息宏
Catch2 线程安全指南在派生线程中使用断言与消息宏【免费下载链接】Catch2A modern, C-native, test framework for unit-tests, TDD and BDD - using C14, C17 and later (C11 support is in v2.x branch, and C03 on the Catch1.x branch)项目地址: https://gitcode.com/GitHub_Trending/ca/Catch2Catch2 自 3.9.0 起为断言宏提供了可选的线程安全支持允许测试在用户自行派生的多个线程中同时执行断言与消息宏。本文以 docs/thread-safety.md 为骨架结合仓库源码深入讲解哪些宏可以在派生线程中使用、哪些宏会终止进程以及线程安全开关的配置方式与性能代价帮助你写出真正可运行的多线程测试用例。线程安全的边界什么可以做什么不可以Catch2 的线程安全目前仅限于全部运行时断言宏以及消息类/消息邻近类宏如INFO、WARN。而以下几类宏不是线程安全的不应当从用户派生的线程中调用基准测试宏benchmark macros节section宏生成器generator宏测试用例宏test case macros其中section 宏的不兼容性是结构性的、长期存在的section 通过测试内部的状态机定义穿过测试的路径这与用户任意派生线程的方式天然冲突因此这一限制短期内不会改变见 docs/thread-safety.md 开篇说明。最重要的一点Catch2 的线程安全是选择加入opt-in的默认情况下断言并不是线程安全的。开启方式见下文如何开启线程安全一节。如何在编译期开启线程安全线程安全支持通过编译期配置宏控制相关宏定义于 src/catch2/catch_user_config.hpp.inCATCH_CONFIG_THREAD_SAFE_ASSERTIONS // 开启线程安全断言 CATCH_CONFIG_NO_THREAD_SAFE_ASSERTIONS // 强制关闭线程安全断言CATCH_CONFIG_THREAD_SAFE_ASSERTIONS在 Catch2 3.9.0 引入当时属于实验特性在 3.12.0 起转为正式非实验特性见 docs/configuration.md。由于线程安全会给即使是单线程使用的场景也带来性能开销Catch2 默认采用非线程安全断言。两个宏同时定义时catch_user_config.hpp.in 会直接触发编译错误Cannot force THREAD_SAFE_ASSERTIONS to both ON and OFF。若使用 CMake 集成这一宏通常由构建配置生成并写入catch_user_config.hpp。开启后底层同步原语也会随之切换。在 src/catch2/internal/catch_thread_support.hpp 中可以看到两种实现#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS ) using Mutex std::mutex; using LockGuard std::lock_guardstd::mutex; struct AtomicCounts { std::atomicstd::uint64_t passed{ 0 }; std::atomicstd::uint64_t failed{ 0 }; std::atomicstd::uint64_t failedButOk{ 0 }; std::atomicstd::uint64_t skipped{ 0 }; }; #else // 单线程性能优化的空实现 struct Mutex { void lock() {} void unlock() {} }; struct LockGuard { LockGuard( Mutex ) {} }; using AtomicCounts Counts; #endif也就是说未开启线程安全时锁是空操作、计数是普通结构体不产生任何同步开销开启后断言计数使用std::atomic向 reporter 上报断言结果前会加std::mutex锁。这正解释了为什么线程安全会有额外性能代价。在派生线程中使用断言宏Catch2 的全部运行时断言宏在开启线程安全后均可从派生线程调用但语义上并非全部适用REQUIRE系列REQUIRE、REQUIRE_FALSE等的语义是失败即停止测试执行。其实现方式是抛出异常但用户派生的线程没有测试级的 try-catch 块来捕获这个测试失败异常因此在派生线程中失败一个REQUIRE会直接终止进程。CHECK系列不存在此问题因为它不尝试停止测试执行可以从任何线程安全使用。CHECKED_IF/CHECKED_ELSE同样是线程安全的——它们在内部就是一个断言宏加一个 if。在 src/catch2/internal/catch_run_context.cpp 中可以看到支撑CHECKED_IF/CHECKED_ELSE的上一次断言是否通过标志被声明为static CATCH_INTERNAL_THREAD_LOCAL bool g_lastAssertionPassed即线程局部存储各线程互不干扰。断言相关的线程局部状态开启线程安全后catch_thread_local.hpp 会把CATCH_INTERNAL_THREAD_LOCAL展开为thread_local未开启时展开为空#if defined( CATCH_CONFIG_THREAD_SAFE_ASSERTIONS ) #define CATCH_INTERNAL_THREAD_LOCAL thread_local #else #define CATCH_INTERNAL_THREAD_LOCAL #endif这些线程局部变量被用于存储上次断言语义状态、最近一次宏的源码位置用于异常/致命错误时的精确定位以及消息作用域清理标志见 catch_run_context.cpp。同时注释也说明这些变量刻意使用线程局部魔数静态量避免为非触及 Catch2 的线程付出初始化代价。共享状态的加锁上报断言结果汇总到 reporter 的路径需要触碰共享状态因此被互斥锁保护。在 catch_run_context.cpp 中auto msgHolder Detail::g_messageHolder(); msgHolder.repairUnscopedMessageInvariant(); // From here, we are touching shared state and need mutex. Detail::LockGuard lock( m_assertionMutex ); { auto _ scopedDeactivate( *m_outputRedirect ); updateTotalsFromAtomics(); m_reporter-assertionEnded( AssertionStats( result, msgHolder.getMessages(), m_totals ) ); }而断言计数在锁外先行通过原子操作累加m_atomicAssertionCount.passed/failed等见 catch_run_context.cpp 与 catch_run_context.hpp 中的mutable Detail::Mutex m_assertionMutex;和Detail::AtomicCounts m_atomicAssertionCount;。这种原子计数 上报加锁的设计正是多线程下断言总数依然精确的底层保证。断言类消息宏与派生线程断言类消息宏assertion-like messages在 Catch2 3.10.0 起线程安全。与断言宏类似并非所有断言类消息宏都能在派生线程中使用SKIP与FAIL会停止测试执行与REQUIRE同理不能在用户派生线程中使用否则会终止进程。SUCCEED、FAIL_CHECK、WARN不会尝试停止测试执行可以从任何线程使用。消息宏与派生线程消息宏message macros在 Catch2 3.10.0 起线程安全。为后续断言附加额外消息的宏如INFO、UNSCOPED_INFO、CAPTURE全部线程安全可在任意线程使用。但请注意这些消息是每线程per-thread的——在用户派生线程中的INFO不会被主线程看到反之亦然。从实现上看消息持有者MessageHolder存放于Detail::g_messageHolder()它被声明为static CATCH_INTERNAL_THREAD_LOCAL MessageHolder value;见 catch_run_context.cpp各线程持有独立的消息集合。完整示例代码示例一主线程REQUIRE派生线程CHECKTEST_CASE( Failed REQUIRE in the main thread is fine ) { std::vectorstd::jthread threads; for ( size_t t 0; t 16; t) { threads.emplace_back( []() { for (size_t i 0; i 10000; i) { CHECK( true ); CHECK( false ); } } ); } REQUIRE( false ); }这会按预期工作进程正常运行完毕测试用例失败并通过/失败断言计数正确16 个线程各 20,000 条断言共 160,000 条通过、160,000 条失败加主线程 1 条失败的REQUIRE失败合计 160,001。但需要理解当主线程失败其断言时已派生的线程会继续运行std::jthread析构时才 join。示例二派生线程中的REQUIRETEST_CASE( Successful REQUIRE in spawned thread is fine ) { std::vectorstd::jthread threads; for ( size_t t 0; t 16; t) { threads.emplace_back( []() { for (size_t i 0; i 10000; i) { REQUIRE( true ); } } ); } }REQUIRE成功时没有问题进程正常结束。TEST_CASE( Failed REQUIRE in spawned thread kills the process ) { std::vectorstd::jthread threads; for ( size_t t 0; t 16; t) { threads.emplace_back( []() { for (size_t i 0; i 10000; i) { REQUIRE( false ); } } ); } }这个用例会灾难性地失败并终止进程——派生线程中的失败REQUIRE抛出测试失败异常而该线程没有测试级 catch 块来捕获它。示例三消息跨线程隔离TEST_CASE( messages dont cross threads ) { std::jthread t1( []() { for ( size_t i 0; i 100; i ) { INFO( spawned thread #1 ); CHECK( 1 1 ); } } ); std::thread t2( []() { for (size_t i 0; i 100; i) { UNSCOPED_INFO( spawned thread #2 ); } } ); for (size_t i 0; i 100; i) { CHECK( 1 2 ); } }主线程中任何失败的CHECK( 1 2 )都不会显示 spawned thread #1 消息因为该消息属于t1线程。如果 reporter 展示通过的断言例如以-s运行测试你会看到 spawned thread #1 消息伴随t1中通过的CHECK( 1 1 )出现。spawned thread #2 永远不会显示因为t2中没有任何断言UNSCOPED_INFO的消息只挂接到同线程后续的断言上。示例四主线程中的FAIL/SKIPTEST_CASE( FAIL in the main thread is fine ) { std::vectorstd::jthread threads; for ( size_t t 0; t 16; t) { threads.emplace_back( []() { for (size_t i 0; i 10; i) { CHECK( true ); CHECK( false ); } } ); } FAIL(); }结果符合预期进程正常结束测试失败总计 321 条断言160 通过、161 失败FAIL本身计为一条失败断言。注意主线程命中FAIL时会因std::jthread析构 join 等待其他线程结束。因此一旦派生了多个线程不推荐在主线程使用SKIP——主线程虽会退出测试执行但派生线程仍会继续运行可能反过来导致测试失败。示例五派生线程中的FAIL/SKIPTEST_CASE( FAIL/SKIP in spawned thread kills the process ) { std::vectorstd::jthread threads; for ( size_t t 0; t 16; t) { threads.emplace_back( []() { for (size_t i 0; i 10000; i) { FAIL(); } } ); } }与失败的REQUIRE相同派生线程中的FAIL和SKIP都会终止进程。仓库自带的线程安全测试仓库在 tests/ExtraTests/X94-ThreadSafetyTests.cpp 中提供了专门的线程安全回归测试其文件头注释说明通过在多个子线程中大量触发断言与消息若链接的是非线程安全版本会可靠地触发段错误CTest 定义还会校验最终断言计数是否正确。测试用例包含主线程失败REQUIRE 子线程CHECK/CAPTURE以及兄弟线程中使用UNSCOPED_INFO两类场景均标记[!shouldfail]与本文示例一、示例三相互印证。你可以用X94的构建目标配合线程安全配置验证自己编译的 Catch2 是否符合预期。STATIC_REQUIRE与STATIC_CHECKSTATIC_REQUIRE、STATIC_REQUIRE_FALSE、STATIC_CHECK、STATIC_CHECK_FALSE这四者在**延迟求值配置delayed evaluation configuration**下全部线程安全。需要注意的是静态断言本身是编译期求值的所谓线程安全更多是指其求值后的记录与上报路径与其他断言宏保持一致。致命错误与多线程默认情况下Catch2 会尝试捕获致命错误POSIX 信号 / Windows 结构化异常 SEH并向用户报告有用信息。这一直是尽力而为best-effort的行为但在存在多线程与锁的场景下捕获成功的概率会下降。如果这开始影响你的项目可以通过 docs/configuration.md 中的other-toggles相关配置将其禁用例如关闭致命错误处理的相关宏开关。性能开销开启线程安全要付出多少线程安全的代价取决于构建与断言路径最坏情况优化构建 走成功断言快速路径时线程安全断言实现的性能开销可达40%。其他情况开销更小介于4% ~ 20%之间。这也是为什么 Catch2 默认不开启线程安全即使你的测试完全单线程运行也会为这份可能的多线程安全性买单。从源码看开销主要来自两处断言计数的原子操作AtomicCounts的std::atomic累加以及上报 reporter 前的互斥锁m_assertionMutex。开启前建议先评估测试中并发断言的收益与单测整体运行时间的损失。总结线程安全是编译期 opt-in特性定义CATCH_CONFIG_THREAD_SAFE_ASSERTIONS开启Catch2 3.9.03.12.0 起非实验CATCH_CONFIG_NO_THREAD_SAFE_ASSERTIONS强制关闭。可用范围全部运行时断言宏、INFO/CAPTURE等消息宏、SUCCEED/FAIL_CHECK/WARN以及STATIC_*系列延迟求值配置下。不可用范围section、generator、benchmark、test case 宏以及在派生线程中使用会终止进程的REQUIRE失败、FAIL、SKIP。消息是每线程的跨线程不共享。性能代价最坏约 40%通常 4% ~ 20%只有确实需要多线程并发断言时才值得开启。进一步配置细节见 docs/configuration.md 的 Thread safety in assertions (and messages) 一节运行期命令行参数如展示通过断言的-s见 docs/command-line.md。【免费下载链接】Catch2A modern, C-native, test framework for unit-tests, TDD and BDD - using C14, C17 and later (C11 support is in v2.x branch, and C03 on the Catch1.x branch)项目地址: https://gitcode.com/GitHub_Trending/ca/Catch2创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Refine v5 Chakra UI Inferencer 完整指南:用 `@refinedev/inferencer/chakra-ui` 自动生成 CRUD 页面

Refine v5 Chakra UI Inferencer 完整指南:用 `@refinedev/inferencer/chakra-ui` 自动生成 CRUD 页面

Refine v5 Chakra UI Inferencer 完整指南:用 refinedev/inferencer/chakra-ui 自动生成 CRUD 页面 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: ht…

2026/9/13 17:51:20 阅读更多 →
RemoveWindowsAI 命令行参数速查:3 种组合搞定 90% 的清理场景

RemoveWindowsAI 命令行参数速查:3 种组合搞定 90% 的清理场景

RemoveWindowsAI 命令行参数速查:3 种组合搞定 90% 的清理场景 【免费下载链接】RemoveWindowsAI Force Remove Copilot, Recall and More in Windows 11 项目地址: https://gitcode.com/GitHub_Trending/re/RemoveWindowsAI RemoveWindowsAI 是一个用于移除…

2026/9/13 17:50:20 阅读更多 →
现在热门的AI论文平台有哪些品牌?亲测后说说真心话

现在热门的AI论文平台有哪些品牌?亲测后说说真心话

每到期末、毕业答辩、课题申报阶段,很多学生都会陷入论文写作的困境:选题毫无头绪、大纲逻辑混乱、正文撰写耗时长、参考文献格式出错、查重重复率偏高、AIGC检测告警、本校排版标准复杂。纯人工从零开始写稿、反复修改格式和降重,不仅耗费数…

2026/9/13 17:50:20 阅读更多 →

最新新闻

CAN自定义协议设计:ID位域、CRC校验与状态机的工程实践

CAN自定义协议设计:ID位域、CRC校验与状态机的工程实践

1. 为什么“CAN自定义协议”不是个技术选择,而是系统级生存问题 在工业现场、车载电子、机器人控制这些真实场景里,我见过太多人把“CAN自定义协议”当成一个可有可无的软件配置项——直到产线停机、整车报错、AGV撞墙。CAN总线本身只是物理层和数据链路…

2026/9/13 18:50:47 阅读更多 →
Cua Hyprland 插件固定源码发布:可复现构建、原生 Profile 契约与发布生命周期验证指南

Cua Hyprland 插件固定源码发布:可复现构建、原生 Profile 契约与发布生命周期验证指南

Cua Hyprland 插件固定源码发布:可复现构建、原生 Profile 契约与发布生命周期验证指南 【免费下载链接】cua Scale computer-use 2.0 with open-source drivers, cross-OS fleets, and benchmarks for training, evaluation, and data generation. 项目地址: htt…

2026/9/13 18:50:46 阅读更多 →
在 ADK Python 中集成 Langchain YouTubeSearchTool 构建视频搜索 Agent

在 ADK Python 中集成 Langchain YouTubeSearchTool 构建视频搜索 Agent

在 ADK Python 中集成 Langchain YouTubeSearchTool 构建视频搜索 Agent 【免费下载链接】adk-python An open-source, code-first Python toolkit for building, evaluating, and deploying sophisticated AI agents with flexibility and control. 项目地址: https://gitco…

2026/9/13 18:50:46 阅读更多 →
lo 库 Slice 拼接指南:深入解析 lo.Concat 的泛型实现、类型保留与底层原理

lo 库 Slice 拼接指南:深入解析 lo.Concat 的泛型实现、类型保留与底层原理

lo 库 Slice 拼接指南:深入解析 lo.Concat 的泛型实现、类型保留与底层原理 【免费下载链接】lo 💥 A Lodash-style Go library based on Go 1.18 Generics (map, filter, contains, find...) 项目地址: https://gitcode.com/GitHub_Trending/lo/lo …

2026/9/13 18:50:46 阅读更多 →
Zola 快速上手实战:用 `zola init` 与 Tera 模板从零搭建一个多页面博客站点

Zola 快速上手实战:用 `zola init` 与 Tera 模板从零搭建一个多页面博客站点

Zola 快速上手实战:用 zola init 与 Tera 模板从零搭建一个多页面博客站点 【免费下载链接】zola A fast static site generator in a single binary with everything built-in. https://www.getzola.org 项目地址: https://gitcode.com/GitHub_Trending/zo/zola …

2026/9/13 18:50:46 阅读更多 →
Metabase ClickHouse 驱动完全指南:从连接配置到查询下推的完整指南

Metabase ClickHouse 驱动完全指南:从连接配置到查询下推的完整指南

Metabase ClickHouse 驱动完全指南:从连接配置到查询下推的完整指南 【免费下载链接】metabase The easy-to-use open source Business Intelligence and Embedded Analytics tool that lets everyone work with data :bar_chart: 项目地址: https://gitcode.com/…

2026/9/13 18:49:46 阅读更多 →

日新闻

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and ag…

2026/9/13 0:00:24 阅读更多 →
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: https://gitcode.com/GitH…

2026/9/13 0:00:24 阅读更多 →
Flutter应用改名全指南:从Android到iOS的配置与工具实践

Flutter应用改名全指南:从Android到iOS的配置与工具实践

刚接一个外包项目时,甲方要求把工程里临时用的应用名改成正式产品名。我本来觉得“改名”这种小事,打开配置文件改一行不就完了?结果真动手才发现,Flutter项目里“应用名称”根本不是一处配置,而是一整套散落在 Androi…

2026/9/13 0:00:24 阅读更多 →

周新闻

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验

AI SDK Harness 依赖更新指南:掌握 harness 包 SDK 依赖的升级、桥接同步与一致性校验 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and ag…

2026/9/13 0:00:24 阅读更多 →
Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化

Refine v5 Ant Design NumberField 组件实战:基于 Intl 的本地化数字格式化 【免费下载链接】refine A React Framework for building internal tools, admin panels, dashboards & B2B apps with unmatched flexibility. 项目地址: https://gitcode.com/GitH…

2026/9/13 0:00:24 阅读更多 →
Flutter应用改名全指南:从Android到iOS的配置与工具实践

Flutter应用改名全指南:从Android到iOS的配置与工具实践

刚接一个外包项目时,甲方要求把工程里临时用的应用名改成正式产品名。我本来觉得“改名”这种小事,打开配置文件改一行不就完了?结果真动手才发现,Flutter项目里“应用名称”根本不是一处配置,而是一整套散落在 Androi…

2026/9/13 0:00:24 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/13 16:51:11 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/12 18:29:34 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/12 19:02:44 阅读更多 →