我是 C++ 新手,正在编写一个多线程应用程序,不同的编写者将对象推送到堆栈上,读者将它们从堆栈中拉出(或至少将指针推送到对象)..
C++ 中是否有任何内置结构可以在不添加锁定代码等的情况下处理此问题?如果没有,Boost 库呢?
编辑:
嗨。感谢您最初的精彩回答。我想我认为这可以是内置的一个原因是我纯粹在 x86 空间中思考,并认为指针的 PUSH/POP 应该是指令级别的原子操作。
我不确定我最初的预感是否属实,但我想这不一定在所有平台上都是如此。不过,如果在 x86 上运行,您是否会向堆栈获得原子 PUSH 和 POP?如果是,这是否本质上使其成为无锁?
是的:Boost.Thread很棒,应该非常适合您的需求。 (现在,很多人说你几乎可以将 Boost 视为内置功能。)
仍然没有可以开箱即用的类,但是一旦您手头有了同步原语,实现您自己的线程安全包装器确实非常简单,例如,
std::stack
。它可能看起来像这样(没有实现每个方法......):
template <typename T> class MyThreadSafeStack {
public:
void push(const T& item) {
boost::mutex::scoped_lock lock(m_mutex);
m_stack.push(item);
}
void pop() {
boost::mutex::scoped_lock lock(m_mutex);
m_stack.pop();
}
T top() const { // note that we shouldn't return a reference,
// because another thread might pop() this
// object in the meanwhile
boost::mutex::scoped_lock lock(m_mutex);
return m_stack.top();
}
private:
mutable boost::mutex m_mutex;
std::stack<T> m_stack;
}
如果您是 C++ 新手,请了解 RAII。与这种情况相关的是,Boost.Thread 有“作用域锁”类,这样就很难因为忘记释放锁而搬起石头砸自己的腿。
如果你发现自己写过这样的代码:
void doStuff() {
myLock.lock();
if (!condition) {
reportError();
myLock.unlock();
return;
}
try {
doStuffThatMayThrow();
}
catch (std::exception& e) {
myLock.unlock();
throw e;
}
doMoreStuff();
myLock.unlock();
}
,那么你应该说“不”,然后转而使用 RAII(语法不是直接来自 Boost):
void doStuff() {
scoped_lock lock;
if (!condition) {
reportError();
return;
}
doStuffThatMayThrow();
doMoreStuff();
}
重点是,当
scoped_lock
对象超出范围时,它的析构函数会释放资源——在本例中是锁。无论您是通过抛出异常退出作用域,还是通过执行同事在函数中间偷偷添加的奇怪的 return
语句,或者只是到达函数末尾,这种情况总会发生。
当前的 C++ 标准根本不解决线程问题,因此第一个问题的答案是否定的。一般来说,在基本数据结构中构建锁定是一个坏主意,因为它们没有足够的信息来正确和/或有效地执行它。相反,锁定应该在使用数据结构的类中执行 - 换句话说,在您自己的应用程序类中。
AFAIK,C++ 中没有内置支持。您必须使用简单的同步工具来同步堆栈操作。如果线程属于同一进程,则 CriticalSection 会执行此操作,否则请使用互斥锁。
C++ 和 Boost 库中都没有内置机制支持这一点(注意:有些人以 Boost 风格编写了线程安全堆栈/等)。您必须借用一些代码或在自己的同步中进行烹饪。
请注意,您的情况可能需要单写入器多读取器保护(SWMRG),其中多个写入器线程可以访问堆栈(但在给定时间点只有一个)并且多个读取器可以访问堆栈(许多在给定的时间点)。 Richter 有参考实现。
Reuanen
的优秀示例可以用这些工具重写,这些工具的使用方式与 boost 示例非常相似。 您需要这些标头:
#include <mutex> // defines mutexes and locks
#include <stack>
然后您可以创建线程安全堆栈:
template <typename T> class MyThreadSafeStack {
public:
void push(const T& item) {
std::lock_guard<std::mutex> lock(m_mutex);
m_stack.push(item);
}
void pop() {
std::lock_guard<std::mutex> lock(m_mutex);
m_stack.pop();
}
T top() const { // note that we shouldn't return a reference,
// because another thread might pop() this
// object in the meanwhile
std::lock_guard<std::mutex> lock(m_mutex);
return m_stack.top();
}
private:
mutable std::mutex m_mutex;
std::stack<T> m_stack;
};
当然,你也可以使用带有原子操作的非锁定结构,但是,这更加复杂。当人们感兴趣时我可以添加一个例子。
SLIST_HEADER & SLIST_ENTRY
)。
该算法是通过使用互锁函数的相当简单的推/弹出单链表堆栈来实现的。唯一不明显的项目是计数器增量以避免 ABA 问题。
我还需要确保堆栈在达到其容量时可以处理“堆栈溢出”情况,并且在所有线程完成后正确停止处理。
这是我尝试解决问题的代码:
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
#include <vector>
#include <algorithm>
实现堆栈类
using namespace std;
class Stack {
private:
mutex mx;
condition_variable cv_add;
condition_variable cv_remove;
int stackArray[100];
int top;
bool finished;
int max_element;
public:
static const int MAX_SIZE = 100;
Stack() : top(-1), finished(false), max_element(-1) {}
void push(int number);
int getMaxElement();
void setMaxElement(int number);
void printMaxElement();
void finish() { finished = true; }
};
void Stack::push(int number) {
unique_lock<mutex> lock(mx);
cv_add.wait(lock, [&] { return top < MAX_SIZE - 1 || finished; });
if (top == MAX_SIZE - 1) {
cout << "Stack Overflow!" << endl;
return;
}
stackArray[++top] = number;
setMaxElement(number);
cout << "Pushed: " << number << endl;
cv_remove.notify_all();
}
int Stack::getMaxElement() {
return max_element;
}
void Stack::setMaxElement(int number) {
if (number > max_element) {
max_element = number;
}
}
void doStuff(int number, Stack* stack) {
for (int i = 0; i < 5; i++) {
stack->push(number);
}
}
这是我能想到的最简单的主要实现
int main() {
Stack stack;
vector<thread> thr;
for (int i = 0; i < 10; i++) {
thr.push_back(thread(doStuff, i, &stack));
}
for (auto& t : thr) {
t.join();
}
stack.finish();
return 0;
}