自旋锁为何循环执行30次_mm_pause()?相关实现合理性问询
问题解答
1. 为什么要执行30次_mm_pause()?CPU在这段时间做什么?
首先明确:_mm_pause()不是让CPU空闲,它是x86架构下的提示性指令,核心作用是告诉CPU当前处于自旋等待状态,需要调整流水线行为以降低性能损耗。
CPU执行_mm_pause()时的具体行为:
- 占用少量指令周期(Intel CPU上约10-14个周期),这段时间CPU不会执行有效计算,但也不会进入休眠;
- 抑制乱序执行与预取操作:若无
pause指令,CPU会尝试预测后续指令并提前执行,而自旋等待中这类预测几乎都会失败,导致流水线清空、缓存失效,反而浪费更多资源;pause能让CPU避免此类无用功; - 降低功耗:相比空循环,
pause会让CPU进入低功耗自旋状态,减少发热。
至于为什么是30次而非1次?这是实践总结的经验值:
- 自旋锁的核心优势是:若锁的持有时间很短,自旋等待的开销远小于线程切换(涉及内核态调度、上下文保存);
- 30次
pause的总时长(约300-420个周期)刚好覆盖大多数短持有锁的释放窗口——比如很多临界区操作仅需几十到上百个周期,30次等待足够等到锁释放,又不会让CPU空转太久; - 若只执行1次,可能锁还未释放就放弃自旋触发线程切换,反而浪费了自旋的优势;次数过多则会在锁长时间持有时空转浪费CPU。
2. 基于C++协程的自旋锁实现是否合理?
这个实现的核心思路是可行的,但存在多处需要修正和优化的点:
合理的地方
- 采用分阶段自旋+协程让出的策略:先尝试快速获取锁,再逐步增加
pause等待时间,最后让出协程避免长时间空转,平衡了自旋的性能优势与CPU利用率; - 内存序选择正确:
test_and_set使用std::memory_order_acquire符合自旋锁语义——获取锁后,能看到其他线程释放锁前的所有操作。
存在的问题与优化点
std::atomic_flag初始化问题:std::atomic_flag默认处于未初始化状态,必须显式用ATOMIC_FLAG_INIT初始化,否则行为未定义。需补充构造函数:Spinlock() : m_flag(ATOMIC_FLAG_INIT) {}- 快速路径重复操作:连续两次
test_and_set完全冗余,可去掉一次; - 协程让出的实现依赖:
co_await Yield{}的效果完全依赖Yield的实现,必须确保它能让当前协程真正让出CPU,让线程执行其他任务,否则仍会空转; - 缺少unlock函数:自旋锁必须配套释放逻辑,需添加:
这里用void unlock() { m_flag.clear(std::memory_order_release); }memory_order_release保证释放锁前的所有操作对其他线程可见; - pause次数的选择:1、3、5次的阶梯式等待可行,也可参考Go的固定次数(如30次)或指数退避策略,根据实际场景调整;
- 代码可读性优化:协程中用
goto again虽合法,但改成while(true)循环结构会更易读。
优化后的简化示例:
#include <atomic> #include <coroutine> inline void cpu_pause() { _mm_pause(); } template<unsigned int Times> inline void cpu_pauses() { for (unsigned int i = 0; i < Times; ++i) cpu_pause(); } struct Yield { bool await_ready() const noexcept { return false; } void await_suspend(std::coroutine_handle<> h) const noexcept { // 需结合协程调度器实现,将当前协程加入就绪队列以让出CPU // 示例:scheduler::schedule(h); } void await_resume() const noexcept {} }; struct Spinlock { std::atomic_flag m_flag; Spinlock() : m_flag(ATOMIC_FLAG_INIT) {} Spinlock(const Spinlock&) = delete; Spinlock& operator=(const Spinlock&) = delete; struct promise_type { std::suspend_never initial_suspend() noexcept { return {}; } std::suspend_never final_suspend() noexcept { return {}; } void return_void() noexcept {} void unhandled_exception() noexcept { std::terminate(); } auto get_return_object() noexcept { return std::coroutine_handle<promise_type>::from_promise(*this); } }; using async_void = std::coroutine_handle<promise_type>; async_void lock() { while (true) { // 快速路径 if (!m_flag.test_and_set(std::memory_order_acquire)) { co_return; } cpu_pauses<1>(); if (!m_flag.test_and_set(std::memory_order_acquire)) co_return; cpu_pauses<3>(); if (!m_flag.test_and_set(std::memory_order_acquire)) co_return; cpu_pauses<5>(); if (!m_flag.test_and_set(std::memory_order_acquire)) co_return; // 让出协程 co_await Yield{}; } } void unlock() { m_flag.clear(std::memory_order_release); } };
内容的提问来源于stack exchange,提问作者likecpp
相关产品推荐
相关产品推荐

