推送和弹出的不同互斥

时间:2011-04-08 08:38:51

标签: c++ multithreading mutex boost-thread

我有一个名为'subscribedQueue'的课程。该类通过其订阅的发布者(复数)接收其数据,并调用其推送方法。

在另一个线程中,调用此类的pop方法来接收该数据。 因此,在某种意义上,该类是多个发布者及其订阅者之间的一种缓冲。对于实现,我基于有关线程安全队列here的信息。

现在我的问题有两个:

  • 如果我使用相同的互斥锁来暂停和弹出值(目前我使用的是两个不同的互斥锁),我的程序是否可能卡住,等待阻塞推送?< / LI>
  • 如果没有,那么push和pop方法怎么可能超过'lock(the_same_mutex)'。

我的假设是,如果我使用相同的互斥锁并且程序进入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'中才能'通知'条件变量。

所以不会导致这种僵局,为什么不呢?

5 个答案:

答案 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)当且仅当互斥锁当前未锁定时,推送和弹出才能通过锁定。否则,线程将被挂起,直到锁定线程释放互斥锁。