当前位置: 首页 > article >正文

一个C++线程安全的栈数据结构的例子

C++ 线程安全栈数据结构示例

在多线程编程中,确保数据结构线程安全非常重要。下面是一个简单的线程安全栈数据结构的实现示例,并详细说明设计要点和容易忽略的安全点。

#include <iostream>
#include <stack>
#include <mutex>
#include <thread>
#include <condition_variable>

template <typename T>
class ThreadSafeStack {
private:
    std::stack<T> data;
    mutable std::mutex mtx; // 互斥锁,用于保护数据
    std::condition_variable cond; // 条件变量,用于线程同步

public:
    // 构造函数
    ThreadSafeStack() : data(), mtx(), cond() {}

    // 禁用拷贝构造函数和赋值操作符
    ThreadSafeStack(const ThreadSafeStack&) = delete;
    ThreadSafeStack& operator=(const ThreadSafeStack&) = delete;

    // 入栈操作
    void push(T value) {
        std::lock_guard<std::mutex> lock(mtx); // 加锁
        data.push(value);
        cond.notify_one(); // 通知等待的线程
    }

    // 出栈操作,如果栈为空则等待
    void pop(T& value) {
        std::unique_lock<std::mutex> lock(mtx); // 加锁
        cond.wait(lock, [this] { return !data.empty(); }); // 等待直到栈不为空
        value = data.top();
        data.pop();
    }

    // 尝试出栈操作,如果栈为空则返回false
    bool try_pop(T& value) {
        std::lock_guard<std::mutex> lock(mtx); // 加锁
        if (data.empty()) {
            return false;
        }
        value = data.top();
        data.pop();
        return true;
    }

    // 检查栈是否为空
    bool empty() const {
        std::lock_guard<std::mutex> lock(mtx); // 加锁
        return data.empty();
    }

    // 返回栈的大小
    size_t size() const {
        std::lock_guard<std::mutex> lock(mtx); // 加锁
        return data.size();
    }
};

// 示例使用
void push_data(ThreadSafeStack<int>& stack, int start, int end) {
    for (int i = start; i < end; ++i) {
        stack.push(i);
    }
}

void pop_data(ThreadSafeStack<int>& stack, int count) {
    for (int i = 0; i < count; ++i) {
        int value;
        stack.pop(value);
        std::cout << "Popped: " << value << std::endl;
    }
}

int main() {
    ThreadSafeStack<int> stack;

    std::thread t1(push_data, std::ref(stack), 0, 10);
    std::thread t2(push_data, std::ref(stack), 10, 20);
    std::thread t3(pop_data, std::ref(stack), 10);
    std::thread t4(pop_data, std::ref(stack), 10);

    t1.join();
    t2.join();
    t3.join();
    t4.join();

    return 0;
}


设计要点


互斥锁(Mutex):使用std::mutex来保护对共享数据的所有访问。确保在访问或修改栈数据时,始终持有互斥锁。

条件变量(Condition Variable):使用std::condition_variable来同步线程。在pop操作中,如果栈为空,线程会等待,直到有数据入栈。这样可以避免忙等待,节省CPU资源。

拷贝构造函数和赋值操作符:为了避免竞态条件和不必要的复杂性,禁用了拷贝构造函数和赋值操作符。

线程安全操作:所有对栈数据的操作(如push、pop、try_pop、empty、size)都在互斥锁的保护下进行,确保线程安全。

容易忽略的安全点


条件变量的误用:在使用条件变量时,必须确保在调用wait之前已经持有互斥锁,并且在notify之前释放互斥锁。否则,可能会导致线程死锁或错过通知。

锁的粒度:在某些情况下,锁的粒度可能会影响性能。如果锁的粒度过大(例如在每个操作中都持有锁),可能会导致性能瓶颈。在设计时,需要权衡锁的粒度和并发性能。

异常安全:在多线程环境中,异常处理变得更加复杂。确保在抛出异常时,锁能够正确释放,避免死锁。

内存模型:在多线程环境中,内存模型的选择会影响线程之间的可见性和顺序性。确保在必要时使用std::atomic或std::memory_order来控制内存顺序。

通过以上设计要点和注意事项,可以实现一个简单且线程安全的栈数据结构,确保在多线程环境下的正确性和性能。


std::unique_lock<std::mutex>和std::lock_guard<std::mutex>应用说明


在C++中,std::unique_lock和std::lock_guard都是用于管理互斥锁的RAII(Resource Acquisition Is Initialization)类。它们的主要区别在于灵活性和功能性。下面详细说明它们的应用场景和区别。

std::lock_guard


特点
简单:std::lock_guard是一个轻量级的锁管理工具,使用非常简单。
固定作用域:std::lock_guard的生命周期与其所在的代码块(通常是函数或循环)一致,当作用域结束时,锁自动释放。
不可移动:std::lock_guard不支持移动语义,因此不能转移到另一个作用域。


应用场景
简单的锁管理:当只需要在特定作用域内锁定和解锁互斥锁时,使用std::lock_guard非常方便。
自动释放锁:确保在作用域结束时自动释放锁,避免忘记解锁导致的死锁。

示例

void some_function(std::mutex& mtx) {
    std::lock_guard<std::mutex> lock(mtx); // 锁定互斥锁
    // 执行需要保护的操作
} // 作用域结束,自动释放锁


std::unique_lock


特点
灵活:std::unique_lock提供了更高级的功能,包括延迟锁定、手动解锁、转移所有权等。
可移动:std::unique_lock支持移动语义,可以转移到另一个作用域。
条件变量配合:std::unique_lock与std::condition_variable一起使用时,可以支持wait和notify操作。
应用场景
延迟锁定:当需要延迟锁定互斥锁时,例如在某些条件满足时才锁定。
手动解锁:在某些情况下,需要在作用域结束前手动解锁互斥锁。
条件变量:与std::condition_variable一起使用,实现复杂的线程同步。
锁的转移:当需要将锁的所有权从一个线程转移到另一个线程时。
示例

void some_function(std::mutex& mtx, std::condition_variable& cond, bool& ready) {
    std::unique_lock<std::mutex> lock(mtx); // 锁定互斥锁
    
    // 等待某个条件变量
    cond.wait(lock, [&ready]{ return ready; }); // 等待ready为true
    
    // 执行需要保护的操作
    
    lock.unlock(); // 手动解锁
    
    // 其他不需要保护的操作
}


总结


std::lock_guard 适用于简单的锁管理,主要用于在固定的作用域内锁定和解锁互斥锁。
std::unique_lock 适用于更复杂的场景,如延迟锁定、手动解锁、与条件变量配合使用以及锁的转移。
在选择使用哪种锁管理工具时,应根据具体需求和场景来决定。如果只需要简单的锁管理,std::lock_guard是一个很好的选择;如果需要更高级的功能,例如与条件变量配合使用,那么std::unique_lock是更合适的选择。


http://www.kler.cn/a/395984.html

相关文章:

  • Array数组方法
  • 昆明华厦眼科医院举办中外专家眼科技术研讨会
  • 【Zabbix自动化运维监控系列】判断zabbix是主动监控,还是被动监控
  • STM32学习笔记-----UART的概念
  • Java I/O(输入/输出)——针对实习面试
  • 【缺陷检测】Anomaly Detection via Reverse Distillation from One-Class Embedding
  • 电子商务系统设计与Spring Boot框架
  • 对话 OpenCV 之父 Gary Bradski:灾难性遗忘和持续学习是尚未解决的两大挑战 | Open AGI Forum
  • cls(c基础)
  • Excel单元格中自适应填充多图
  • SpringCloud核心组件(三)
  • 6547网:青少年软件编程Python等级考试(六级)真题试卷
  • 一键抠图:免费安全的在线图片去除背景工具
  • uniapp 实现 ble蓝牙同时连接多台蓝牙设备,支持app、苹果(ios)和安卓手机,以及ios连接蓝牙后的一些坑
  • 鸿蒙元服务 证书过期替换 ERROR - hap-sign-tool: error: Signature verify failed
  • Spring Boot3 实战案例合集上线了
  • Shell脚本:遍历目录下的文件
  • 深度学习神经网络在机器人领域应用的深度剖析:原理、实践与前沿探索
  • Hive的map/struct/array怎么通过insert插入数据
  • 硬件工程师之电子元器件—二极管(6)之肖特基二极管特性
  • STM32 BootLoader 刷新项目 (十) Flash擦除-命令0x56
  • SQL进阶写法
  • 机器视觉和计算机视觉的区别
  • 提升前端性能:如何优化多个异步请求的执行效率Promise.all()
  • python isinstance(True, int)
  • Web_前端_HTML入门学习的案例案例1