c++ - Linux 上 C++ std::condition_variable::notify_all() 上的段错误

标签 c++ linux

我正在尝试让自己了解最新的 C++11 更改,并且我正在围绕 std::queue 制作一个名为 SafeQueue 的线程安全包装器。我有两个可能阻塞的条件:队列已满和队列为空。我为此使用 std::condition_variable 。不幸的是,在 Linux 上,对空条件的 notification_all() 调用出现了段错误。它在带有 clang 的 Mac 上运行良好。 enqueue() 方法中出现段错误:

#ifndef mqueue_hpp
#define mqueue_hpp

#include <queue>
#include <mutex>

//////////////////////////////////////////////////////////////////////
// SafeQueue - A thread-safe templated queue.                       //
//////////////////////////////////////////////////////////////////////
template<class T>
class SafeQueue
{
public:
    // Instantiate a new queue. 0 maxsize means unlimited.
    SafeQueue(unsigned int maxsize = 0);
    ~SafeQueue(void);
    // Enqueue a new T. If enqueue would cause it to exceed maxsize,
    // block until there is room on the queue.
    void enqueue(const T& item);
    // Dequeue a new T and return it. If the queue is empty, wait on it
    // until it is not empty.
    T& dequeue(void);
    // Return size of the queue.
    size_t size(void);
    // Return the maxsize of the queue.
    size_t maxsize(void) const;
private:
    std::mutex m_mutex;
    std::condition_variable m_empty;
    std::condition_variable m_full;
    std::queue<T> m_queue;
    size_t m_maxsize;
};

template<class T>
SafeQueue<T>::SafeQueue(unsigned int maxsize) : m_maxsize(maxsize) { }

template<class T>
SafeQueue<T>::~SafeQueue() { }

template<class T>
void SafeQueue<T>::enqueue(const T& item) {
    // Synchronize.
    if ((m_maxsize != 0) && (size() == m_maxsize)) {
        // Queue full. Can't push more on. Block until there's room.
        std::unique_lock<std::mutex> lock(m_mutex);
        m_full.wait(lock);
    }
    {
        std::lock_guard<std::mutex> lock(m_mutex);
        // Add to m_queue and notify the reader if it's waiting.
        m_queue.push(item);
    }
    m_empty.notify_all();
}

template<class T>
T& SafeQueue<T>::dequeue(void) {
    // Synchronize. No unlock needed due to unique lock.
    if (size() == 0) {
        // Wait until something is put on it.
        std::unique_lock<std::mutex> lock(m_mutex);
        m_empty.wait(lock);
    }
    std::lock_guard<std::mutex> lock(m_mutex);
    // Pull the item off and notify writer if it's waiting on full cond.
    T& item = m_queue.front();
    m_queue.pop();
    m_full.notify_all();
    return item;
}

template<class T>
size_t SafeQueue<T>::size(void) {
    std::lock_guard<std::mutex> lock(m_mutex);
    return m_queue.size();
}

template<class T>
size_t SafeQueue<T>::maxsize(void) const {
    return m_maxsize;
}

#endif /* mqueue_hpp */

显然我做错了什么,但我无法弄清楚。 gdb 的输出:

Core was generated by `./test'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x0000000000000000 in ?? ()
(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x0000000000414739 in std::condition_variable::notify_all() ()
#2  0x00000000004054c4 in SafeQueue<int>::enqueue (this=0x7ffee06b3470,
    item=@0x7ffee06b355c: 1) at ../mqueue.hpp:59
#3  0x0000000000404ab6 in testsafequeue () at test.cpp:13
#4  0x0000000000404e99 in main () at test.cpp:49
(gdb) frame 2
#2  0x00000000004054c4 in SafeQueue<int>::enqueue (this=0x7ffee06b3470,
    item=@0x7ffee06b355c: 1) at ../mqueue.hpp:59
59          m_empty.notify_all();
(gdb) info locals
No locals.
(gdb) this.m_empty
Undefined command: "this.m_empty".  Try "help".
(gdb) print this->m_empty
$1 = {_M_cond = {__data = {{__wseq = 0, __wseq32 = {__low = 0,
          __high = 0}}, {__g1_start = 0, __g1_start32 = {__low = 0,
          __high = 0}}, __g_refs = {0, 0}, __g_size = {0, 0},
      __g1_orig_size = 0, __wrefs = 0, __g_signals = {0, 0}},
    __size = '\000' <repeats 47 times>, __align = 0}}

感谢帮助。

我的示例测试崩溃了。

SafeQueue<int> queue(10);
queue.enqueue(1);

enqueue() 中的 notification_all() 出现段错误。

最佳答案

在这两个函数中,您都使用一个锁来等待条件变量,但一旦等待结束,您就会销毁该锁,然后再使用新锁对队列进行实际操作。

在获取新锁之间,另一个线程可能会获取互斥体的锁,例如从队列中删除原始线程打算从队列中获取的对象,可能会在空队列上调用 front

您需要在每个函数中获取一个锁,并在其下执行所有操作。仅当 wait 执行时才会(自动)释放锁。

此外,从 wait 返回并不意味着调用了 notify_*wait 可能会被虚假唤醒。此外,notify_all 还可以通知多个线程有关单个新元素可用的信息。 您需要在循环中调用 wait,在退出之前检查执行操作所需的条件。

wait 还提供了一个重载,您可以使用该重载将条件作为第二个参数作为谓词,以避免显式循环。


除此之外,还有这些行

T& item = m_queue.front();
m_queue.pop();
//...
return item;

也会导致未定义的行为。 pop 将销毁 item 引用的对象,从而产生悬空引用。使用返回的引用会导致未定义的行为。

您需要从队列中复制/移动对象,而不是保留对其的引用:

T item = m_queue.front();
m_queue.pop();
//...
return item;

因此,出队也必须返回T,而不是T&

关于c++ - Linux 上 C++ std::condition_variable::notify_all() 上的段错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70476847/

相关文章:

c++ - "hide"通过在派生类中将基类虚函数设为纯虚函数是否有效?

c++ - 是否可以在运行时选择 C++ 泛型类型参数?

c++ - OpenMP 并行代码运行速度较慢

c - closedir() 后无效的 free()

linux - 将文本 append 到管道文件

c++ - 如何正确地将字节数组反序列化回 C++ 中的对象?

堆栈内存中的c++ lambda

linux - 如何在 ubuntu 中安装 wkhtmltopdf 软件包

c++ - 如何检测终端中的unicode字符串宽度?

linux - 如何使用 GLOBBING 列出包含数字范围的文件?