家族规范与源码实现)
mold 中的并发基石深入解读 oneTBB 互斥锁Mutex家族规范与源码实现【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold本指南以仓库third-party/tbb中 oneAPI Threading Building BlocksoneTBB官方规范文档《Mutual Exclusion》为主体系统讲解 oneTBB 提供的 10 种互斥锁类mutex、rw_mutex、spin_mutex、spin_rw_mutex、speculative_spin_mutex、speculative_spin_rw_mutex、queuing_mutex、queuing_rw_mutex、null_mutex、null_rw_mutex的接口契约、公平性/可重入性保证与适用场景并结合 mutex.h 的源码实现剖析其“自适应自旋 等待通知”的底层机制。读完本文你将能看懂 TBB 互斥锁的完整规范理解scoped_lock作用域锁模式与三类编译期 traits并掌握在 mold 这类高性能链接器项目中如何为不同临界区挑选合适的锁。一、总览互斥锁家族解决什么问题oneTBB 提供了一组互斥原语用于编写无数据竞争race-free的线程安全代码。一个互斥对象mutex用于防止数据竞争并在线程之间提供安全的数据同步。规范文档原文见 mutual_exclusion.rst其中列出的互斥类共 10 个按职责可分为两组互斥Mutex类mutex、spin_mutex、speculative_spin_mutex、queuing_mutex、null_mutex读写锁ReaderWriterMutex类rw_mutex、spin_rw_mutex、speculative_spin_rw_mutex、queuing_rw_mutex、null_rw_mutex每个类的完整接口说明位于 mutual_exclusion/ 目录下对应的*_cls.rst文档中。这些类共同点在于全部不可拷贝、不可移动且普遍不公平、不可重入null_*系列除外。二、规范基石Mutex 与 ReaderWriterMutex 命名需求要读懂 10 个类必须先理解它们共同建模的两个“命名需求”Named Requirements。2.1 Mutex 需求与 scoped_lock 作用域锁模式Mutex 需求文档 指出互斥锁与锁的接口“相对精简spartan专为高性能设计”并强制推行scoped locking pattern作用域锁模式这是 C 库中广泛使用的模式原因是无需记得手动释放锁当异常从互斥保护区域抛出时锁会自动释放。模式由两部分组成一个mutex对象一个lock对象——构造 lock 对象即获得 mutex 上的锁销毁 lock 对象即释放锁。规范给出的示例{ // Construction of myLock acquires lock on myMutex M::scoped_lock myLock( myMutex ); // ... actions to be performed while holding the lock ... // Destruction of myLock releases lock on myMutex }若区域内操作抛出异常锁会随代码块退出而自动释放。一个类型M满足 Mutex 需求需要具备如下scoped_lock接口见规范原文class M { // Implementation specifics // ... class scoped_lock { public: constexpr scoped_lock() noexcept; scoped_lock(M m); ~scoped_lock(); scoped_lock(const scoped_lock) delete; scoped_lock operator(const scoped_lock) delete; void acquire(M m); bool try_acquire(M m); void release(); }; };各成员语义M::scoped_lock()构造scoped_lock不获取互斥锁M::scoped_lock(M)构造scoped_lock并在给定互斥锁上取得锁M::~scoped_lock()释放已获取的锁若已获取void acquire(M)在给定互斥锁上获取锁bool try_acquire(M)尝试在给定互斥锁上获取锁成功返回true否则falsevoid release()释放已获取的锁。此外Mutex 类型还必须定义三个编译期 traitsstatic constexpr bool M::is_rw_mutex是否为读写互斥锁static constexpr bool M::is_recursive_mutex是否可重入recursivestatic constexpr bool M::is_fair_mutex是否公平fair。规范明确互斥类型与M::scoped_lock类型既不可拷贝也不可移动。同时留有一个实现自由度表中标注为否定保证如“不公平”“不可重入”的条目实现可以给出相反的肯定保证即“更优”实现是允许的。规范还给出建模 Mutex 需求类的保证总表类Fair公平Reentrant可重入mutexNoNospin_mutexNoNospeculative_spin_mutexNoNoqueuing_mutexYesNonull_mutexYesYes2.2 ReaderWriterMutex 需求读写锁语义ReaderWriterMutex 需求文档 在 Mutex 需求基础上扩展出读写锁概念引入布尔参数writewrite true表示申请写锁write false表示申请读锁。当一个 ReaderWriterMutex 未被写锁持有时可以同时持有多个读锁写锁则排斥所有其他线程同时持锁。其scoped_lock接口在 Mutex 需求之上增加了write参数与升级/降级操作class RWM { class scoped_lock { public: constexpr scoped_lock() noexcept; scoped_lock(RWM m, bool write true); ~scoped_lock(); scoped_lock(const scoped_lock) delete; scoped_lock operator(const scoped_lock) delete; void acquire(RWM m, bool write true); bool try_acquire(RWM m, bool write true); void release(); bool upgrade_to_writer(); bool downgrade_to_reader(); }; };关键成员语义scoped_lock(RWM, bool write true)构造并获取锁write为true取写锁否则取读锁acquire/try_acquire同 Mutex 版但多出write参数决定读写属性release()释放锁若未持锁则行为未定义undefinedbool upgrade_to_writer()把读锁升级为写锁。若锁曾被释放并重新获取则返回false否则返回true包括本来就持有写锁的情形bool downgrade_to_reader()把写锁降级为读锁。若锁曾被释放并重新获取则返回false否则返回true包括本来就是读锁的情形。同样需要is_rw_mutex、is_recursive_mutex、is_fair_mutex三个 traits。建模 ReaderWriterMutex 需求类的保证总表类Fair公平Reentrant可重入rw_mutexNoNospin_rw_mutexNoNospeculative_spin_rw_mutexNoNoqueuing_rw_mutexYesNonull_rw_mutexYesYes规范附注两个对当前所有读写锁都成立的实现事实is_recursive_mutex均为falsescoped_lock::downgrade_to_reader恒返回true。三、互斥Mutex类逐个详解3.1mutex自适应互斥锁文档见 mutex_cls.rst。mutex以自适应adaptive方式建模 Mutex 需求无法获取锁的线程会先自旋spin自旋一定时间后再阻塞block。它满足 ISO C 标准 [thread.mutex.requirements] 的全部互斥需求但不公平、不可重入。声明于头文件oneapi/tbb/mutex.hnamespace oneapi { namespace tbb { class mutex { public: mutex() noexcept; ~mutex(); mutex(const mutex) delete; mutex operator(const mutex) delete; class scoped_lock; void lock(); bool try_lock(); void unlock(); static constexpr bool is_rw_mutex false; static constexpr bool is_recursive_mutex false; static constexpr bool is_fair_mutex false; }; } }成员函数语义mutex()以未锁定状态构造互斥锁~mutex()销毁一个未锁定的互斥锁持锁销毁属未定义行为void lock()获取锁采用自适应等待逻辑忙等一定时间后转入阻塞bool try_lock()非阻塞尝试获取锁成功返回true失败返回falsevoid unlock()释放当前线程持有的锁。3.2spin_mutex纯自旋互斥锁文档见 spin_mutex_cls.rst。spin_mutex用自旋锁建模 Mutex 需求同样满足 ISO C [thread.mutex.requirements]且不公平、不可重入。声明于oneapi/tbb/spin_mutex.h接口与mutex完全一致lock/try_lock/unlock 三个falsetraits。其lock()语义为若锁已被占用则持续自旋try_lock()非阻塞尝试成功返回true。它适合临界区极短、持锁时间远小于线程切换开销的场景——把 CPU 时间花在自旋上比让出线程更划算。3.3speculative_spin_mutex基于硬件事务内存的推测执行锁文档见 speculative_spin_mutex_cls.rst。该锁同样建模 Mutex 需求在支持硬件事务内存HTM的处理器上如 Intel® Transactional Synchronization ExtensionsIntel® TSX实现允许无冲突地并发执行受保护数据的修改不支持 HTM 时退化为普通自旋锁行为。声明于oneapi/tbb/spin_mutex.h与spin_mutex同一头文件类体不含lock/try_lock/unlock的直接声明因为其获取/释放完全经由scoped_lock完成。其特性与适用前提不公平、不可重入is_fair_mutex false、is_recursive_mutex false当满足以下条件时吞吐量可能优于非推测式互斥锁运行在支持硬件事务内存的处理器上多个线程可以并发进入互斥锁保护的临界区且大多互不冲突否则行为与spin_mutex类似吞吐量甚至可能更差。也就是说speculative_spin_mutex适合“临界区读多写少、冲突概率低、且 CPU 支持 TSX”的场合。3.4queuing_mutex公平排队互斥锁文档见 queuing_mutex_cls.rst。queuing_mutex建模 Mutex 需求不公平的致命缺点被消除它是公平的fair线程按照请求锁的先后顺序获得锁不可重入。声明于oneapi/tbb/queuing_mutex.hnamespace oneapi { namespace tbb { class queuing_mutex { public: queuing_mutex() noexcept; ~queuing_mutex(); queuing_mutex(const queuing_mutex) delete; queuing_mutex operator(const queuing_mutex) delete; class scoped_lock; static constexpr bool is_rw_mutex false; static constexpr bool is_recursive_mutex false; static constexpr bool is_fair_mutex true; }; } }公平性通过排队FIFO保证每个等待线程在队列中登记先到先得避免饥饿starvation。代价通常是比自旋锁更高的同步开销。3.5null_mutex空操作互斥锁文档见 null_mutex_cls.rst。null_mutex在语法层面建模 Mutex 需求但不做任何事。它用于实例化“期望一个 Mutex 类型参数、但该实例实际上不需要互斥”的模板。声明于oneapi/tbb/null_mutex.hnamespace oneapi { namespace tbb { class null_mutex { public: constexpr null_mutex() noexcept; ~null_mutex(); null_mutex(const null_mutex) delete; null_mutex operator(const null_mutex) delete; class scoped_lock; void lock(); bool try_lock(); void unlock(); static constexpr bool is_rw_mutex false; static constexpr bool is_recursive_mutex true; // 恒为 true static constexpr bool is_fair_mutex true; // 恒为 true }; } }lock()、try_lock()恒返回true、unlock()均为空操作。注意其 traits 与其余互斥类不同is_recursive_mutex true、is_fair_mutex true——因为空锁既不冲突也永不等待等价于“可重入且公平”。四、读写锁ReaderWriterMutex类逐个详解4.1rw_mutex自适应读写锁文档见 rw_mutex_cls.rst。rw_mutex以自适应方式建模 ReaderWriterMutex 需求等待线程先自旋再阻塞。它满足 ISO C [thread.sharedmutex.requirements] 的全部共享互斥需求是一个带写者偏好writer preference的不公平读写锁。声明于oneapi/tbb/rw_mutex.hnamespace oneapi { namespace tbb { class rw_mutex { public: rw_mutex() noexcept; ~rw_mutex(); rw_mutex(const rw_mutex) delete; rw_mutex operator(const rw_mutex) delete; class scoped_lock; // exclusive ownership void lock(); bool try_lock(); void unlock(); // shared ownership void lock_shared(); bool try_lock_shared(); void unlock_shared(); static constexpr bool is_rw_mutex true; static constexpr bool is_recursive_mutex false; static constexpr bool is_fair_mutex false; }; } }成员函数语义lock()获取写锁自适应等待try_lock()非阻塞尝试获取写锁成功返回true否则falseunlock()释放当前线程持有的写锁lock_shared()获取读锁自适应等待try_lock_shared()非阻塞尝试获取读锁成功返回true否则falseunlock_shared()释放当前线程持有的读锁。“写者偏好”意味着一旦有写者等待新到达的读者会被让位从而降低写者饥饿风险。4.2spin_rw_mutex自旋读写锁文档见 spin_rw_mutex_cls.rst。spin_rw_mutex建模 ReaderWriterMutex 需求满足 ISO C [thread.sharedmutex.requirements]是一个带退避backoff与写者偏好的不公平自旋读写锁。声明于oneapi/tbb/spin_rw_mutex.h接口与rw_mutex完全相同lock/try_lock/unlocklock_shared/try_lock_shared/unlock_sharedtraits 为true/false/false。语义要点lock()获取写锁若锁被占用则自旋try_lock()非阻塞尝试获取写锁unlock()释放写锁lock_shared()获取读锁若写锁已被占用则自旋try_lock_shared()非阻塞尝试获取读锁unlock_shared()释放读锁。由于是纯自旋实现它适合“持锁时间极短、读多写少”的临界区如保护单条计数器或标志位。4.3speculative_spin_rw_mutex推测式自旋读写锁文档见 speculative_spin_rw_mutex_cls.rst。该锁建模 ReaderWriterMutex 需求在支持硬件事务内存如 Intel® TSX的处理器上可实现为“允许无冲突修改并发执行”否则行为类似spin_rw_mutex吞吐量可能更差。声明于oneapi/tbb/spin_rw_mutex.his_fair_mutex false、is_recursive_mutex false。在支持 HTM 的处理器上规范描述其可实现为如下三档语义推测式读者与推测式写者互不阻塞speculative readers and writers do not block each other非推测式读者阻塞写者但允许推测式读者继续a non-speculative reader blocks writers but allows speculative readers非推测式写者阻塞所有读者与写者a non-speculative writer blocks all readers and writers。适用前提与speculative_spin_mutex相同CPU 支持 HTM且多个线程可并发进入临界区且大多无冲突。4.4queuing_rw_mutex公平排队读写锁文档见 queuing_rw_mutex_cls.rst。queuing_rw_mutex建模 ReaderWriterMutex 需求公平按请求顺序获取锁、不可重入。声明于oneapi/tbb/queuing_rw_mutex.h类体仅声明scoped_lock与三个 traitstrue/false/true读写锁获取经由scoped_lock的write参数区分。适合对公平性有硬性要求、且读多写少的共享数据结构。4.5null_rw_mutex空操作读写锁文档见 null_rw_mutex_cls.rst。null_rw_mutex在语法层面建模 ReaderWriterMutex 需求同样满足 ISO C [thread.sharedmutex.requirements] 的全部语法要求但不做任何事用于实例化“期望 ReaderWriterMutex 参数、但实际无需互斥”的模板。声明于oneapi/tbb/null_rw_mutex.hnamespace oneapi { namespace tbb { class null_rw_mutex { public: constexpr null_rw_mutex() noexcept; ~null_rw_mutex(); null_rw_mutex(const null_rw_mutex) delete; null_rw_mutex operator(const null_rw_mutex) delete; class scoped_lock; void lock(); bool try_lock(); void unlock(); void lock_shared(); bool try_lock_shared(); void unlock_shared(); static constexpr bool is_rw_mutex true; static constexpr bool is_recursive_mutex true; static constexpr bool is_fair_mutex true; }; } }所有操作均为空操作try_lock()与try_lock_shared()恒返回true。与null_mutex一样三个 traits 全为true其含义是“永远不会失败/阻塞”而非字面意义的公平与可重入。五、源码级验证自适应锁到底如何实现规范描述“自适应adaptive”与“先自旋再阻塞”其真实实现可以在 mutex.h 中直接看到。TBB 将tbb::mutex定义为detail::d1::mutex见第 88 行的using detail::d1::mutex类体核心如下class mutex { public: using scoped_lock unique_scoped_lockmutex; static constexpr bool is_rw_mutex false; static constexpr bool is_recursive_mutex false; static constexpr bool is_fair_mutex false; void lock() { call_itt_notify(prepare, this); while (!try_lock()) { my_flag.wait(true, /* context */ 0, std::memory_order_relaxed); } } bool try_lock() { bool result !my_flag.load(std::memory_order_relaxed) !my_flag.exchange(true); if (result) { call_itt_notify(acquired, this); } return result; } void unlock() { call_itt_notify(releasing, this); my_flag.exchange(false); my_flag.notify_one_relaxed(); } private: waitable_atomicbool my_flag{0}; };这段实现印证了规范的多项表述自适应自旋lock()内层循环while (!try_lock())即“自旋重试”而my_flag.wait(true, ...)是底层waitable_atomic可等待原子提供的“阻塞 等待唤醒”原语。二者结合正是“忙等一定时间后阻塞”的自适应逻辑非公平try_lock用!my_flag.load() !my_flag.exchange(true)抢占式获取后到线程可直接“插队”因此is_fair_mutex false不可重入my_flag是一个布尔标志而非“持有者线程 ID 计数”同一线程重复lock()会自旋死锁因此is_recursive_mutex falsescoped_lock由unique_scoped_lockmutex位于detail/_scoped_lock.h提供遵循规范要求的构造获取、析构释放模式代码中的call_itt_notify系列调用是 Intel ITTInstrumentation and Tracing Technology埋点配合create_itt_sync可在 VTune 等工具中观察锁竞争这也解释了构造函数中的create_itt_sync(this, tbb::mutex, )。rw_mutex的共享/独占两套接口lock/lock_shared等在其头文件 rw_mutex.h 中实现接口形态与规范文档 rw_mutex_cls.rst 一一对应。六、在 mold 中的真实应用链接器如何使用 spin_mutexmold 是一个现代、高速的链接器其链接过程会启动大量 worker 线程并行处理目标文件与符号因此互斥锁在 mold 源码中是真实存在的并发原语。从源码结构看mold 直接复用了本仓库third-party/tbb提供的互斥类——在 mold.h 中声明了tbb::spin_mutex mu;这样的成员用于保护需要原子性串行化的共享状态。这正是本系列文档的典型消费场景链接器符号合并、字符串池写入、输出段分配等临界区通常极短几次内存读写、一条哈希表操作持锁时间远低于一次线程上下文切换因此选用spin_mutex自旋而非让出 CPU比mutex先自旋后阻塞或系统级pthread_mutex更贴合性能需求——这也与规范中spin_mutex“Spins if the lock is taken” 的语义完全一致。若未来 mold 需要保护“读多写少”的共享结构如多线程并发查询、偶尔更新的映射表规范中的rw_mutex/spin_rw_mutex带lock_shared/try_lock_shared/unlock_shared共享所有权接口即是对应的备选方案若某个并行阶段实际单线程执行则可用null_mutex作为模板参数保留接口、去除开销。七、选型速查与使用注意事项综合两份命名需求文档中的保证总表可得到如下速查结论需求类FairReentrant等待策略典型场景MutexmutexNoNo自适应先自旋后阻塞通用互斥临界区较长Mutexspin_mutexNoNo纯自旋极短临界区如保护计数器Mutexspeculative_spin_mutexNoNo自旋 HTM 推测执行冲突率低的读多写少临界区需 TSXMutexqueuing_mutexYesNoFIFO 排队需公平、防饥饿Mutexnull_mutexYesYes空操作模板参数占位实际不需互斥ReaderWriterrw_mutexNoNo自适应读多写少、写者偏好ReaderWriterspin_rw_mutexNoNo自旋 退避读多写少且临界区极短ReaderWriterspeculative_spin_rw_mutexNoNo自旋 HTM 推测执行无冲突并发读写需 TSXReaderWriterqueuing_rw_mutexYesNoFIFO 排队需公平的读写锁ReaderWriternull_rw_mutexYesYes空操作读写锁模板参数占位使用时的关键注意事项统一走scoped_lock规范强制作用域锁模式构造即加锁、析构即解锁异常安全手动lock()/unlock()需自行保证配对且无法获得异常安全不可拷贝、不可移动mutex与其scoped_lock均删除拷贝/移动构造与赋值无法放入 STL 容器按值传递通常用指针或引用间接持有持锁状态下销毁是未定义行为所有类的析构函数均要求“销毁未锁定的互斥锁”升级/降级语义读写锁的upgrade_to_writer()/downgrade_to_reader()返回false表示“锁曾被释放并重新获取”调用方需据此重新校验依赖不变式当前实现中downgrade_to_reader恒返回true推测式锁的前提speculative_*系列仅在支持硬件事务内存如 Intel® TSX的处理器上才可能获得吞吐收益且要求临界区冲突率低否则性能可能反而更差否定保证可被“加强”规范注释指出表中标注“No”的保证实现允许给出相反的肯定保证更优实现因此 trait 值以各头文件中的static constexpr为准。如需深入阅读规范原文与全部子文档位于 specification/source/mutual_exclusion.rst 及 mutual_exclusion/ 目录两份命名需求详见于 named_requirements/mutexes/mutex.rst 与 named_requirements/mutexes/rw_mutex.rst各互斥类的真实声明可对照 third-party/tbb/include/oneapi/tbb/ 下的同名头文件逐一验证。【免费下载链接】moldmold: A Modern Linker 项目地址: https://gitcode.com/GitHub_Trending/mo/mold创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考