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是更合适的选择。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |