【问题标题】:Thread safe implementation of circular buffer循环缓冲区的线程安全实现
【发布时间】:2012-04-02 08:53:13
【问题描述】:

来自 boost 库的 Circular_buffer 不是线程安全的。所以我将 boost::circular_buffer 对象包装在一个类中,如下所示。通过使用条件变量、互斥锁和锁获取/释放来实现线程之间的互斥(我认为)。这个实现线程安全吗?

#include <boost/thread/condition.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/thread/thread.hpp>
#include <boost/circular_buffer.hpp>

// Thread safe circular buffer 
template <typename T>
class circ_buffer : private boost::noncopyable
{
public:
    typedef boost::mutex::scoped_lock lock;
    circ_buffer() {}
    circ_buffer(int n) {cb.set_capacity(n);}
    void send (T imdata) {
        lock lk(monitor);
        cb.push_back(imdata);
        buffer_not_empty.notify_one();
    }
    T receive() {
        lock lk(monitor);
        while (cb.empty())
            buffer_not_empty.wait(lk);
        T imdata = cb.front();
        cb.pop_front();
        return imdata;
    }
    void clear() {
        lock lk(monitor);
        cb.clear();
    }
    int size() {
        lock lk(monitor);
        return cb.size();
    }
    void set_capacity(int capacity) {
        lock lk(monitor);
        cb.set_capacity(capacity);
    }
private:
    boost::condition buffer_not_empty;
    boost::mutex monitor;
    boost::circular_buffer<T> cb;
};

编辑 现在这是一个模板类,它接受任何类型的对象(不仅仅是cv::Mat 对象)。

【问题讨论】:

  • 这看起来更适合CodeReview
  • 请原谅我的愚蠢问题,但是哪里需要线程安全的循环缓冲区?在我曾经使用过循环缓冲区的所有地方,像这样从多个线程访问它是一个严重的错误。所以只是出于好奇,你的用例是什么?
  • @LiKao 我用它来从网络摄像机抓取帧到 MATLAB,见我之前的帖子stackoverflow.com/questions/9472880/…。你会如何处理这个问题?
  • 您的 Mat 对象有多大/有多贵?
  • @LiKao : 你会用这样的东西来实现一个生产者-消费者队列 (en.wikipedia.org/wiki/Producer-consumer_problem)。这样的队列可以在多线程管道的各个阶段之间使用。

标签: c++ multithreading boost opencv


【解决方案1】:

是的。
如果你用同一个锁锁定所有公共方法,它将是线程安全的。

您可以考虑使用 read-write locks,如果您有很多并发阅读器,它的性能可能会更好。

如果您没有很多读者,它只会增加开销,但可能值得检查选项和测试。

【讨论】:

  • 我认为读写锁在循环缓冲区中没有意义。生产者和消费者都修改缓冲区,所以他们实际上都是writers
  • @DavidRodríguez-dribeas - 在这种情况下你是对的。我并没有真正进入设计,只是线程安全部分。
【解决方案2】:

我认为它看起来不错,只是在send 中制作了一些毫无意义的 Mat 副本。不需要new,直接pushsend的参数到你的cb即可。

【讨论】:

  • +1 用于避免无意义、昂贵和增加争用的复制。
  • @MartinJames 我无法直接推送发送的参数。 “cv::Mat 类实现了引用计数和浅拷贝,这样当一个图像分配给另一个图像时,图像数据不会被复制,两个图像将指向同一个内存块。” “保留引用计数,以便仅当对图像的所有引用都将被破坏时才会释放内存。如果您希望创建一个包含原始图像的新副本的图像,您将使用方法 copyTo ()。” - 来自“OpenCV 2 Computer Vision Application Programming Cookbook”(第 28 页)。
  • 在这种情况下你仍然不需要新的,在堆栈上分配新图像也可以。但这不是您想要的功能,循环缓冲区中的共享副本吗?
  • +1 让我意识到我不需要在堆上分配 Mat。 Mat 对象可以存储在堆栈中,而不是使用Mat image2; image.copyTo(image2); 然后cb.push_back(image2);。我仍然需要使用 copyTo() 因为否则缓冲区中的所有对象都将引用最后发送的对象。或者,我可以将图像复制移动到我的 circ_buffer 类之外,在这种情况下,是的,我可以按照你的建议将 send 的参数直接推送到缓冲区。
  • '当一个图像被分配给另一个图像时,图像数据不会被复制,两个图像将指向同一个内存块'——这不正是你想要的吗?当您将引用分配给队列时,不会复制,现在有 2 个引用。当在生产者中创建新图像并覆盖旧图像指针时,队列中只有一个引用。所以,不需要复制。
【解决方案3】:

您的实现类似于blogger 所示的实现。您应该阅读该博客,看看您在实施过程中是否遗漏了什么。

如果您的 Mat 对象的创建/复制成本很高,则应避免不断地创建/复制/删除它们。相反,您应该有一个 Mat 对象池(又名空闲列表),这些对象在某种管道架构中不断回收。我在这个answer 中描述了这种类型的架构到一个相关的问题。

在那个答案中,我建议使用阻塞堆栈来实现池,但您也可以使用阻塞 circular_buffer。我建议使用堆栈的原因是因为我认为它可能对缓存更友好,但我从未实际测量过它是否会有所作为。

【讨论】:

  • Mat 对象(图像)不会太大,每次调用时大小几乎相同。我刚刚意识到我可以在堆栈上分配 Mat 对象。
  • 我一直在使用对象池,(基于阻塞队列),很长一段时间。事实上,我几乎只将 poolObject+message-passing 模式用于我的多线程应用程序设计。 no-copy、no-malloc、no-GC 和内置流控制并不是唯一的优点。在 GUI 应用程序中,在任何表单或工作线程之前创建对象池意味着我通常可以忘记显式池销毁或线程终止的麻烦。在计时器上转储池级别会显示泄漏,而没有可悲的 3rd 方泄漏工具(如 V*******)会减慢 wole 应用程序的爬行速度。
  • 默认情况下,cv::Mat 的拷贝构造函数会创建一个浅拷贝。
【解决方案4】:

很老的问题:) 这是一个带有无锁实现的 disgin Link

这是 Link 的 BSD-2 库

【讨论】:

    【解决方案5】:

    乍一看看起来不错,只是您根本没有使用buffer_not_full 条件。您可能想要添加类似于buffer_not_empty 代码的代码。

    【讨论】:

    • 如果数据源产生的数据超出缓冲区的容量,boost::circular_buffer 对象会覆盖最旧的数据。还行吧。所以buffer_not_full 条件不需要检查。
    【解决方案6】:
    //This implementation above is broken. You also need condition variable
    //boost::condition buffer_not_full; and wait in send on available space in the circular buffer.
    enter code here
    
    void send (T imdata) {
       lock lk(monitor);
       while (cb.full())
          buffer_not_full.wait(lk);
       cb.push_back(imdata);
       buffer_not_empty.notify_one();
    }
    
    T receive() {
         lock lk(monitor);
         while (cb.empty())
            buffer_not_empty.wait(lk);
         T imdata = cb.front();
         cb.pop_front();
         buffer_not_full.notify_one();
         return imdata;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-12-26
      • 1970-01-01
      • 1970-01-01
      • 2015-09-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多