我有一个名为'subscribedQueue'的课程。该类通过其订阅的发布者(复数)接收其数据,并调用其推送方法。
在另一个线程中,调用此类的pop方法来接收该数据。 因此,在某种意义上,该类是多个发布者及其订阅者之间的一种缓冲。对于实现,我基于有关线程安全队列here的信息。
现在我的问题有两个:
我的假设是,如果我使用相同的互斥锁并且程序进入pop方法,它将获取pop中的锁定,检查队列是否为空并等待条件变量,该变量永远不能在push方法(因为pop已经获取了锁)。
当前代码(使用两种不同的互斥锁):
#include <boost/thread.hpp>
#include <queue>
#include "subscriber.h"
#include "pubdata.h"
#ifdef DEBUG
#include <iostream>
#include <boost/lexical_cast.hpp>
#endif
namespace PUBLISHSUBSCRIBE
{
template<class T>
class SubscribedQueue: public PUBLISHSUBSCRIBE::Subscriber<T>, private std::queue< PubData<T> >
{
public:
PubData<T> pop(); //removes the next item from the queue, blocks until the queue is not empty
void push(const PubData<T> data); //method used by the publisher to push data onto the queue
private:
mutable boost::mutex writeMutex_; //only needed for publishing/pushing data
mutable boost::mutex readMutex_; //only needed for reading/popping data
boost::condition_variable notify_;
};
template<class T>
PubData<T> SubscribedQueue<T>::pop() { //Blocks until the queue is not empty
boost::mutex::scoped_lock lock(readMutex_);
while(std::queue< PubData<T> >::empty())
notify_.wait(lock); //block until recieving a notification AND the queue is not empty
PubData<T> head = std::queue< PubData<T> >::front();
std::queue< PubData<T> >::pop();
#ifdef DEBUG
std::string debugOut("pop: " + boost::lexical_cast<std::string>(head) + " - timestamp: " + boost::lexical_cast<std::string>(head.timestamp()) + " - from: " + boost::lexical_cast<std::string>(this) + "\n" );
std::cout <<debugOut;
#endif
lock.unlock();
return head;
}
template<class T>
void SubscribedQueue<T>::push(const PubData<T> data){
boost::mutex::scoped_lock lock(writeMutex_);
#ifdef DEBUG
std::cout << "published: " << data << std::endl;
#endif
std::queue< PubData<T> >::push(data);
lock.unlock();
notify_.notify_one();
}
}
#endif //SUBSCRIBEDQUEUE_H
[编辑]最让我担心的是:我有一个boost :: condition_variable notify_,在pop上执行'等到通知'。 但pop必须先锁定互斥锁,这个互斥锁也必须锁定在'push'中才能'通知'条件变量。
所以不会导致这种僵局,为什么不呢?
答案 0 :(得分:3)
标准库容器不是线程安全的;如果您尝试同时从多个线程修改容器,则会发生坏事。
如果你有一个单独的互斥锁用于推送和弹出操作,那么你就不能防止两个线程同时推送和弹出,所以你根本没有真正保护集合。
我的假设是,如果我使用相同的互斥锁并且程序进入pop方法,它将获取pop中的锁定,检查队列是否为空并等待条件变量,该变量永远不能在push方法(因为pop已经获取了锁)。
当您在pop中等待条件变量时,等待()解锁互斥锁,因此等待时push()将能够锁定它。 push()调用notify_one()并通过scoped_lock超出函数末尾的作用域来解锁互斥锁。然后,当下一次调度pop()线程时,它将立即重新锁定互斥锁并继续。
答案 1 :(得分:1)
根据定义,“pop”是从列表中删除项目的行为。因此,如果要从多个线程推送和弹出相同的列表,则需要使用相同的互斥锁来保护该列表。
如果您使用不同的互斥锁,那么这意味着这些不同的线程可以同时添加/删除项目,从而破坏列表。
当线程A添加到受互斥锁保护的列表中时,线程B尝试弹出该列表中的项目,必须等到线程A完成添加项目并离开锁定。
答案 2 :(得分:1)
首先,您现在不需要使用互斥锁mutable
,因为它们似乎没有在任何const
函数中使用。
其次,不是两个互斥体,一个用于读取,一个用于写入,你应该只有一个互斥锁用于访问到队列并限制访问最窄的范围,不要保持锁定指令超过需要的时间。
答案 3 :(得分:1)
是的,你必须使用一个互斥锁,否则说push为一个值腾出空间并增加了大小,但没有完成将该值复制到位......读者可以读取任何方式的垃圾。
您不必担心读者和编写者死锁 - 条件变量旨在仲裁这种情况,允许推送线程在弹出线程等待通知时运行。
答案 4 :(得分:0)
您必须只使用一个Mutex来创建线程安全队列,因为您必须确保一次只有一个线程正在访问队列(无论是推送还是弹出)。
回答你的问题: 1)使用push和pop的当前实现,不存在死锁的可能性(除非你在其他地方使用你的互斥锁),因为锁定仅限于push和pop的范围,scoped_lock也会在异常的情况下释放互斥锁。 2)当且仅当互斥锁当前未锁定时,推送和弹出才能通过锁定。否则,线程将被挂起,直到锁定线程释放互斥锁。