C++标准并发支持库综合分析报告:从C++11基础到C++26前沿
梳理了C++标准并发库从C++11到C++26的演进历程。C++11首次引入标准线程模型(std::thread)和原子操作,但存在非RAII设计缺陷。C++20推出std::jthread实现自动资源管理,并引入协作式取消机制(std::stop_token/source)。在同步机制方面,报告对比了基础互斥量(std::mutex)与RAII锁封装器(std::lock_guard等),并分析
C++标准并发支持库综合分析报告:从C++11基础到C++26前沿
引言
C++11的范式转移
C++11标准的发布标志着C++语言发展史上的一个分水岭。在此之前,C++虽然能够通过平台特定的API(如POSIX线程库pthreads或Windows线程库)支持并发编程,但语言本身并未提供一个标准化的、平台无关的并发模型。这使得编写可移植的、健壮的并发应用程序成为一项艰巨的挑战。C++11通过引入一个形式化的内存模型和一套完整的并发支持库,从根本上改变了这一局面。它首次为开发者提供了标准的工具集,包括线程、互斥锁、条件变量和原子操作,使C++从一个仅仅能支持并发的语言,转变为一个原生内建并发支持的现代化编程语言。这一转变对于构建高性能、可移植的现代并发系统至关重要。
演进轨迹(C++11至C++26)
本报告旨在系统性地梳理C++标准并发库的演进脉络,揭示其从诞生到成熟的设计哲学变迁。C++11奠定了并发编程的基础,提供了一系列强大但有时也存在使用风险的基础构件。随后的C++14和C++17标准对这些构件进行了增量式的改进与完善。而C++20则代表了又一次重大的飞跃,引入了更安全的抽象(如std::jthread)和全新的协调原语(如std::latch和std::barrier),显著提升了并发编程的安全性与表达力。展望未来,C++23和C++26标准正酝酿着革命性的变革,旨在引入统一的执行框架(std::execution)和标准化的安全内存回收机制(危险指针与RCU),以应对日益复杂的异步编程挑战。这条演进路径清晰地反映了C++标准委员会对并发编程复杂性的理解日益深刻,以及其致力于提供更安全、更高效、更易用的工具集的决心。
报告目标
本报告的目标是为经验丰富的C++软件架构师和高级开发人员提供一份深度分析的技术参考。通过详尽地剖析各个并发组件的设计原理、演进动机、实际应用中的权衡取舍以及未来的发展方向,本报告旨在帮助技术决策者在设计和实现高性能并发系统时,能够做出明智的技术选型,正确且高效地使用标准库提供的工具,并为迎接C++异步编程的未来浪潮做好充分准备。
第一部分:核心线程原语与管理
本部分将深入探讨并发编程的基本单元——线程,并追溯其在C++标准中的演进历程:从一个需要审慎管理的底层句柄,发展为一个功能完备、资源安全的现代化并发实体。
1.1 C++11基础:std::thread与内存模型
概念与机制
C++11引入的std::thread是标准库对操作系统线程的直接抽象。它作为一个执行线程的句柄,封装了线程的创建、启动和生命周期管理。通过向其构造函数传递一个可调用对象(如函数指针、函数对象或lambda表达式)及其参数,开发者可以轻松地启动一个新的并发执行流。
关键设计缺陷:非RAII特性
尽管std::thread功能强大,但其最受诟病的设计缺陷在于它并非一个完全符合资源获取即初始化(RAII)惯例的类型。
std::thread对象的析构函数行为是其核心风险点:如果一个std::thread对象在析构时其状态仍是joinable()(即可连接的,意味着其代表的线程仍在或可能在运行),程序将调用std::terminate()强制终止。这一设计将管理线程生命周期的责任完全推给了开发者,要求必须在所有可能的代码路径(包括正常执行和异常抛出)中显式地调用join()或detach()。这种手动的资源管理方式极其脆弱,是C++11风格并发代码中一类常见且难以调试的bug的根源。
join()与detach()的抉择
join()和detach()定义了主线程与子线程之间两种截然不同的关系。调用join()会阻塞当前线程,直到被调用的线程执行完毕。这在主线程需要等待子任务完成并回收其结果或资源的场景下是必要的,它创建了一个明确的同步点。相反,detach()会切断std::thread对象与其底层执行线程之间的关联,使得该线程成为一个“守护线程”。此后,该线程的生命周期由C++运行时环境管理,当其执行完毕后,其资源会被自动回收。然而,分离线程的做法通常不被推荐,因为这使得追踪和管理其生命周期、处理其可能抛出的异常以及确保其在程序退出前正确完成变得异常困难。
1.2 C++20的演进:std::jthread作为现代RAII封装
修正设计缺陷
为了解决std::thread的根本性设计缺陷,C++20引入了std::jthread。std::jthread被设计为std::thread的直接替代品,其核心目标就是提供一个行为安全、符合RAII惯例的线程封装。
默认的RAII行为
std::jthread的革命性改进体现在其析构函数中。当一个joinable的std::jthread对象离开作用域时,其析构函数会自动调用request_stop()(请求线程停止),然后调用join()等待线程终止。这种设计将线程的生命周期与对象的生命周期绑定,确保了即使在发生异常的情况下,线程资源也能被安全、有序地释放。这使得std::jthread成为一个真正的RAII类型,极大地简化了资源管理,并从根本上消除了因忘记join()或detach()而导致的程序崩溃风险。
兼容性考量
标准委员会选择引入一个新的std::jthread类,而不是直接修改std::thread的行为,主要是出于对向后兼容性和二进制兼容性的考虑。在C++20发布之时,std::thread已经被广泛使用了近十年,无数现存的代码库和二进制库都依赖于其既有的析构行为。任何对其析构函数的修改都将破坏这些代码的正确性,引发难以预料的兼容性灾难。因此,引入一个全新的、设计更优的类是唯一可行的演进路径。
从std::thread到std::jthread的演进,不仅仅是一次简单的API增强,它深刻地揭示了C++标准委员会在并发设计理念上的成熟与转变。这一过程是C++并发库发展哲学的一个缩影:从提供强大但充满陷阱的底层原语,转向构建默认安全、封装了最佳实践的高级抽象。
最初,C++11提供的std::thread更像是一个对pthreads等平台API的薄层封装 1,它将底层的复杂性和风险(如线程生命周期管理)直接暴露给开发者,导致了诸如因未连接线程而调用std::terminate等常见错误。经过近十年的实践,社区和委员会深刻认识到了这些普遍存在的故障模式。C++20的回应并非简单的修补,而是对默认线程对象的彻底反思。
std::jthread的设计将RAII和协作式取消作为其核心的、与生俱来的特性。这种“发现一个普遍且易错的手动模式,然后用一个标准化的、安全的抽象来替代它”的演进模式,在C++并发库中反复出现(例如,用std::scoped_lock替代手动管理多个锁)。
这一趋势表明,C++标准库的设计正日益成熟,它将开发者的生产力和程序的正确性置于优先地位,努力使“安全的方式”成为“最简单的方式”。这对代码审查、C++教学以及并发代码库的整体健壮性都产生了深远的影响,并强烈预示着未来的标准库将继续青睐高级的、基于策略的抽象,而非原始的底层构件。
1.3 深入协作式取消:std::stop_token与std::stop_source
“礼貌”的中断模型
std::jthread的另一项核心创新是其内建的协作式取消机制。该机制通过<stop_token>头文件提供了一套标准框架,用于向线程发出停止工作的请求,而非强制终止。这种模型与其他语言中危险的“杀死线程”机制形成鲜明对比,后者极易导致资源泄露、死锁等严重问题。C++的取消模型是“协作式”的,这意味着被请求的线程必须主动、周期性地检查停止请求,并在收到请求后执行优雅的清理和退出流程。
机制剖析
协作式取消框架主要由以下几个组件构成:
- std::stop_source:作为取消信道的“写入端”,std::stop_source对象通常由创建线程的代码所持有。它通过request_stop()方法向所有关联的std::stop_token发出停止请求。
- std::stop_token:作为信道的“读取端”,std::stop_token被传递给工作线程。线程内部通过调用其stop_requested()方法来轮询停止状态。
- std::stop_callback:这是一个关键的辅助工具。它允许向一个std::stop_token注册一个回调函数,该函数会在stop_source请求停止时被调用。这对于中断那些正处于阻塞状态(例如,等待在条件变量上)的线程至关重要,因为回调函数可以执行唤醒操作。
与std::jthread的无缝集成
这套取消框架与std::jthread实现了无缝集成,构成了其安全性的基石。每个std::jthread对象内部都拥有一个std::stop_source。当传递给std::jthread构造函数的可调用对象的第一个参数是std::stop_token时,jthread会自动从其内部的stop_source创建一个token并传递给该可调用对象。这一特性与jthread析构函数(该析构函数会先调用request_stop()再join())的行为相结合,创造了一个开箱即用的、完整的、安全的、可中断的线程抽象。对于长时间运行的任务,即使其顶层函数不接受stop_token,也应设计一种机制来响应取消请求,这是编写健壮并发程序的最佳实践。
第二部分:互斥与锁策略
在并发环境中,保护共享数据免受非同步访问是确保程序正确性的核心。本部分将系统性地分析C++标准库提供的互斥访问工具,从基础的互斥量到旨在预防死锁等常见错误的复杂RAII锁封装器。
2.1 基础互斥量:std::mutex、std::timed_mutex与std::recursive_mutex
- std::mutex:这是最基础、最常用的互斥原语。其核心功能是提供一种机制,确保在任何时刻,只有一个线程能够进入由其保护的代码区段(即“临界区”)。通过在进入临界区前调用lock(),在离开时调用unlock(),可以实现对共享资源的独占访问。
- std::timed_mutex:作为std::mutex的扩展,std::timed_mutex增加了时间相关的锁定功能。它提供了try_lock_for()和try_lock_until()方法,允许线程在尝试获取锁时设定一个最长等待时间。如果在指定时间内未能获得锁,该方法将返回false而不是无限期阻塞。这在需要避免无限等待、实现超时逻辑或进行轮询的场景中非常有用。
- std::recursive_mutex:这是一种特殊的互斥量,允许同一个线程多次获取同一个锁而不会产生死锁。每次lock()调用都必须对应一次unlock()调用,只有当所有lock()都被unlock()平衡后,其他线程才能获取该锁。尽管在某些特定场景下(如对共享数据进行递归操作的函数)需要这种特性,但std::recursive_mutex的使用通常被认为是一种代码设计的警示信号,可能表明类的职责划分不清或存在更优的设计方案。
2.2 RAII锁封装器:比较分析
锁管理中的RAII原则
在并发编程中,手动调用lock()和unlock()是一种极其脆弱和危险的实践。如果临界区内发生异常,或者存在多个返回点,很容易导致unlock()被跳过,从而造成互斥量永久锁定,引发死锁。RAII惯例是解决此问题的根本方法。通过将互斥量的生命周期管理封装在对象的构造和析构函数中,RAII锁封装器能够保证无论代码如何执行(正常返回或异常抛出),只要锁对象离开作用域,其管理的互斥量就一定会被释放。
std::lock_guard
std::lock_guard是C++11提供的最简单的RAII锁。它在构造时锁定传入的互斥量,在析构时释放它。std::lock_guard是不可移动、不可复制的,并且除了构造和析构之外不提供任何其他功能。由于其极简的设计和最小的性能开销,它是在临界区仅涉及单个互斥量且无需任何高级锁操作(如手动解锁或延迟锁定)时的首选工具。
std::unique_lock
std::unique_lock是功能更强大、更灵活的RAII锁,但相应地也带来了一些额外的性能开销。它的设计旨在处理更复杂的锁定场景,其关键特性包括:
- 延迟锁定(Deferred Locking):可以与std::defer_lock标签一同构造,使得unique_lock对象与一个互斥量关联,但并不立即锁定它。锁可以在稍后的代码中根据需要显式获取。
- 手动控制:在其生命周期内,可以随时调用lock()、unlock()、try_lock()等方法,提供了对锁状态的精细控制。
- 所有权转移:std::unique_lock是可移动的(movable),这意味着锁的所有权可以从一个unique_lock对象转移到另一个,允许锁在不同作用域或函数间传递。
- 与条件变量的集成:这是std::unique_lock最关键的特性之一。std::condition_variable的wait系列方法要求传入一个std::unique_lock。这是因为wait操作需要原子地解锁互斥量并使线程进入等待状态,然后在被唤醒后重新自动加锁。只有std::unique_lock的灵活性才能支持这种复杂的操作序列。
std::scoped_lock (C++17)
std::scoped_lock是C++17引入的现代化多锁管理方案。它是一个可变参数模板类,可以在其构造函数中接受一个或多个互斥量。
- 死锁规避:std::scoped_lock的核心价值在于它内部实现了一个死锁规避算法(等效于调用std::lock)来同时获取所有传入的互斥量。这从根本上解决了因多个线程以不同顺序获取多个锁而导致的经典死锁问题。
- 简洁与安全:在C++17之前,安全地锁定多个互斥量需要手动调用std::lock,然后为每个互斥量创建std::lock_guard并使用std::adopt_lock标签,这是一个繁琐且容易出错的过程。
std::scoped_lock将这一整个安全模式封装成了一行清晰、易读的代码,极大地提升了代码的安全性和可维护性。
RAII锁封装器的演进历程——从lock_guard和unique_lock到scoped_lock——与从thread到jthread的演进轨迹如出一辙。它清晰地展示了C++标准委员会的一种核心设计策略:识别出那些在实践中反复出现且风险极高的编程模式,然后通过标准化的方式提供一个健壮的解决方案,从而提升语言的抽象层次,让安全的并发编程变得更加触手可及。
在C++11时代,标准库提供了lock_guard用于基础的RAII锁定,以及unique_lock用于需要更高灵活性的复杂场景。然而,对于安全地锁定多个互斥量这一常见需求,开发者仍需诉诸于一种手动的、易错的模式:先调用std::lock来规避死锁,再为每个锁创建带有std::adopt_lock标签的lock_guard。实践证明,开发者常常会错误地实现这个模式,或者忘记锁的获取顺序,从而引入难以调试的死锁。
委员会敏锐地捕捉到了这个普遍存在的痛点。因此,C++17引入了std::scoped_lock,其设计的唯一目的就是直接解决这个具体而危险的问题。它将死锁规避算法和对多个互斥量的RAII管理封装在一个简单、统一的构件中。
std::scoped_lock的出现不仅仅是为了方便,它是一项有针对性的安全特性。它的存在体现了一种设计哲学:当某个并发模式既普遍又危险时,就值得为其设计一个专用的、默认安全的标准库组件。这种做法鼓励开发者从更高层次的意图(“安全地锁定这些资源”)出发进行思考,而不是纠缠于底层的、易错的实现细节。
表1:RAII锁封装器对比
特性 | std::lock_guard | std::unique_lock | std::scoped_lock |
---|---|---|---|
引入标准 | C++11 | C++11 | C++17 |
RAII | 是 | 是 | 是 |
可锁定的互斥量数量 | 一个 | 一个 | 一个或多个 |
死锁规避 | 不适用 | 不适用 | 是(针对多个互斥量) |
可移动性 | 否 | 是 | 否 |
延迟锁定 | 否 | 是 | 否 |
手动unlock() | 否 | 是 | 否 |
与std::condition_variable配合使用 | 否 | 是 | 否 |
主要用例 | 简单、单互斥量的临界区保护。 | 需要灵活性、所有权转移或与条件变量结合使用的复杂场景。 | 安全地同时锁定多个互斥量。 |
2.3 优化读密集型工作负载:std::shared_mutex (C++17)
读写锁模式
在许多并发场景中,对共享数据的访问呈现出“读多写少”的特点。在这种情况下,使用std::mutex会造成性能瓶颈,因为它不允许任何形式的并发访问,即使是多个不会修改数据的读取操作。读写锁(Reader-Writer Lock)模式就是为解决这类问题而设计的,其核心思想是允许多个读者线程并发访问,但在有写者线程时则保证其独占访问。
两种访问级别
C++17通过std::shared_mutex将读写锁模式引入标准库。它提供了两种访问级别:
- 共享所有权(Shared Ownership):允许多个线程同时获取共享锁。这通常通过RAII封装器std::shared_lock来实现。持有共享锁的线程应只对数据进行只读操作。
- 独占所有权(Exclusive Ownership):在任何时刻只允许一个线程获取独占锁。这通常通过std::unique_lock或std::lock_guard来实现。持有独占锁的线程可以对数据进行读写操作。
锁兼容性规则
std::shared_mutex的锁兼容性规则如下:
- 当一个线程持有独占锁时,其他任何线程(无论是想成为读者还是写者)都不能获取任何类型的锁,必须等待。
- 当一个或多个线程持有共享锁时,其他线程可以继续获取共享锁,但任何试图获取独占锁的线程都必须等待,直到所有共享锁都被释放。
- 只有在没有任何线程持有任何锁的情况下,一个线程才能成功获取独占锁。
“无锁升级”的限制
一个至关重要的设计细节是,C++标准库中的std::shared_mutex不支持“锁升级”,即原子地将一个持有的共享锁转换为独占锁。如果一个持有共享锁的线程发现需要写入数据,它必须先释放该共享锁,然后再去尝试获取独占锁。这一设计的目的是为了从根本上避免一种特定的死锁场景:如果两个持有共享锁的线程同时尝试升级为独占锁,它们会互相等待对方释放共享锁,从而导致死锁。开发者需要意识到这一限制,并在设计上避免需要锁升级的逻辑。
第三部分:线程间通信与同步
本部分将探讨超越简单互斥的同步机制,这些机制允许线程之间进行信号传递和行为协调。内容涵盖了从通用的条件变量到C++20引入的高度专用化协调原语,以及基于任务的异步编程模型。
3.1 条件变量:std::condition_variable与伪唤醒的风险
用途与机制
std::condition_variable是一种同步原语,它允许一个或多个线程阻塞等待,直到某个共享条件变为真,并由另一个线程发出通知。它必须与std::mutex协同工作,以保护构成“条件”的共享数据。
等待/通知协议
正确使用std::condition_variable需要遵循严格的协议:
- 等待方(Waiter):
- 必须获取一个与条件变量关联的std::mutex的std::unique_lock。
- 调用wait()方法。此调用会原子地执行两个操作:释放锁,并使当前线程进入阻塞状态。
- 当线程被唤醒(通过通知或伪唤醒),wait()会尝试重新获取锁。只有成功获取锁后,wait()才会返回。
- 通知方(Notifier):
- 获取同一个std::mutex的锁(通常使用std::lock_guard)。
- 修改共享状态,使得条件成立。
- 调用notify_one()唤醒一个等待的线程,或调用notify_all()唤醒所有等待的线程。
伪唤醒(Spurious Wakeups)
伪唤醒是使用条件变量时必须处理的一个关键且微妙的问题。它指的是等待中的线程可能在没有任何notify调用的情况下从wait()调用中“意外”返回。这种现象的产生源于底层操作系统调度器和多处理器硬件的复杂性与性能优化策略。
正确的等待循环
由于伪唤醒的存在,一个简单的cv.wait(lock)调用几乎总是错误的。因为即使线程被唤醒,它等待的条件也可能并未满足。因此,正确的用法是必须将wait()调用置于一个循环中,每次唤醒后都重新检查条件。
std::unique_lock<std::mutex> lock(m);
while (!condition_is_met) {
cv.wait(lock);
}
C++标准库为此提供了更简洁、更安全的谓词(predicate)重载版本,它在内部实现了这个循环,是使用条件变量的规范方式 14:
std::unique_lock<std::mutex> lock(m);
cv.wait(lock, [&]{ return condition_is_met; });
忽略对伪唤醒的处理是并发编程中一个常见的错误来源,因此,始终使用循环或谓词版本的wait是使用std::condition_variable的一条铁律。
3.2 C++20协调工具集:std::latch、std::barrier和std::counting_semaphore
引入新原语的动机
std::condition_variable是一个功能强大的通用同步工具,但对于许多常见的同步模式而言,它的使用显得过于复杂和繁琐。手动管理互斥锁、条件状态和处理伪唤醒等细节,不仅增加了开发者的心智负担,也容易引入错误。C++20引入std::latch、std::barrier和std::counting_semaphore,正是为了针对这些常见模式提供更具表达力、更不易出错且可能性能更高的专用工具。
std::latch:一次性倒计时门闩
- 机制:std::latch是一个一次性的同步点,其内部维护一个递减计数器。它在构造时用一个初始计数值进行初始化。线程可以通过调用count_down()来使计数器减一(这是一个非阻塞操作)。其他线程可以调用wait(),该调用会阻塞,直到计数器值达到零。
- 用例:std::latch非常适合“分叉-汇合”(fork-join)模型,例如,一个主线程启动了多个工作线程去执行一次性的初始化任务,然后主线程需要等待所有初始化任务全部完成后才能继续执行。在这种场景下,每个工作线程在完成任务后调用count_down(),主线程则调用wait()等待。std::latch是不可重用的,一旦计数器归零,它就永久处于打开状态。
std::barrier:可重用循环屏障
- 机制:std::barrier是一个可重用的同步点,用于协调一组固定数量的线程。它在构造时用参与线程的数量进行初始化。参与线程通过调用
arrive_and_wait()来到达屏障点,此调用会阻塞,直到所有参与线程都到达。当最后一个线程到达时,所有被阻塞的线程将被同时释放,然后屏障会自动重置,进入下一个同步周期。此外,std::barrier还可以在每个周期结束、释放线程之前,执行一个可选的“完成函数”。 - 用例:std::barrier是迭代式并行算法的理想选择。在这些算法中,一组工作线程需要在每一步计算完成后进行同步,以确保所有线程都完成了当前阶段的工作,然后才能一起进入下一阶段。科学计算、物理模拟和并行数据处理流水线是其典型的应用场景。
std::counting_semaphore:通用资源计数器
- 机制:std::counting_semaphore可以看作是一个广义的互斥锁,它管理着对一个包含N个资源的池的访问。它在构造时用一个代表可用资源数量的计数值进行初始化。线程通过调用
acquire()来请求一个资源,此操作会使计数器减一;如果计数器为零,则acquire()会阻塞。线程通过调用release()来释放一个资源,此操作会使计数器加一,并可能唤醒一个正在等待的线程。std::binary_semaphore是std::counting_semaphore的一个特化,其计数值最大为1,行为类似于一个非递归的互斥锁。 - 用例:std::counting_semaphore非常适合用于限制并发度,例如,控制同时访问数据库的连接数,或者限制一个线程池中处理特定类型任务的线程数量。与互斥锁的一个关键区别是,调用
release()的线程不必是之前调用acquire()的同一个线程。这个特性使得信号量也成为实现生产者-消费者模式中进行信令通知的有效工具。
C++20引入的这些专用同步原语,是标准委员会从近十年std::condition_variable的实践应用中汲取的重要设计教训的体现。这一教训的核心在于:对于编写正确的并发代码而言,代码的表达力和语义清晰度至关重要。通过提供与常见同步模式直接对应的工具,标准库极大地降低了开发者的认知负荷,并从根本上消除了一整类因手动实现这些模式而产生的bug。
以std::latch的典型用例——等待N个初始化任务完成——为例。在C++20之前,实现这一逻辑需要组合使用std::mutex、一个共享计数器以及一个std::condition_variable。这种手动的实现方式非常复杂:计数器必须在锁的保护下进行递减;等待线程必须使用正确的while循环来避免伪唤醒;通知逻辑也必须精确无误。在这个样板代码中,任何一个微小的疏忽都可能导致难以察觉的并发bug。
std::latch的出现,用两个简单的调用——latch.count_down()和latch.wait()——取代了上述整个复杂的机制。代码的意图变得一目了然。这不仅仅是便利性的提升,更是将编程意图编码到了类型系统中。当开发者使用std::barrier时,他是在明确地声明其算法具有循环同步的特性。这使得代码具有自文档化的效果,不仅更易于人类理解和推理,也为静态分析工具提供了更丰富的语义信息,同时还允许标准库为这些特定模式提供高度优化的底层实现。
表2:C++20协调原语对比
原语 | std::latch | std::barrier | std::counting_semaphore |
---|---|---|---|
引入标准 | C++20 | C++20 | C++20 |
核心概念 | 倒计时至零 | 集合点(Rendezvous) | 资源计数器 |
可重用性 | 否(一次性) | 是(循环) | 是 |
主要操作 | count_down(), wait() | arrive_and_wait() | acquire(), release() |
所有权 | 不适用 | 参与者固定 | 无所有权(释放者可与获取者不同) |
完成阶段动作 | 否 | 是(可选的可调用对象) | 否 |
主要用例 | 等待一组一次性事件完成(如初始化)。 | 在并行算法中,于重复的同步点同步一组固定的线程。 | 限制对N个资源的并发访问;生产者-消费者信令。 |
3.3 基于任务的异步并发:<future>库
超越线程的抽象
C++11的<future>头文件引入了基于任务的并行模型,这是一种比直接管理std::thread更高层次的抽象。其核心思想是启动一个能够异步计算并产生一个值的任务,然后在未来的某个时间点获取这个值。
Promise/Future通信管道
<future>库的核心组件可以被理解为一个一次性的、单向的通信管道 39:
- std::promise:管道的“写入端”或“生产者”。异步任务通过std::promise对象,使用set_value()方法将计算结果放入管道,或者使用set_exception()方法存入一个异常。
- std::future:管道的“读取端”或“消费者”。启动任务的线程持有对应的std::future对象,用于在未来获取结果。调用future的get()方法会阻塞当前线程,直到管道中有值可用。get()只能被调用一次,调用后future对象变为无效状态。
- std::shared_future:为了支持多个消费者等待同一个结果的场景,std::future可以通过.share()方法转换为std::shared_future。std::shared_future是可复制的,允许多个线程并发地等待并获取同一个异步结果(结果会被复制给每个调用get()的线程)。
创建Future的三种方式
- std::async:这是最高级、最简洁的方式。std::async接受一个可调用对象,异步地(可能在新线程中)执行它,并直接返回一个与该函数返回值相关联的std::future。然而,
std::async的使用存在一些重要的陷阱。首先是其启动策略(launch policy):默认策略允许实现延迟执行(std::launch::deferred),即直到调用future的get()或wait()时才同步执行任务,这可能违背了并行的初衷。因此,显式指定std::launch::async通常是必要的。其次,std::async返回的future有一个特殊的行为:如果它是一个临时对象(即未被存储在命名变量中),其析构函数将会阻塞,直到异步任务完成。这个“阻塞的析构函数”行为可能导致代码意外地串行化,是一个需要特别注意的陷阱。 - std::packaged_task:它是一个对可调用对象的封装器,将其与一个promise绑定在一起。当packaged_task被调用时,其内部函数的返回值会自动被存入其关联的promise中。这种方式将“任务的定义”与“任务的执行”解耦。开发者可以创建一系列packaged_task对象,将它们存入队列,然后由一个工作线程池来取出并执行,这为构建复杂的任务调度系统提供了基础。
- std::promise:这是最底层的、最手动的方式。当异步结果的产生过程比较复杂,不是通过单个函数的返回值就能简单获得时,就需要直接使用std::promise。例如,一个任务可能需要等待多个事件或执行多个步骤后才能最终确定结果值,这时就可以在任务逻辑的最后手动调用promise的set_value()。
第四部分:无锁编程的基石:原子操作与内存排序
本部分将深入探讨C++并发库的最底层,解析编写高性能无锁(lock-free)代码所必需的工具——原子类型,以及支配其行为的复杂但至关重要的内存模型。
4.1 std::atomic:超越普通整型
原子性保证
原子操作是指在多线程环境中不可被中断的、完整的操作。对于一个普通整型变量i,i++这样的操作在底层实际上包含“读取-修改-写入”三个步骤,在并发环境下,其他线程可能在这三个步骤之间插入执行,从而导致数据竞争(data race)和不正确的结果。std::atomic<T>模板类正是为了解决这个问题而生,它为类型T提供了一系列原子操作,如fetch_add、load、store、exchange等,确保这些操作的执行是不可分割的。
std::atomic的适用范围
std::atomic可以应用于所有内置整型、指针类型。C++20进一步扩展了其能力,通过引入std::atomic_ref,使得对非原子对象的原子操作成为可能,包括对std::shared_ptr的原子访问。
性能考量
原子操作是无锁编程的基础。通过避免使用互斥锁,无锁算法可以消除锁竞争、减少线程阻塞,从而在高度并行的系统中获得显著的性能和伸缩性优势。然而,这种性能优势的代价是极高的编程复杂性。正确使用原子操作需要对C++内存模型有深刻的理解,否则极易引入微妙且难以调试的并发bug。
4.2 揭秘内存模型:memory_order实用指南
内存排序的必要性
在现代多核处理器系统中,为了最大化性能,编译器和CPU核心都可能对内存操作(读/写)进行重排序。这意味着代码中的指令顺序不一定等于它们在硬件上的实际执行顺序。在单线程环境下,这种重排序通常是透明且安全的。但在多线程环境下,如果缺乏明确的约束,一个线程的写入操作可能对另一个线程以意想不到的顺序变得可见,甚至根本不可见,从而导致程序逻辑错误。
std::memory_order枚举就是C++提供给开发者的工具,用以在原子操作上指定精确的内存可见性与排序约束。
C++内存模型的设计,本质上是在程序员的便利性、跨硬件平台的可移植性以及极致性能之间进行深思熟虑的权衡。其默认的内存顺序std::memory_order_seq_cst提供了一种安全且易于理解的模型,但可能以牺牲性能为代价。而更弱的内存顺序则将底层硬件的性能特性直接暴露给程序员,创造了一套强大但使用时需格外谨慎的工具。
现代硬件架构的内存模型差异巨大。例如,x86架构提供了强内存模型,其默认保证了较为严格的顺序性,而ARM等弱内存模型架构则允许更大程度的指令重排序以优化性能。如果C++标准强行规定一种过于严格的内存模型,那么在弱模型硬件上就会导致不必要的性能损失;反之,如果模型过于宽松,又会让习惯了强模型行为的开发者感到困惑并容易出错。
面对这种多样性,C++标准选择提供一个包含不同强度等级的内存顺序谱系。
std::memory_order_seq_cst通过强制实现一个所有线程都认同的全局操作序列,为开发者提供了一个简单、可移植的心智模型,但其代价是在弱模型硬件上可能需要插入昂贵的内存屏障(memory fence)指令。
std::memory_order_release和std::memory_order_acquire则精确地映射了线程间消息传递和建立可见性所需的核心同步概念,通常具有更低的开销。而std::memory_order_relaxed则几乎直接对应于硬件的原始原子指令,在不需要任何排序保证的场景下提供了最高的执行速度。
因此,C++内存模型并非随意设计的产物,而是对底层硬件现实进行精心抽象后形成的接口。深刻理解这一点,能让专家级开发者在性能和复杂性之间做出有意识的、明智的取舍。选择seq_cst意味着优先考虑简洁性和正确性;而选择更弱的内存顺序,则是在追求极致性能(尤其是在ARM等平台上)时,主动承担更高认知负荷的专业决策。
排序保证(由弱到强)
- std::memory_order_relaxed:最弱的内存顺序。它只保证操作本身的原子性,不提供任何跨线程的同步或排序保证。编译器和CPU可以自由地对relaxed原子操作与其他内存访问进行重排序。它适用于那些只关心最终原子结果,而不在乎中间过程顺序的场景,例如实现一个简单的性能计数器。
- std::memory_order_acquire / std::memory_order_release:这是无锁编程中最常用的一对内存顺序,它们共同构建了“同步于”(synchronizes-with)关系。
- release(用于写操作):一个release存储操作就像一道屏障,它保证在它之前的所有内存写入(无论是原子的还是非原子的),对于之后成功读取到这个新值的acquire加载操作都是可见的。它阻止了其之前的内存操作被重排到它之后。
- acquire(用于读操作):一个acquire加载操作也像一道屏障,它保证在它之后的所有内存读取,都能看到由与之配对的release存储操作所“发布”的数据。它阻止了其之后的内存操作被重排到它之前。
这对组合是实现线程间消息传递和状态发布的关键,例如在实现无锁队列的入队和出队操作时。
- std::memory_order_acq_rel:用于“读取-修改-写入”(RMW)类型的原子操作(如fetch_add)。它结合了acquire(针对读取部分)和release(针对写入部分)的语义。这意味着,它既能保证读取到其他线程release的数据,也能保证将自己的修改release给其他线程。
- std::memory_order_seq_cst:默认且最强的内存顺序。它不仅提供了acquire-release的所有保证,还额外确保了所有seq_cst操作存在一个单一的、所有线程都认同的全局总排序。这使得程序的行为最符合直觉,最容易推理。但这种强保证在许多硬件平台上需要插入昂贵的内存屏障指令,可能会成为性能瓶颈。因此,只有在确实需要全局一致性,或者无法安全地使用更弱内存顺序时,才应使用它。
表3:内存排序保证
内存顺序 | 操作类型 | 保证与约束 | 典型用例/性能 |
---|---|---|---|
memory_order_relaxed | 加载、存储、RMW | 仅保证原子性。无排序约束,允许最大程度的重排。 | 递增原子计数器,只关心最终原子值。性能最高。 |
memory_order_release | 存储、RMW | 阻止之前的内存操作被重排到此存储之后。使其之前的写入对一个acquire线程可见。 | 释放锁或发布数据供其他线程消费。 |
memory_order_acquire | 加载、RMW | 阻止之后的内存操作被重排到此加载之前。使其能看到一个release线程的写入。 | 获取锁或读取已发布的数据。 |
memory_order_acq_rel | RMW | 结合acquire(用于加载)和release(用于存储)的语义。 | 同时需要获取和释放语义的原子RMW操作,如一步完成加锁解锁。 |
memory_order_seq_cst | 加载、存储、RMW | acq_rel的所有保证,外加所有seq_cst操作的单一全局总排序。 | 默认选项。用于需要简单强保证或难以推理更弱顺序的场景。可能因内存屏障而最慢。 |
4.3 ABA问题:无锁算法中的持久挑战
问题定义
ABA问题是使用“比较并交换”(Compare-and-Swap, CAS)循环实现无锁算法时一个经典且隐蔽的陷阱。其过程如下:
- 线程T1读取共享内存位置M的值,得到A。
- T1基于值A进行一些计算,准备用新值C通过CAS操作更新M。
- 在T1执行CAS之前,它被挂起。
- 线程T2介入,将M的值从A修改为B,然后又修改回A。
- T1恢复执行,它执行CAS操作,检查M的当前值是否仍为它当初读取的A。检查通过,于是T1成功将M的值更新为C。
问题在于,尽管M的值在T1看来没有变化(仍然是A),但其代表的状态可能已经发生了根本性的改变。T1的CAS操作在错误的假设下成功了,可能导致数据结构损坏。
问题的重要性
在涉及指针和动态内存管理的无锁数据结构(如无锁栈或队列)中,ABA问题尤为致命。一个节点可能被从数据结构中移除(其地址为A),其内存被释放,然后系统又分配了一块新的内存用于新节点,而这块新内存的地址恰好也是A。此时,尽管指针值相同,但它指向的已经是完全不同的对象。如果CAS操作仅凭指针值相等就判断状态未变,将导致灾难性的后果。
经典解决方案
在C++26标准化方案出现之前,解决ABA问题通常依赖于一些非标准的、平台相关或需要手动实现的技巧,例如:
- 标签指针(Tagged Pointers):将指针的一部分低位用作“版本号”或“标签”。每次修改指针时,不仅更新指针值,也增加版本号。这样,即使指针值变回A,版本号也已经不同,CAS会因此失败。这需要硬件支持对足够宽的整数(如64位或128位)进行原子CAS操作。
- 使用双字CAS指令:某些硬件平台提供能原子地比较并交换两个相邻内存字(例如,一个指针和一个计数器)的指令。这为实现带版本号的CAS提供了硬件支持。
这些解决方案的复杂性和平台依赖性,凸显了在标准库层面提供通用、可移植的安全内存回收机制的必要性,这也正是第五部分将要探讨的C++26前沿特性的核心动机。
第五部分:新前沿:C++20及未来的高级并发
本部分将分析C++并发库最前沿、最具前瞻性的发展,聚焦于那些将定义未来高性能异步编程范式的抽象机制。这些新特性共同勾勒出一个更加完善、高效和易用的C++并发编程生态系统。
5.1 简化异步:C++20协程在并发系统中的作用
超越回调与Future
C++20引入的协程(Coroutines)是一项语言级别的特性,旨在从根本上改变异步代码的编写方式。它允许开发者用看似同步、顺序的风格来编写本质上是异步的逻辑,从而摆脱传统异步编程中普遍存在的“回调地狱”(callback hell)或繁琐的future链式调用。
核心关键字
协程的功能通过三个新的关键字来实现:
- co_await:协程的核心。当co_await一个“可等待对象”(awaitable)时,它会暂停当前协程的执行,将控制权返回给调用者或调度器,并在等待的操作完成后,从暂停点恢复执行。
- co_yield:用于实现生成器(generator)。它产生一个值给消费者,然后暂停协程,等待下一次请求。
- co_return:用于从协程中返回值,标志着协程的执行结束。
编译器魔法
任何函数只要包含了上述三个关键字之一,就会被编译器识别为协程。编译器会自动将该函数转换成一个复杂的状态机。这个状态机对象,连同函数的所有局部变量,通常会被分配在堆上(除非编译器能进行优化)。这种机制使得协程可以在不阻塞任何物理线程的情况下被挂起和恢复,从而极大地提高了系统的并发能力和资源利用率。
缺失的环节:执行器
一个至关重要的事实是,C++20标准只提供了协程的底层语言机制,但并未提供一个标准的高层执行器(executor)或运行时(runtime)来调度和执行这些协程。这意味着开发者不能“开箱即用”地运行协程,而必须依赖于第三方库(如cppcoro、concurrencpp、Boost.Asio等)提供的调度器。这一缺失凸显了下一节将要讨论的标准化工作的紧迫性和重要性。
5.2 抽象执行:P2300 std::execution (发送者/接收者) 提案
统一的异步抽象
P2300提案,即std::execution,旨在为C++提供一个统一的、可组合的异步编程框架,以解决上述“执行器缺失”的问题。它的目标是成为C++中所有异步操作的基础,取代功能有限的std::async,并为协程、GPU计算、网络I/O等各种异步任务提供一个标准的、通用的调度与组合模型。
核心概念:发送者/接收者模型 (Sender/Receiver)
P2300模型基于三个核心抽象:
- 调度器 (Scheduler):一个轻量级对象,代表了在哪里(on which execution context)执行工作的策略。例如,一个调度器可以代表一个线程池、一个特定的GPU流或当前线程的事件循环。
- 发送者 (Sender):一个惰性的、可组合的对象,它描述了一个异步操作及其依赖关系,但本身并不执行任何工作。发送者可以通过管道操作符(|)与各种算法(如then、when_all)链接起来,构建复杂的异步工作流。
- 接收者 (Receiver):一个定义了三个回调函数(set_value, set_error, set_done)的对象。它作为发送者工作流的终点,负责消费异步操作的结果、错误或完成信号,并将控制权传递给下一个异步阶段。
组合是关键
这三个组件通过一个明确的流程协同工作:
- 一个调度器通过schedule()操作产生一个初始的发送者。
- 这个发送者可以通过一系列算法进行转换和组合,形成一个更复杂的发送者,描述了整个异步任务链。
- 最终,这个描述了完整工作的发送者通过connect()操作与一个接收者连接,生成一个“操作状态”(operation state)对象。
- 这个操作状态对象封装了待执行的、具体化的工作。最后通过start()或sync_wait()等消费者(consumer)来启动整个异步流程。
这种惰性求值、声明式的模型为编译器和库实现者提供了巨大的优化空间。
现状与未来
由于其规模和复杂性,P2300未能进入C++23标准。然而,它在委员会中获得了强有力的支持,并被普遍认为是C++26的核心特性之一。它代表了C++高性能异步编程的未来方向,是构建下一代并发库和应用程序的基石。
5.3 迈向标准化的安全内存回收
问题重述
第四部分指出,std::atomic为无锁算法提供了原子修改的工具,但它并未解决无锁编程中两个最棘手的问题:安全的内存回收(如何确定一个可能被其他线程访问的节点可以被安全地delete)和ABA问题。C++26的两个重要提案——危险指针和RCU——正是为了在标准库层面提供这些问题的解决方案。
危险指针 (Hazard Pointers, P2530 for C++26)
- 机制:危险指针是一种“协作式垃圾回收”机制。当一个读者线程(reader)将要访问一个共享对象时,它会先将该对象的指针“声明”到一个线程局部的、对其他线程可见的“危险指针”列表中。当一个写者线程(writer)想要删除一个对象时,它不能立即delete,而是先将其“退休”(retire)。退休的对象会被放入一个待删除列表。写者线程会周期性地扫描所有读者线程的危险指针列表。只有当一个退休对象的指针没有出现在任何一个危险指针列表中时,写者才能安全地将其内存回收。
- 用例:危险指针通过确保一个节点在其被任何读者线程访问期间不会被回收和重用,从而有效地解决了ABA问题。它非常适用于那些读操作非常频繁、竞争激烈且需要极低延迟的无锁数据结构。
读-复制-更新 (Read-Copy-Update, RCU, P2545 for C++26)
- 机制:RCU是一种为“读多写少”场景高度优化的同步技术。当写者需要修改数据时,它不会在原地修改,而是先创建一个数据的副本,在副本上进行所有修改,然后通过一次原子的指针交换操作,将一个全局指针指向这个新的、修改后的版本。在此期间,所有已经开始的读操作将继续在旧版本的数据上进行,不受任何影响。旧版本的数据只有在经过一个“宽限期”(grace period)后才能被安全回收。这个宽限期被定义为足以确保所有在指针交换前开始的读者都已完成其读操作的时间。
- 用例:RCU的读者端开销极低,通常只需要一次普通的指针读取,没有任何锁或原子操作的开销。这使得它在读取操作占绝对主导地位的场景下(如路由表、配置数据、访问控制列表等)具有无与伦比的性能优势。
协程、std::execution以及安全内存回收原语的并行发展并非巧合,而是一项协调一致的、多管齐下的系统性工程,其共同目标是为现代C++并发编程构建一个完整的、从底层到顶层的垂直整合技术栈。这个生态系统中的每一个组件都精确地解决了不同层次的问题,并且它们之间被设计成可以协同工作、互相增强。
首先,在最底层,编写正确且高效的无锁数据结构是一项艰巨的任务(如第四部分所述)。危险指针(P2530)和RCU(P2545)的标准化,旨在提供内存安全管理的基础设施层。它们将使开发者能够构建高性能的无锁容器,而无需成为该领域的顶尖专家,从而解决了数据结构层面的挑战。
其次,一旦拥有了这些高性能的并发数据结构,就需要一个高效的机制来调度在其上运行的任务。这就是“执行层”的角色。std::execution(P2300)提供了一个通用的、可组合的框架,用于定义和调度任何类型的异步任务,其执行上下文可以是一个CPU线程池,也可以是一个GPU流,从而解决了任务调度的普适性问题。
最后,在最高层,开发者需要一种简洁、直观的方式来编写利用这些调度任务的应用程序逻辑。这就是“语法层”或“逻辑层”的作用。C++20协程提供了现代化的语言语法,使得开发者可以用简单的、看似顺序的代码来组织复杂的异步工作流,而这些工作流的底层动力正是由std::execution框架提供的。
综上所述,这三者并非孤立的特性,而是构成了一个新兴C++异步生态系统的三个相互依存的层次。RCU/HP提供了安全的数据结构,std::execution提供了运行工作的引擎,而协程则提供了编写工作的用户友好语法。这个生态系统的最终愿景是:开发者可以简单地co_await一个任务,该任务由std::execution调度到一个操作着由危险指针保护的无锁队列的线程池上执行,而每一层的复杂性都由标准库进行了优雅的封装和管理。
结论与战略建议
关键趋势综合
本报告的分析揭示了C++标准并发库演进过程中的几个核心趋势:
- 向默认安全的转变:从std::thread到std::jthread,从手动多锁管理到std::scoped_lock,标准库的设计理念正持续地从提供底层构件转向封装最佳实践,使安全编程成为更简单、更自然的选择。
- 同步原语的专用化:C++20引入的latch、barrier和semaphore表明,标准库正朝着提供更具表达力的专用工具方向发展,以应对特定的、常见的同步模式,从而降低通用工具(如condition_variable)的误用风险。
- 异步模型的统一化:以P2300 std::execution为代表的努力,旨在为所有形式的异步操作(从CPU计算到I/O、GPU任务)提供一个统一、可组合的框架,这是C++并发编程未来发展的核心方向。
- 无锁编程基础设施的标准化:P2530(危险指针)和P2545(RCU)的提出,标志着标准库开始着手解决无锁编程中最困难的部分——安全内存回收,这将极大地降低构建高性能无锁数据结构的门槛。
对架构师与开发者的指导
基于以上分析,我们为C++并发系统设计者和开发者提供以下战略建议:
- 工具选型指南:
- 线程管理:优先使用std::jthread替代std::thread,以获得RAII保证和内建的协作式取消支持。
- 锁定:对于多互斥量锁定场景,应始终使用C++17的std::scoped_lock以避免死锁。对于单互斥量,根据是否需要与条件变量交互或手动控制来选择std::unique_lock或std::lock_guard。
- 协调:在需要线程协调时,首先考虑使用C++20的专用原语。根据“一次性等待”、“循环同步”或“资源限制”的模式,分别选择std::latch、std::barrier或std::counting_semaphore,仅在这些工具不适用时才回退到使用更底层的std::condition_variable。
- 拥抱现代C++:迁移到C++17和C++20标准,不仅仅是为了获得新功能,更是为了利用这些版本中内含的、旨在从根本上减少常见并发错误的重大安全改进。采用这些现代特性可以显著提高代码的健壮性和可维护性。
- 为未来做准备:尽管std::execution尚未正式成为标准,但它所代表的发送者/接收者模型是C++异步编程的未来。团队应开始通过stdexec等参考实现来学习和试验这一模型。对协程的深入理解和实践同样至关重要。提前掌握这些概念,将在未来的技术浪潮中获得显著的竞争优势。
结语
C++标准并发支持库已经从C++11时代的一组基础原语,演进为一个日益成熟、层次分明、安全性不断增强的复杂生态系统。它为构建世界上性能要求最严苛的软件提供了坚实的基础。通过理解其演进的脉络和设计哲学,开发者可以更有效地驾驭并发编程的复杂性,编写出更安全、更高效、更具前瞻性的代码。
引用的著作
- FYI: C++20 std:jthread vs Julia’s threads - Offtopic, 访问时间为 九月 22, 2025, https://discourse.julialang.org/t/fyi-c-20-std-jthread-vs-julias-threads/116502
- Which c++ standard to adopt for new projects, c++11 to c++20? - Reddit, 访问时间为 九月 22, 2025, https://www.reddit.com/r/cpp/comments/1f5rdl0/which_c_standard_to_adopt_for_new_projects_c11_to/
- Moving to C++ 20? Here’s What You Need to Know - Incredibuild, 访问时间为 九月 22, 2025, https://www.incredibuild.com/blog/what-you-need-to-do-to-move-on-to-c-20-the-complete-list
- thread vs std::jthread | by Samira | Medium, 访问时间为 九月 22, 2025, https://medium.com/@samiracppdev/vs-d3033f079cb7
- A new thread in C++20 (jthread) - Medium, 访问时间为 九月 22, 2025, https://medium.com/@vgasparyan1995/a-new-thread-in-c-20-jthread-ebd121ae8906
- Concurrent Details Matter - Why and How we fixed std::thread by std::jthread : C++ On Sea, 访问时间为 九月 22, 2025, https://cpponsea.uk/2020/sessions/details-matter.html
- C++ | std::jthread and cooperative cancellation with stop token - nextptr, 访问时间为 九月 22, 2025, https://www.nextptr.com/tutorial/ta1588653702/stdjthread-and-cooperative-cancellation-with-stop-token
- C++ 20 stop_token in jthread — politely interrupting the thread… | by Somenath Mukhopadhyay | Medium, 访问时间为 九月 22, 2025, https://medium.com/@som.mukhopadhyay/c-20-stop-token-in-jthread-politely-interrupting-the-thread-a808cd0a2a0f
- Better worker threads with c++23 cooperative thread interruption - twdev.blog, 访问时间为 九月 22, 2025, https://twdev.blog/2023/06/stop_source/
- std::stop_token - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/stop_token.html
- std::lock (C++) - Steven Gong, 访问时间为 九月 22, 2025, https://stevengong.co/notes/lock-(C++)
- C++ Locking Mechanisms - Jennifer Chukwu, 访问时间为 九月 22, 2025, https://jenniferchukwu.com/posts/LockingMechanisms
- unique_lock or lock_guard: Which Is Better? - C++ - GeeksforGeeks, 访问时间为 九月 22, 2025, https://www.geeksforgeeks.org/cpp/stdunique_lock-or-stdlock_guard-which-is-better/
- std::condition_variable - cppreference.com - C++ Reference, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/condition_variable.html
- std::scoped_lock - cppreference.com - C++ Reference, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/scoped_lock.html
- std::scoped_lock - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/scoped_lock
- std::shared_mutex in C++ - GeeksforGeeks, 访问时间为 九月 22, 2025, https://www.geeksforgeeks.org/cpp/std-shared_mutex-in-cpp/
- Shared Mutex (Read/write lock) - CLUE++'s documentation!, 访问时间为 九月 22, 2025, https://cppstdx.readthedocs.io/en/latest/shared_mutex.html
- std::shared_mutex - cppreference.com - C++ Reference, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/shared_mutex.html
- std::shared_mutex - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/shared_mutex
- Upgrading a read-write lock - Mastering the C++17 STL [Book] - O’Reilly Media, 访问时间为 九月 22, 2025, https://www.oreilly.com/library/view/mastering-the-c-17/9781787126824/12805707-7910-413a-98b5-a802cd25f1a5.xhtml
- Exploring Inter-thread Communication with Condition Variables | CodeSignal Learn, 访问时间为 九月 22, 2025, https://codesignal.com/learn/courses/concurrency-essentials-in-cpp/lessons/exploring-inter-thread-communication-with-condition-variables
- Spurious wakeup - Wikipedia, 访问时间为 九月 22, 2025, https://en.wikipedia.org/wiki/Spurious_wakeup
- Spurious wakeups explanation sounds like a bug that just isn’t worth fixing, is that right?, 访问时间为 九月 22, 2025, https://softwareengineering.stackexchange.com/questions/186842/spurious-wakeups-explanation-sounds-like-a-bug-that-just-isnt-worth-fixing-is
- std::condition_variable - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/condition_variable
- Latches And Barriers – MC++ BLOG - Modernes C++, 访问时间为 九月 22, 2025, https://www.modernescpp.com/index.php/latches-and-barriers/
- condition_variable existed since c++11, so std::latch must provide some ben… | Hacker News, 访问时间为 九月 22, 2025, https://news.ycombinator.com/item?id=25924636
- Primitives in C++ for Multithreading | Siddharth C, 访问时间为 九月 22, 2025, https://siddharthchillale.github.io/blog/multithreading_primitives/primitives/
- Synchronization Primitives in C++20 - KDAB, 访问时间为 九月 22, 2025, https://www.kdab.com/synchronization-primitives-in-c20/
- std::latch - cppreference.com - C++ Reference, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/latch.html
- std::latch - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/latch
- std::barrier - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/barrier.html
- std::barrier - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/barrier
- std::counting_semaphore, std::binary_semaphore - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/counting_semaphore
- Synchronization Primitives - userver, 访问时间为 九月 22, 2025, https://userver.tech/docs/v2.0/d6/d6c/md_en_2userver_2synchronization.html
- Where can we use std::barrier over std::latch? - c++ - Stack Overflow, 访问时间为 九月 22, 2025, https://stackoverflow.com/questions/48985967/where-can-we-use-stdbarrier-over-stdlatch
- The promises and challenges of std::async task-based parallelism in C++11 - Eli Bendersky, 访问时间为 九月 22, 2025, https://eli.thegreenplace.net/2016/the-promises-and-challenges-of-stdasync-task-based-parallelism-in-c11/
- The Promises and Challenges of std::async Task-based Parallelism in C++11 - DZone, 访问时间为 九月 22, 2025, https://dzone.com/articles/the-promises-and-challenges-of-stdasync-task-based
- Concurrency in C++ : Passing Data between Threads — Promise-Future - Medium, 访问时间为 九月 22, 2025, https://medium.com/@litombeg/concurrency-in-c-passing-data-between-threads-promise-future-e22fb672736f
- C++17: I See a Monad in Your Future! | Bartosz Milewski’s Programming Cafe, 访问时间为 九月 22, 2025, https://bartoszmilewski.com/2014/02/26/c17-i-see-a-monad-in-your-future/
- C++ Asynch future promise package_task and shared_future | Polyglot Blog, 访问时间为 九月 22, 2025, https://www.bit-byter.com/blog/files/cpp-Future.html
- Promise and Future – MC++ BLOG - Modernes C++, 访问时间为 九月 22, 2025, https://www.modernescpp.com/index.php/promise-and-future/
- std::future in C++ - GeeksforGeeks, 访问时间为 九月 22, 2025, https://www.geeksforgeeks.org/cpp/std-future-in-cpp/
- std::future - cppreference.com, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/thread/future
- std::future - cppreference.com, 访问时间为 九月 22, 2025, http://en.cppreference.com/w/cpp/thread/future.html
- Futures, async, packaged_tasks and promises in C++ - ncona.com, 访问时间为 九月 22, 2025, https://ncona.com/2018/02/futures-async-packaged_tasks-and-promises-in-c/
- What Limitation of std::async is Stroustrup Referring To? - Stack Overflow, 访问时间为 九月 22, 2025, https://stackoverflow.com/questions/26372904/what-limitation-of-stdasync-is-stroustrup-referring-to
- Synchronization Primitives with std::atomic | CodeSignal Learn, 访问时间为 九月 22, 2025, https://codesignal.com/learn/courses/concurrency-essentials-in-cpp/lessons/synchronization-primitives-with-stdatomic
- When should each thread synchronization objects be used? - Stack Overflow, 访问时间为 九月 22, 2025, https://stackoverflow.com/questions/760168/when-should-each-thread-synchronization-objects-be-used
- An Introduction to Memory Ordering and Atomic Operations | CodeSignal Learn, 访问时间为 九月 22, 2025, https://codesignal.com/learn/courses/lock-free-concurrent-data-structures/lessons/an-introduction-to-memory-ordering-and-atomic-operations
- std::memory_order - cppreference.com - C++ Reference, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/atomic/memory_order.html
- P2055R1: A Relaxed Guide to memory_order_relaxed - Open Standards, 访问时间为 九月 22, 2025, https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p2135r1.pdf
- Multithreading in C++: Memory Ordering - Ramtin’s Blog, 访问时间为 九月 22, 2025, https://www.ramtintjb.com/blog/memory-ordering
- Synchronization and Ordering Constraints – MC++ BLOG - Modernes C++, 访问时间为 九月 22, 2025, https://www.modernescpp.com/index.php/synchronization-and-ordering-constraints/
- Memory orders?? : r/cpp - Reddit, 访问时间为 九月 22, 2025, https://www.reddit.com/r/cpp/comments/1innywn/memory_orders/
- ABA problem - Wikipedia, 访问时间为 九月 22, 2025, https://en.wikipedia.org/wiki/ABA_problem
- Understanding and Effectively Preventing the ABA Problem in Descriptor-based Lock-free Designs - Bjarne Stroustrup, 访问时间为 九月 22, 2025, https://www.stroustrup.com/isorc2010.pdf
- The ABA Problem | Baeldung on Computer Science, 访问时间为 九月 22, 2025, https://www.baeldung.com/cs/aba-concurrency
- Solving the ABA Problem for Lock-Free Free Lists - moodycamel.com, 访问时间为 九月 22, 2025, https://moodycamel.com/blog/2014/solving-the-aba-problem-for-lock-free-free-lists
- ABA Problem | [Lab] Lock Free Programming Lab - GitBook, 访问时间为 九月 22, 2025, https://eric-lo.gitbook.io/lock-free-programming/aba-problem
- C++ Coroutines for Async Development: Why You Should be Excited - Tomato Soup, 访问时间为 九月 22, 2025, https://www.wholetomato.com/blog/cpp-coroutines-async-development/
- c++ - What are coroutines in C++20? - Stack Overflow, 访问时间为 九月 22, 2025, https://stackoverflow.com/questions/43503656/what-are-coroutines-in-c20
- Understanding C++ Coroutine Implementation - Medium, 访问时间为 九月 22, 2025, https://medium.com/@AlexanderObregon/understanding-c-coroutine-implementation-8e6e5a2c3edd
- Coroutines (C++20) - cppreference.com - C++ Reference, 访问时间为 九月 22, 2025, https://en.cppreference.com/w/cpp/language/coroutines.html
- Coroutines & Concurrency in C++ - Prog stuff - WordPress.com, 访问时间为 九月 22, 2025, https://stoyannk.wordpress.com/2019/05/05/coroutines-concurrency-in-c/
- c++ - Awaiting a predicate with C++20 coroutines - Stack Overflow, 访问时间为 九月 22, 2025, https://stackoverflow.com/questions/74583083/awaiting-a-predicate-with-c20-coroutines
- David-Haim/concurrencpp: Modern concurrency for C++. Tasks, executors, timers and C++20 coroutines to rule them all - GitHub, 访问时间为 九月 22, 2025, https://github.com/David-Haim/concurrencpp
- chriskohlhoff/executors: C++ library for executors - GitHub, 访问时间为 九月 22, 2025, https://github.com/chriskohlhoff/executors
- P2300R10: `std::execution` - WG21 Links, 访问时间为 九月 22, 2025, https://wg21.link/P2300
- P2300R5: `std::execution` - Open Standards, 访问时间为 九月 22, 2025, https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2300r5.html
- NVIDIA/stdexec: `std::execution`, the proposed C++ framework for asynchronous and parallel programming. - GitHub, 访问时间为 九月 22, 2025, https://github.com/NVIDIA/stdexec
- Is it necessary to refer to the unified executor proposal of C++ to improve async in Rust?, 访问时间为 九月 22, 2025, https://internals.rust-lang.org/t/is-it-necessary-to-refer-to-the-unified-executor-proposal-of-c-to-improve-async-in-rust/17573
- Executors: a Change of Perspective - ACCU, 访问时间为 九月 22, 2025, https://accu.org/journals/overload/29/165/teodorescu/
- std::execution – MC++ BLOG - Modernes C++, 访问时间为 九月 22, 2025, https://www.modernescpp.com/index.php/stdexecution/
- P2300 (Sender/Receiver) is DEAD in the water for C++23 !!! : r/cpp - Reddit, 访问时间为 九月 22, 2025, https://www.reddit.com/r/cpp/comments/st9bjm/p2300_senderreceiver_is_dead_in_the_water_for_c23/
- Hazard Pointers in C++26 – MC++ BLOG - Modernes C++, 访问时间为 九月 22, 2025, https://www.modernescpp.com/index.php/hazard-pointers-in-c26/
- Simplify concurrent programming with C++26 Hazard Pointers. - CppDepend:, 访问时间为 九月 22, 2025, https://cppdepend.com/blog/simplify-concurrent-programming-with-c26-hazard-pointers/
- Read-copy-update (RCU) – MC++ BLOG - Modernes C++, 访问时间为 九月 22, 2025, https://www.modernescpp.com/index.php/read-copy-update-rcu/
- Read-copy-update - Wikipedia, 访问时间为 九月 22, 2025, https://en.wikipedia.org/wiki/Read-copy-update
- High-level C++ Implementation of the Read-Copy-Update Pattern - Gábor Márton’s Webpage, 访问时间为 九月 22, 2025, https://martong.github.io/high-level-cpp-rcu_informatics_2017.pdf
- Will you use C++26 rcu library? : r/cpp - Reddit, 访问时间为 九月 22, 2025, https://www.reddit.com/r/cpp/comments/1dppjkk/will_you_use_c26_rcu_library/
- P2530: Hazard Pointers Belong In The Standard? : r/cpp - Reddit, 访问时间为 九月 22, 2025, https://www.reddit.com/r/cpp/comments/sywjf3/p2530_hazard_pointers_belong_in_the_standard/
- Read-Copy Update (RCU) - Open Standards, 访问时间为 九月 22, 2025, https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2023/p2545r4.pdf
- Is anyone using coroutines seriously? : r/cpp - Reddit, 访问时间为 九月 22, 2025, https://www.reddit.com/r/cpp/comments/18b8zpy/is_anyone_using_coroutines_seriously/
更多推荐
所有评论(0)