【问题标题】:Problem with thread-safe queue?线程安全队列有问题吗?
【发布时间】:2010-10-07 02:11:50
【问题描述】:

我正在尝试使用 c++ 中的 pthreads 编写线程安全队列。我的程序 93% 的时间都在工作。另外 7% 的时间它会吐出垃圾,或者似乎睡着了。我想知道我的队列中是否存在上下文切换会破坏它的缺陷?

// thread-safe queue
// inspired by http://msmvps.com/blogs/vandooren/archive/2007/01/05/creating-a-thread-safe-producer-consumer-queue-in-c-without-using-locks.aspx
// only works with one producer and one consumer
#include <pthread.h>
#include <exception>

template<class T>
class tsqueue
{
    private:
        volatile int m_ReadIndex, m_WriteIndex;
        volatile T *m_Data;
        volatile bool m_Done;
        const int m_Size;
        pthread_mutex_t m_ReadMutex, m_WriteMutex;
        pthread_cond_t m_ReadCond, m_WriteCond;
    public:
        tsqueue(const int &size);
        ~tsqueue();
        void push(const T &elem);
        T pop();
        void terminate();
        bool isDone() const;
};

template <class T>
tsqueue<T>::tsqueue(const int &size) : m_ReadIndex(0), m_WriteIndex(0), m_Size(size), m_Done(false) {
    m_Data = new T[size];
    pthread_mutex_init(&m_ReadMutex, NULL);
    pthread_mutex_init(&m_WriteMutex, NULL);
    pthread_cond_init(&m_WriteCond, NULL);
    pthread_cond_init(&m_WriteCond, NULL);
}

template <class T>
tsqueue<T>::~tsqueue() {
    delete[] m_Data;
    pthread_mutex_destroy(&m_ReadMutex);
    pthread_mutex_destroy(&m_WriteMutex);
    pthread_cond_destroy(&m_ReadCond);
    pthread_cond_destroy(&m_WriteCond);
}


template <class T>
void tsqueue<T>::push(const T &elem) {
    int next = (m_WriteIndex + 1) % m_Size;
    if(next == m_ReadIndex) {
        pthread_mutex_lock(&m_WriteMutex);
        pthread_cond_wait(&m_WriteCond, &m_WriteMutex);
        pthread_mutex_unlock(&m_WriteMutex);
    }
    m_Data[m_WriteIndex] = elem;
    m_WriteIndex = next;
    pthread_cond_signal(&m_ReadCond);
}

template <class T>
T tsqueue<T>::pop() {
    if(m_ReadIndex == m_WriteIndex) {
        pthread_mutex_lock(&m_ReadMutex);
        pthread_cond_wait(&m_ReadCond, &m_ReadMutex);
        pthread_mutex_unlock(&m_ReadMutex);
        if(m_Done && m_ReadIndex == m_WriteIndex) throw "queue empty and terminated";
    }
    int next = (m_ReadIndex +1) % m_Size;
    T elem = m_Data[m_ReadIndex];
    m_ReadIndex = next;
    pthread_cond_signal(&m_WriteCond);
    return elem;
}

template <class T>
void tsqueue<T>::terminate() {
    m_Done = true;
    pthread_cond_signal(&m_ReadCond);
}

template <class T>
bool tsqueue<T>::isDone() const {
    return (m_Done && m_ReadIndex == m_WriteIndex);
}

可以这样使用:

// thread 1
while(cin.get(c)) {
    queue1.push(c);
}
queue1.terminate();


// thread 2
while(!queue1.isDone()) {
    try{ c = queue1.pop(); }
    catch(char const* str){break;}
    cout.put(c);
}

如果有人看到这个问题,请说出来:)

【问题讨论】:

  • MSVC 编译器?你是在多核机器上吗?
  • 您真的测量了精确的 93% / 7% 比率吗?哈哈。
  • Rob -- gcc。我认为单核,但我在学校计算机上通过 PuTTy 运行它,所以我不完全确定。奥古斯托——跑了 32 次,失败了两次。足够接近的近似值:) 我想我应该四舍五入到 94%

标签: c++ multithreading concurrency queue pthreads


【解决方案1】:

是的,这里肯定有问题。您对队列成员变量的所有访问都发生在互斥锁之外。事实上,我不完全确定您的互斥锁在保护什么,因为它们只是在等待条件变量。

此外,您的读取器和写入器似乎将始终步调一致地运行,绝不允许队列的大小超过一个元素。

【讨论】:

  • 我拥有互斥锁的唯一原因是 pthread_cond_wait 需要它们。由于我一次只有一个读取器或写入器,因此不应同时读取或写入任何变量(有或没有互斥锁)。
  • 哦,我不确定您所说的它们将同步运行是什么意思。他们为什么会?仅当线程 1 和线程 2 以完全相同的速度运行并且在不同的 CPU 上时才会发生这种情况吗?否则线程 1 可能会在线程 2 被调用之前运行多次。
【解决方案2】:

如果这是您的实际代码,那么一个问题就是您正在初始化 m_WriteCond 两次,而根本没有初始化 m_ReadCond

【讨论】:

  • 哦,哇,完全错过了。我很惊讶这东西没有爆炸。谢谢:)
【解决方案3】:

您应该将此类视为monitor。您应该为每个队列(普通互斥锁)设置一个“监视器锁”。每当您输入一个读取或写入队列中任何字段的方法时,您应该在输入后立即锁定此互斥锁。这可以防止多个线程一次与队列交互。您应该在等待条件之前和离开方法时释放锁,以便其他线程可以进入。确保在等待条件完成后重新获取锁。

【讨论】:

  • 锁定整个队列似乎有点低效,不是吗?没有理由它不应该能够同时读取和写入,只要它不是同一个元素。不过我很欣赏这个建议:)
  • 您需要保护整个共享状态。读取器和写入器都访问这两个索引,因此您实际上确实需要锁定整个队列。有了两个线程,这不是什么大问题。如果您有很多读者和作者,请查找读者-作者问题以获得更有效的方法。
【解决方案4】:

如果你想要任何性能不错的东西,我强烈建议你放弃你的 R/W 锁,而只使用一个非常简单的自旋锁。或者如果你真的认为你可以通过 R/W 锁获得你想要的性能,我会根据 Joe Duffy 的 this 设计(单字 R/W Spinlock)推出你自己的。

【讨论】:

  • 啊,谢谢。我会读一读!我还是多线程新手。
【解决方案5】:

似乎问题在于您有一个竞争条件,即线程 2 可以在线程 1 执行任何 cin.get(c) 之前运行。需要确保数据已初始化,并且当您获取信息时,您需要确保在未输入数据的情况下您正在做某事。

也许这是我没有看到完成此操作的其余代码。

【讨论】:

  • 我认为你遗漏了一些东西。如果首先调用pop(),那么m_ReadIndex == m_WriteIndex 和线程2 将进入休眠状态,直到它发出信号。
猜你喜欢
  • 2017-06-14
  • 1970-01-01
  • 1970-01-01
  • 2019-08-21
  • 2018-05-12
  • 2012-11-05
  • 1970-01-01
  • 1970-01-01
  • 2013-02-23
相关资源
最近更新 更多