如何用C++基于atomic_flag自旋锁实现高并发队列

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

在高并发编程场景中,多线程对共享数据结构的访问需要保证线程安全,传统互斥锁在竞争时会触发线程上下文切换,带来额外的性能开销。自旋锁通过忙等待的方式避免上下文切换,更适合短临界区的并发场景,而C++标准库提供的atomic_flag是实现自旋锁的理想原子类型,它保证了操作的原子性且无锁特性。

如何用C++基于atomic_flag自旋锁实现高并发队列

自旋锁的实现原理

自旋锁的核心逻辑是当一个线程尝试获取锁时,如果锁已经被其他线程持有,该线程会循环检查锁的状态直到获取成功,而不是进入休眠状态。C++的atomic_flag类型只有两种状态:设置和清除,它的test_and_set操作是原子性的,非常适合用来实现自旋锁的获取和释放逻辑。

基础自旋锁实现

下面是一个基于atomic_flag的简单自旋锁实现:

#include <atomic>

class SpinLock {
private:
    // atomic_flag默认初始化为清除状态
    std::atomic_flag flag = ATOMIC_FLAG_INIT;

public:
    // 获取锁,自旋等待直到成功
    void lock() {
        // test_and_set原子地设置flag为true,返回之前的状态
        // 如果之前是false,说明获取锁成功,退出循环
        // 如果之前是true,说明锁被持有,继续循环
        while (flag.test_and_set(std::memory_order_acquire)) {
            // 可以在这里加入pause指令优化CPU占用,不同平台实现不同
            // 例如x86平台可以用__asm__ __volatile__("pause")
        }
    }

    // 释放锁
    void unlock() {
        // 清除flag状态,释放锁
        flag.clear(std::memory_order_release);
    }
};

这里的memory_order_acquirememory_order_release是内存序参数,保证了锁操作前后的内存可见性,避免指令重排带来的问题。

基于自旋锁的高并发队列实现

有了自旋锁之后,我们可以构建一个线程安全的队列,队列的入队和出队操作都需要加锁保证同一时间只有一个线程修改队列结构。这里采用单向链表作为队列的底层存储结构,方便实现入队和出队操作。

队列结构设计

队列包含头节点和尾节点,头节点是哨兵节点,不存储实际数据,尾节点指向最后一个存储数据的节点。自旋锁用来保护对头尾节点和节点数据的修改操作。

完整源码实现

#include <atomic>
#include <memory>

// 自旋锁定义,同上
class SpinLock {
private:
    std::atomic_flag flag = ATOMIC_FLAG_INIT;

public:
    void lock() {
        while (flag.test_and_set(std::memory_order_acquire)) {
            // 可选:加入CPU pause优化
        }
    }

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

// 队列节点结构
template <typename T>
struct QueueNode {
    T data;
    std::unique_ptr<QueueNode<T>> next;

    QueueNode(T val) : data(std::move(val)), next(nullptr) {}
};

// 高并发队列实现
template <typename T>
class ConcurrentQueue {
private:
    // 哨兵头节点
    std::unique_ptr<QueueNode<T>> head;
    // 尾节点指针,指向最后一个数据节点
    QueueNode<T>* tail;
    // 自旋锁实例
    SpinLock lock;

public:
    ConcurrentQueue() {
        // 初始化头节点为哨兵节点,尾节点指向头节点
        head = std::make_unique<QueueNode<T>>(T());
        tail = head.get();
    }

    // 禁止拷贝构造和赋值
    ConcurrentQueue(const ConcurrentQueue&) = delete;
    ConcurrentQueue& operator=(const ConcurrentQueue&) = delete;

    // 入队操作
    void enqueue(T val) {
        // 创建新节点
        auto new_node = std::make_unique<QueueNode<T>>(std::move(val));
        QueueNode<T>* new_node_ptr = new_node.get();

        lock.lock();
        // 将新节点接到尾节点后面
        tail->next = std::move(new_node);
        // 更新尾节点指针
        tail = new_node_ptr;
        lock.unlock();
    }

    // 出队操作,成功返回true,失败(队列为空)返回false
    bool dequeue(T& result) {
        lock.lock();
        // 检查队列是否为空:头节点的next为空说明没有数据节点
        if (head->next == nullptr) {
            lock.unlock();
            return false;
        }
        // 取出第一个数据节点
        auto first_data_node = std::move(head->next);
        result = std::move(first_data_node->data);
        // 更新头节点为原来的第一个数据节点,原来的哨兵节点会被自动释放
        head = std::move(first_data_node);
        lock.unlock();
        return true;
    }

    // 判断队列是否为空
    bool empty() {
        lock.lock();
        bool is_empty = (head->next == nullptr);
        lock.unlock();
        return is_empty;
    }
};

代码测试示例

下面是一个简单的多线程测试示例,验证队列的线程安全性:

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

int main() {
    ConcurrentQueue<int> queue;
    const int THREAD_NUM = 4;
    const int OPER_NUM = 1000;

    // 生产者线程,每个线程入队1000个数据
    std::vector<std::thread> producers;
    for (int i = 0; i < THREAD_NUM; ++i) {
        producers.emplace_back([&queue, i]() {
            for (int j = 0; j < OPER_NUM; ++j) {
                queue.enqueue(i * OPER_NUM + j);
            }
        });
    }

    // 消费者线程,每个线程出队直到队列为空
    std::vector<std::thread> consumers;
    std::atomic<int> total_count(0);
    for (int i = 0; i < THREAD_NUM; ++i) {
        consumers.emplace_back([&queue, &total_count]() {
            int val;
            while (queue.dequeue(val)) {
                total_count.fetch_add(1, std::memory_order_relaxed);
            }
        });
    }

    // 等待所有生产者完成
    for (auto& t : producers) {
        t.join();
    }

    // 等待所有消费者完成
    for (auto& t : consumers) {
        t.join();
    }

    std::cout << "Total dequeued elements: " << total_count << std::endl;
    // 预期输出4000,即4个线程每个入队1000个
    return 0;
}

注意事项

  • 自旋锁适合临界区执行时间短的场景,如果临界区执行时间长,忙等待会浪费大量CPU资源,此时更适合用互斥锁。
  • atomic_flag是C++中唯一保证无锁的原子类型,其他原子类型不一定是无锁的,使用前可以通过is_always_lock_free检查。
  • 本示例的队列实现中,自旋锁保护了整个入队出队操作,实际场景中可以根据需求优化锁粒度,进一步提升并发性能。
  • 内存序的选择需要根据实际场景调整,本示例使用的acquire和release语义可以满足大部分自旋锁的场景需求。

C++atomic_flag自旋锁高并发队列修改时间:2026-07-19 18:57:29

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