使用pthread实现的并发队列

时间:2013-04-22 15:54:52

标签: c++ multithreading

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

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

1 个答案:

答案 0 :(得分: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&#39的拷贝构造函数抛出,......)如果确实如此,pthread_mutex_unlock永远不会被调用。

使代码异常安全的最佳解决方案是为pthread_mutex_(un)lock编写RAII包装器。类似的东西:

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

正如@qdii所提到的,如果您可以使用C ++ 11,那么您可能会有兴趣用新的标准等价物pthreadsstd::thread替换所有std::mutex内容, std::condition_variable,...)至少有两个优点:(a)与pthreads不同,它们是便携式的,(b)您不必为正确实施异常安全而烦恼太多因为STL会处理大部分内容(但是你仍然必须使用正确的习惯用法,例如std::unique_lock来保存互斥锁 - 相当于我的跛脚MutexLock RAII包装,除了标准的实际上是经过深思熟虑。)