14

我是 C++ 新手,正在编写一个多线程应用程序,不同的编写者将对象推送到堆栈上,而读者将它们从堆栈中拉出(或至少将指针推送到对象)..

是否有任何内置于 C++ 中的结构可以在不添加锁定代码等的情况下处理这个问题?如果没有,那么 Boost 库呢?

编辑:

你好。感谢您最初的精彩回答。我想我认为这可能是内置的一个原因是我纯粹在 x86 空间中思考,并认为指针的 PUSH/POP 应该是指令级别的原子操作。

我不确定我最初的预感是否正确,但我想这不一定在所有平台上都是正确的。虽然如果在 x86 上运行,您是否将原子 PUSH 和 POP 放入堆栈,如果是,这是否实质上使其无锁?

4

7 回答 7

23

是的: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您的同事在函数中间偷偷添加的奇怪语句,或者只是通过到达函数末尾,这种情况总是会发生。

于 2009-04-26T11:50:16.530 回答
5

当前的 C++ 标准根本不涉及线程,因此您的第一个问题的答案是否定的。通常,将锁定构建到基本数据结构中是一个坏主意,因为它们没有足够的信息来正确和/或有效地执行它。相反,锁定应该在使用数据结构的类中执行 - 换句话说,在您自己的应用程序类中。

于 2009-04-26T09:41:12.783 回答
1

AFAIK,在 C++ 中没有内置支持。您必须使用简单的同步工具来同步堆栈操作。如果线程属于同一进程,CriticalSection 会这样做,否则就使用 Mutex。

于 2009-04-26T09:41:47.293 回答
1

在 C++ 和 Boost 库中都没有内置机制来支持这一点(注意:有些人以 Boost 风格编写了线程安全堆栈/等)。您将不得不借用一些代码或在自己的同步中做饭。

请注意,您的情况可能需要一个单写者多读者保护 (SWMRG),其中多个写者线程可以访问堆栈(但在给定时间点只有一个)并且多个读者可以访问堆栈(许多在给定的时间点)。Richter 有参考实现

于 2009-04-26T11:54:12.467 回答
1

如果不想使用锁定,则需要使用无锁堆栈。这实际上并不难(无锁队列更难)。您确实需要一个特定于平台的比较交换原语,例如 Windows 上的 InterlockedCompareExchange,但这并不难抽象。

请参阅此处以获取 C# 中的示例:

http://www.boyet.com/Articles/LockFreeRedux.html

于 2009-04-26T12:12:39.427 回答
1

C++ 11 标准引入了内存模型以及用于线程安全编程的标准工具。

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;
};    

当然,您也可以使用具有原子操作的非锁定结构,但是,这更复杂。当人们感兴趣时,我可以添加一个示例。

于 2021-09-14T09:21:34.290 回答
0

如果您在 Windows 上运行,SLIST 实现了无锁堆栈(带有结构SLIST_HEADER & SLIST_ENTRY)。

该算法是通过使用互锁函数的相当简单的 push/pop 单链表堆栈实现的。唯一不明显的项目是计数器增量以避免 ABA 问题。

于 2009-05-28T10:15:20.467 回答