为何Mutex可跨线程安全使用?线程安全相关疑问解析
Mutex线程安全核心问题解析
技术疑问
- 多线程同时读写同一变量会引发未定义行为甚至崩溃,但Mutex本身也是变量,为何不会出现此类问题?
- 若Mutex能实现多线程下的安全访问,为何不直接让所有变量无需加锁即可实现多线程安全访问?
研究过程
在Visual Studio中查看mutex::lock的定义,最终定位到无实现的_Mtx_lock函数,随后找到其Windows平台实现代码如下:
int _Mtx_lock(_Mtx_t mtx) { /* lock mutex */ return (mtx_do_lock(mtx, 0)); } static int mtx_do_lock(_Mtx_t mtx, const xtime *target) { /* lock mutex */ if ((mtx->type & ~_Mtx_recursive) == _Mtx_plain) { /* set the lock */ if (mtx->thread_id != static_cast<long>(GetCurrentThreadId())) { /* not current thread, do lock */ mtx->_get_cs()->lock(); mtx->thread_id = static_cast<long>(GetCurrentThreadId()); } ++mtx->count; return (_Thrd_success); } else { /* handle timed or recursive mutex */ int res = WAIT_TIMEOUT; if (target == 0) { /* no target --> plain wait (i.e. infinite timeout) */ if (mtx->thread_id != static_cast<long>(GetCurrentThreadId())) mtx->_get_cs()->lock(); res = WAIT_OBJECT_0; } else if (target->sec < 0 || target->sec == 0 && target->nsec <= 0) { /* target time <= 0 --> plain trylock or timed wait for */ /* time that has passed; try to lock with 0 timeout */ if (mtx->thread_id != static_cast<long>(GetCurrentThreadId())) { /* not this thread, lock it */ if (mtx->_get_cs()->try_lock()) res = WAIT_OBJECT_0; else res = WAIT_TIMEOUT; } else res = WAIT_OBJECT_0; } else { /* check timeout */ xtime now; xtime_get(&now, TIME_UTC); while (now.sec < target->sec || now.sec == target->sec && now.nsec < target->nsec) { /* time has not expired */ if (mtx->thread_id == static_cast<long>(GetCurrentThreadId()) || mtx->_get_cs()->try_lock_for( _Xtime_diff_to_millis2(target, &now))) { /* stop waiting */ res = WAIT_OBJECT_0; break; } else res = WAIT_TIMEOUT; xtime_get(&now, TIME_UTC); } } if (res != WAIT_OBJECT_0 && res != WAIT_ABANDONED) ; else if (1 < ++mtx->count) { /* check count */ if ((mtx->type & _Mtx_recursive) != _Mtx_recursive) { /* not recursive, fixup count */ --mtx->count; res = WAIT_TIMEOUT; } } else mtx->thread_id = static_cast<long>(GetCurrentThreadId()); switch (res) { case WAIT_OBJECT_0: case WAIT_ABANDONED: return (_Thrd_success); case WAIT_TIMEOUT: if (target == 0 || (target->sec == 0 && target->nsec == 0)) return (_Thrd_busy); else return (_Thrd_timedout); default: return (_Thrd_error); } } }
结合atomic_关键字,我推导Mutex可通过如下简化方式实现:
atomic_bool state = false; void lock() { if(!state) state = true; else while(state){} } void unlock() { state = false; } bool try_lock() { if(!state) state = true; else return false; return true; }
问题解答
1. Mutex本身为何能避免多线程读写问题?
Mutex的核心实现依赖硬件原子指令或操作系统内核同步原语,这两类机制都能保证对Mutex内部状态的读写是原子性的,不会出现普通变量那样的指令交错问题:
- 推导代码中使用的
atomic_bool,其读写操作由编译器和硬件保证原子性,完全规避了多线程下的未定义行为; - Windows平台的
mutex实现里调用的_get_cs()->lock(),底层是通过原子操作检查锁状态,若获取失败则让线程进入内核等待队列,既保证了状态变更的安全性,又避免了空耗CPU。
2. 为何不让所有变量都自动实现线程安全?
这是性能、灵活性与语义清晰性三方权衡的结果:
- 性能开销:同步操作(原子指令或锁)会带来缓存一致性开销,若涉及内核等待还会有态切换开销。如果所有变量默认加锁,单线程场景下的无意义同步会大幅拖慢程序运行效率;
- 场景多样性:很多变量不需要全局同步(比如线程局部变量、只读变量),还有些场景需要更细粒度的同步策略(比如读写锁、条件变量),强制统一的线程安全会剥夺开发者根据实际场景优化的能力;
- 语义清晰性:显式加锁能让临界区逻辑一目了然,避免隐式同步带来的调试复杂度——开发者能明确知道哪些代码块需要多线程互斥,排查问题时更高效。
内容的提问来源于stack exchange,提问作者Ngdgvcb
相关产品推荐
相关产品推荐

