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