我已经编写了自己的线程安全队列版本。但是,当我运行此程序时,它会挂起/死锁。
想知道,为什么这会永远锁定/挂起。
void concurrentqueue::addtoQueue(const int number)
{
locker currentlock(lock_for_queue);
numberlist.push(number);
pthread_cond_signal(&queue_availability_condition);
}
int concurrentqueue::getFromQueue()
{
int number = 0;
locker currentlock(lock_for_queue);
if ( empty() )
{
pthread_cond_wait(&queue_availability_condition,&lock_for_queue);
}
number = numberlist.front();
numberlist.pop();
return number;
}
bool concurrentqueue::empty()
{
return numberlist.empty();
}
我写过,班级储物柜是RAII。
class locker
{
public:
locker(pthread_mutex_t& lockee): target(lockee)
{
pthread_mutex_lock(&target);
}
~locker()
{
pthread_mutex_unlock(&target);
}
private:
pthread_mutex_t target;
};
我的编剧/读者线程代码非常简单。编写器线程,添加到队列和读取器线程,从队列中读取。
void * writeintoqueue(void* myqueue)
{
void *t = 0;
concurrentqueue *localqueue = (concurrentqueue *) myqueue;
for ( int i = 0; i < 10 ; ++i)
{
localqueue->addtoQueue(i*10);
}
pthread_exit(t);
}
void * readfromqueue(void* myqueue)
{
void *t = 0;
concurrentqueue *localqueue = (concurrentqueue *) myqueue;
int number = 0;
for ( int i = 0 ; i < 10 ; ++i)
{
number = localqueue->getFromQueue();
std::cout << "The number from the queue is " << number << std::endl;
}
pthread_exit(t);
}
答案 0 :(得分:5)
这绝对不安全:
if ( empty() )
{
pthread_cond_wait(&queue_availability_condition,&lock_for_queue);
}
如果另一个先前没有等待的线程在getFromQueue()
之后调用addtoQueue()
已经发出条件变量并退出但是在等待线程获得锁之前,那么该线程可以退出并期望队列具有其中的价值观。您必须重新检查队列是否为空。
将if更改为while:
while ( empty() )
{
pthread_cond_wait(&queue_availability_condition,&lock_for_queue);
}
答案 1 :(得分:2)
将spong的评论重新编写为答案:您的locker
类不应该按值复制pthread_mutex_t
。您应该使用引用或指针,例如:
class locker
{
public:
locker(pthread_mutex_t& lockee): target(lockee)
{
pthread_mutex_lock(&target);
}
~locker()
{
pthread_mutex_unlock(&target);
}
private:
pthread_mutex_t& target; // <-- this is a reference
};
原因是所有pthreads数据类型都应该被视为不透明类型 - 你不知道它们中包含什么,不应该复制它们。该库执行诸如查看特定内存地址以确定是否保持锁定之类的操作,因此如果有一个变量的两个副本指示是否保持锁定,则可能发生奇怪的事情,例如多个线程似乎成功锁定相同的互斥锁。
我测试了你的代码,它也让我陷入僵局。然后我通过Valgrind运行它,虽然它在那种情况下没有死锁(由于不同的时间,或者Valgrind一次只能模拟一个线程),Valgrind报告了很多错误。修复locker
以使用引用后,它运行时没有死锁,并且在Valgrind中没有生成任何错误。