科技颠覆者 发表于 2025-3-25 04:26:37

一个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, { 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是更合适的选择。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页: [1]
查看完整版本: 一个C++线程安全的栈数据结构的例子