如何用C++ std::atomic_flag实现忙等自旋锁Spinlock

来源:草根站长作者:松松建站头衔:草根站长
导读:本期聚焦于小伙伴创作的《如何用C++ std::atomic_flag实现忙等自旋锁Spinlock》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《如何用C++ std::atomic_flag实现忙等自旋锁Spinlock》有用,将其分享出去将是对创作者最好的鼓励。

自旋锁是一种忙等锁,当线程尝试获取锁时如果锁已被占用,线程不会进入休眠状态,而是会不断循环检查锁的状态直到获取成功,这种特性让它在临界区执行耗时极短的场景下比互斥锁有更低的切换开销。

如何用C++ std::atomic_flag实现忙等自旋锁Spinlock

自旋锁的实现原理

自旋锁的核心逻辑包含两个操作:获取锁和释放锁。获取锁时尝试将锁的状态从空闲修改为占用,如果修改失败就不断重试;释放锁时将锁的状态改回空闲。要实现正确的自旋锁,这两个操作必须是原子操作,否则会出现多个线程同时获取到锁的问题。

C++11引入的std::atomic_flag是最简单的原子类型,它只有两种状态:设置态和清除态,提供的test_and_setclear方法都是原子操作,非常适合用来实现自旋锁的底层状态管理。

基于std::atomic_flag的自旋锁实现

基础自旋锁实现

下面是一个最基础的自旋锁实现,包含构造、加锁、解锁、尝试加锁四个核心方法:

#include <atomic>
#include <thread>

class SpinLock {
private:
    // atomic_flag默认初始化为清除态,即锁空闲
    std::atomic_flag flag = ATOMIC_FLAG_INIT;

public:
    // 禁止拷贝构造和赋值,避免锁状态被错误复制
    SpinLock(const SpinLock&) = delete;
    SpinLock& operator=(const SpinLock&) = delete;

    SpinLock() = default;

    // 加锁:不断尝试将flag设置为设置态,直到成功
    void lock() {
        // test_and_set原子操作:返回flag之前的状态,然后将flag设为设置态
        // 如果之前是清除态(返回false),说明加锁成功;否则循环重试
        while (flag.test_and_set(std::memory_order_acquire)) {
            // 忙等,线程会一直循环检查flag状态
        }
    }

    // 解锁:将flag恢复为清除态
    void unlock() {
        // memory_order_release保证解锁前的所有操作对获取锁的线程可见
        flag.clear(std::memory_order_release);
    }

    // 尝试加锁:如果锁空闲则获取,否则立即返回
    bool try_lock() {
        // 尝试设置flag,返回true说明之前是清除态,加锁成功
        return !flag.test_and_set(std::memory_order_acquire);
    }
};

内存序说明

上面的实现中使用了两种内存序:

  • std::memory_order_acquire:用在加锁和尝试加锁的test_and_set操作,保证当前线程后续的所有读操作不会被重排到该操作之前,同时能看到其他线程释放锁前的所有写操作。
  • std::memory_order_release:用在解锁的clear操作,保证当前线程解锁前的所有写操作不会被重排到该操作之后,同时能被后续获取锁的线程看到。

这两种内存序搭配使用,能够保证自旋锁的临界区操作的可见性,避免出现数据竞争问题。

自旋锁使用示例

下面是一个多线程使用自旋锁保护共享变量的示例:

#include <iostream>
#include <vector>
#include <thread>

// 共享变量
int shared_counter = 0;
// 定义自旋锁
SpinLock spin_lock;

// 线程执行的函数,对共享变量加1000次
void increment_task(int times) {
    for (int i = 0; i < times; ++i) {
        spin_lock.lock();
        ++shared_counter;
        spin_lock.unlock();
    }
}

int main() {
    const int thread_num = 4;
    const int increment_times = 1000;
    std::vector<std::thread> threads;

    // 创建4个线程
    for (int i = 0; i < thread_num; ++i) {
        threads.emplace_back(increment_task, increment_times);
    }

    // 等待所有线程执行完成
    for (auto& t : threads) {
        t.join();
    }

    // 预期结果是4*1000=4000
    std::cout << "最终共享变量值: " << shared_counter << std::endl;
    return 0;
}

编译运行后,输出的结果会是4000,说明自旋锁正确保护了共享变量的修改操作,没有出现数据竞争。

自旋锁的适用场景和注意事项

适用场景

  • 临界区的执行时间非常短,通常在微秒级别,忙等的开销小于线程休眠唤醒的开销。
  • 线程竞争不激烈,不会长时间占用CPU循环等待。
  • 不能休眠的场景,比如中断处理、内核态代码等。

注意事项

  • 不要在单核CPU上使用自旋锁,因为单核下持有锁的线程无法同时运行,等待线程会一直占用CPU空转,浪费资源。
  • 临界区不要包含可能阻塞的操作,比如IO、内存分配等,否则会导致等待线程长时间忙等,占用大量CPU。
  • 自旋锁不支持递归获取,同一个线程连续调用两次lock会导致死锁。
  • 如果竞争比较激烈,建议使用互斥锁std::mutex,避免大量CPU资源浪费在忙等上。

带退避策略的自旋锁优化

基础的自旋锁在竞争激烈时会大量占用CPU,我们可以加入退避策略,当多次获取锁失败后,让线程短暂让出CPU,减少资源浪费:

#include <atomic>
#include <thread>
#include <chrono>

class BackoffSpinLock {
private:
    std::atomic_flag flag = ATOMIC_FLAG_INIT;

public:
    BackoffSpinLock(const BackoffSpinLock&) = delete;
    BackoffSpinLock& operator=(const BackoffSpinLock&) = delete;
    BackoffSpinLock() = default;

    void lock() {
        int retry_count = 0;
        while (flag.test_and_set(std::memory_order_acquire)) {
            ++retry_count;
            // 重试次数较少时,短暂让出CPU
            if (retry_count < 10) {
                std::this_thread::yield();
            } else if (retry_count < 50) {
                // 重试次数较多时,休眠更长时间
                std::this_thread::sleep_for(std::chrono::microseconds(10));
            } else {
                // 重试次数非常多时,休眠更久
                std::this_thread::sleep_for(std::chrono::milliseconds(1));
            }
        }
    }

    void unlock() {
        flag.clear(std::memory_order_release);
    }

    bool try_lock() {
        return !flag.test_and_set(std::memory_order_acquire);
    }
};

这种带退避策略的自旋锁在竞争不激烈时性能和基础自旋锁接近,在竞争激烈时能减少CPU的浪费,适用性更强。

C++std::atomic_flagSpinlock忙等自旋锁修改时间:2026-06-25 20:13:02

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。