并发编程数据结构-栈
有锁栈
Stack1 - 基础线程安全栈
Stack1 是一个简单的线程安全栈实现,使用了 std::mutex 来保证 push 和 pop 操作的原子性。主要特点包括:
- 使用 std::lock_guard 确保操作期间栈的线程安全。
- 提供了两种 push 操作(左值引用和右值引用),优化了性能。
- pop 操作抛出自定义的 EmptyStackError 异常,以处理栈为空的情况。
- 提供了 pop(T& v) 和 pop() 两种接口,方便用户选择直接传引用或返回 shared_ptr。
- 问题:用户需要不断检查 empty() 并手动处理栈空的情况,这在多线程环境下并不高效。
template <typename T>
class Stack1
{
public:
Stack1() = default;
~Stack1() = default;
Stack1(const Stack1&) = delete;
Stack1& operator=(const Stack1&) = delete;
Stack1(Stack1&& other) noexcept{
std::scoped_lock lock(_mtx, other._mtx);
_stack = std::move(other._stack);
}
Stack1& operator=(Stack1&& other) noexcept
{
if(this != &other)
{
std::scoped_lock lock(_mtx, other._mtx);
_stack = std::move(other._stack);
}
return *this;
}
void push(const T&v)
{
std::lock_guard<std::mutex> lock(_mtx);
_stack.emplace(v);
}
void push(T &&v)
{
std::lock_guard<std::mutex> lock(_mtx);
_stack.emplace(std::move(v));
}
void pop(T &v)
{
std::lock_guard<std::mutex> lock(_mtx);
if(_stack.empty()) throw EmptyStackError();
v = std::move(_stack.top());
_stack.pop();
}
std::shared_ptr<T> pop()
{
std::lock_guard<std::mutex> lock(_mtx);
if(_stack.empty()) throw EmptyStackError();
auto res = std::make_shared<T>(std::move(_stack.top()));
_stack.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lock(_mtx);
return _stack.empty();
}
private:
std::stack<T> _stack;
mutable std::mutex _mtx;
};
Stack2 - 引入条件变量的栈
Stack2 在 Stack1 的基础上,通过引入条件变量 std::condition_variable 改进了 pop 操作,使得线程在栈为空时可以阻塞等待,直到有数据入栈时才唤醒。
- 使用 wait_pop 代替普通的 pop,在栈为空时阻塞线程,避免繁琐的轮询检查。
- 每次 push 操作后,会通知阻塞的线程,解除等待状态。
- 问题:在
wait_pop
中,如果在获得了cv的通知之后抛出异常,导致其他的线程无法获得该notify,因此全部阻塞。
template<typename T>
class Stack2
{
public:
Stack2() = default;
~Stack2() = default;
Stack2(const Stack2&) = delete;
Stack2& operator=(const Stack2&) = delete;
Stack2(Stack2&& other) noexcept
{
std::scoped_lock lock(_mtx, other._mtx);
_stack = std::move(other._stack);
}
Stack2& operator=(Stack2&& other) noexcept
{
if(this != &other)
{
std::scoped_lock lock(_mtx, other._mtx);
_stack = std::move(other._stack);
}
return *this;
}
void push(const T& v) // 入栈,唤醒阻塞的线程
{
std::unique_lock<std::mutex> lock(_mtx);
_stack.emplace(v);
_cv.notify_one();
}
void push(T&& v) // 入栈,唤醒阻塞的线程
{
std::unique_lock<std::mutex> lock(_mtx);
_stack.emplace(std::move(v));
_cv.notify_one();
}
void wait_pop(T& v)
{
std::unique_lock<std::mutex> lock(_mtx);
_cv.wait(lock, [this]()->bool{return !_stack.empty();});
v = std::move(_stack.top());
_stack.pop();
}
std::shared_ptr<T> wait_pop()
{
std::unique_lock<std::mutex> lock(_mtx);
_cv.wait(lock, [this]()->bool{return !_stack.empty();});
auto res = std::make_shared<T>(std::move(_stack.top())); // 当此处抛出异常,由于本线程获得了cv的通知,导致其他的线程无法获得改notify,因此全部阻塞
_stack.pop();
return res;
}
void pop(T &v)
{
std::lock_guard<std::mutex> lock(_mtx);
if(_stack.empty()) throw EmptyStackError();
v = std::move(_stack.top());
_stack.pop();
}
std::shared_ptr<T> pop()
{
std::lock_guard<std::mutex> lock(_mtx);
if(_stack.empty()) throw EmptyStackError();
auto res = std::make_shared<T>(std::move(_stack.top()));
_stack.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lock(_mtx);
return _stack.empty();
}
private:
mutable std::mutex _mtx;
std::condition_variable _cv;
std::stack<T> _stack;
};
Stack3 - 内存提前分配的优化栈
Stack3 进一步优化了 Stack2 中的潜在问题,通过提前在 push 时进行内存分配,避免在 wait_pop 中由于内存分配失败而抛出异常。内部栈_stack
中直接存储数据的shared_ptr
而不是数据本身,这样在pop
时避免了构造返回值shared_ptr
抛出异常的问题。
将数据分配内存的时间提前到push的过程中,这样wait_pop就不会因为无法分配内存抛出异常了
template<typename T>
class Stack3
{
public:
Stack3() = default;
~Stack3() = default;
Stack3(const Stack3&) = delete;
Stack3& operator=(const Stack3&) = delete;
Stack3(Stack3&& other) noexcept
{
std::scoped_lock lock(_mtx, other._mtx);
_stack = std::move(other._stack);
}
Stack3& operator=(Stack3&& other) noexcept
{
if(this != &other)
{
std::scoped_lock lock(_mtx, other._mtx);
_stack = std::move(other._stack);
}
return *this;
}
void push(const T& v) // 入栈,唤醒阻塞的线程
{
auto t = std::make_shared<T>(v);
std::unique_lock<std::mutex> lock(_mtx);
_stack.push(t);
_cv.notify_one();
}
void push(T&& v) // 入栈,唤醒阻塞的线程
{
auto t = std::make_shared<T>(std::move(v));
std::unique_lock<std::mutex> lock(_mtx);
_stack.push(t);
_cv.notify_one();
}
void wait_pop(T& v)
{
std::unique_lock<std::mutex> lock(_mtx);
_cv.wait(lock, [this]()->bool{return !_stack.empty();});
v = std::move(*_stack.top());
_stack.pop();
}
std::shared_ptr<T> wait_pop()
{
std::unique_lock<std::mutex> lock(_mtx);
_cv.wait(lock, [this]()->bool{return !_stack.empty();});
auto res = _stack.top(); // 当此处抛出异常,由于本线程获得了cv的通知,导致其他的线程无法获得改notify,因此全部阻塞
_stack.pop();
return res;
}
void pop(T &v)
{
std::lock_guard<std::mutex> lock(_mtx);
if(_stack.empty()) throw EmptyStackError();
v = std::move(*_stack.top());
_stack.pop();
}
std::shared_ptr<T> pop()
{
std::lock_guard<std::mutex> lock(_mtx);
if(_stack.empty()) throw EmptyStackError();
auto res = _stack.top();
_stack.pop();
return res;
}
bool empty() const
{
std::lock_guard<std::mutex> lock(_mtx);
return _stack.empty();
}
private:
mutable std::mutex _mtx;
std::condition_variable _cv;
std::stack<std::shared_ptr<T>> _stack;
};
标签:std,mtx,lock,编程,pop,并发,other,数据结构,stack
From: https://www.cnblogs.com/sfbslover/p/18402978