1

我正在编写一个用 pthread 实现的多线程队列。由于我根据互联网上的几个教程提出了代码,因此我想确保我的代码中没有逻辑错误:

template <typename T>
class ThreadQueue {
public:
    ThreadQueue() {
        pthread_mutex_init(&m_qmtx, NULL);
        pthread_cond_init(&m_condv, NULL);
    }

    ~ThreadQueue() {
        pthread_mutex_lock(&m_qmtx);
        m_queue.clear();
        pthread_mutex_unlock(&m_qmtx);
    }

    void push(T t_data) {
        pthread_mutex_lock(&m_qmtx);
        m_queue.push_back(t_data);
        pthread_mutex_unlock(&m_qmtx);

        pthread_cond_signal(&m_condv);
    }

    T front() {
        T ret;
        pthread_mutex_lock(&m_qmtx);
        while (m_queue.empty()) {
            pthread_cond_wait(&m_condv, &m_qmtx);
        }
        ret = m_queue.front();
        pthread_mutex_unlock(&m_qmtx);
        return ret;
    }

    void pop() {
        pthread_mutex_lock(&m_qmtx);
        if (!m_queue.empty())
            m_queue.pop_front();
        pthread_mutex_unlock(&m_qmtx);
    }

private:
    std::deque<T> m_queue;
    pthread_mutex_t m_qmtx;
    pthread_cond_t m_condv;
};
4

1 回答 1

4

我可以从您的代码中看到的一个大问题是它不是异常安全的。只要您的deque操作不抛出它就不是问题,但是如果(或者更确切地说,何时,这只是时间问题)它们会抛出,那么您的互斥锁将保持锁定状态,然后您将被卡住。

例子:

void push(T t_data) {
    pthread_mutex_lock(&m_qmtx);
    m_queue.push_back(t_data);
    pthread_mutex_unlock(&m_qmtx);

    pthread_cond_signal(&m_condv);
}

在这里,push_back可能由于各种原因抛出(内存不足,T 的复制构造函数抛出,...),如果是,则pthread_mutex_unlock永远不会被调用。

使您的代码异常安全的最佳解决方案是为pthread_mutex_(un)lock. 就像是:

class MutexLock {
public:
    MutexLock(pthread_mutex_t& mutex)
        : m_mutex(mutex)
    {
        if (pthread_mutex_lock(m_mutex))
            throw std::runtime_error("Could not lock the mutex.");
    }
    ~MutexLock() { pthread_mutex_unlock(m_mutex); }
private:
    pthread_mutex_t& m_mutex;
}

然后你可以push像这样重写你的函数(和其他函数):

void push(T t_data) {
    {
        MutexLock lock(m_qmtx);
        m_queue.push_back(t_data);
    } // note: braces to enforce *lock* scope, for identical results to your code
    pthread_cond_signal(&m_condv);
}

注意:如您所见,我还在pthread_mutex_lock包装器的构造函数中添加了错误处理(您当前代码中的另一个问题:当函数返回错误代码时,您需要处理它!)。但是在析构函数中这并不重要,因为(a)如果析构函数运行,则意味着包装器已成功构建,因此您的锁持有互斥锁(您将能够安全地解锁它),以及(b)析构函数不应该抛出,所以即使解锁失败,你也无能为力。

更多阅读:有关异常安全(这是 C++ 中的一个基本概念)的更多信息,请参阅优秀的 Herb Sutter 的系列Guru Of The Week,他有许多关于异常安全的文章(特别是问题 #8、21、56、 59、60、61、65,也许还有其他我错过的)。您可能还想了解 RAII。

或者:C++11

pthreads正如@qdii 所提到的,如果您可以使用 C++11,那么您可能会对用新的标准等价物 ( std::thread, std::mutex, , ...)替换所有东西感兴趣,std::condition_variable它们至少有两个优点:(a) 不像pthreads它们是可移植的,并且 (b) 您不必为正确实现异常安全而费心,因为 STL 负责大部分工作(但您仍然必须使用正确的习惯用法,例如std::unique_lock持有互斥锁 - 相当于我的蹩脚MutexLock的 RAII 包装器,除了标准的包装器实际上是经过深思熟虑的)。

于 2013-04-22T16:29:06.573 回答