线程安全、无数据争用、无延迟的共享容器 (circular_buffer)

thread-safe, data-race free, lag-free shared container (circular_buffer)

本文关键字:circular buffer 数据 安全 争用 延迟 线程 共享      更新时间:2023-10-16

我面临以下情况(我不得不承认我太菜鸟了,不能相信自己独自解决..(:我有线程 A,它偶尔会创建新的 cv::Mat 对象供线程 B 使用。我需要一个线程安全的容器 C(在这种情况下为 boost::circular_buffer(,它将容纳线程 A 生成的 cv::Mat 对象。然后,线程 B 需要不断循环访问 C 中的所有项以生成动画。因此,我需要一个线程安全的 C,它将不允许数据争用,但也会导致线程 B 的动画不会(理想情况下(或非常小(如果不可能(滞后 ->我点希望线程 B 在线程 A 更新 C 时冻结。我能想到的最好的是:

#include <boost/circular_buffer.hpp>
#include <opencv2/core.hpp>
#include <boost/core/noncopyable.hpp>
#include <memory>
#include <type_traits>
#include <algorithm>
using im_buf = boost::circular_buffer<cv::Mat>;
class ImageBuffer : private boost::noncopyable { 
private:
  im_buf buffer;
  std::mutex mtx;
  std::unique_lock<std::mutex> lock;
public:
  // operator<< accepting cv::Mat, cv::Mat& and cv::Mat&&
  template <class T,
    class = typename std::enable_if
    <std::is_same<cv::Mat, typename std::decay<T>::type>
     ::value>::type>
   void operator<<(T&& mat) {
      lock.lock();
      buffer.push_back(std::forward<T>(mat));
      lock.unlock();
    }
    template <typename Func> // excpect callable objects only
    inline void iterate(Func func) {
      lock.lock();
      std::for_each(buffer.begin(),buffer.end(),func);
      lock.unlock();
    }
    inline ImageBuffer():
      buffer {settings::max_images_in_loop},
      mtx {},
      lock {mtx} {}
    ~ImageBuffer()=default;
    ImageBuffer(const ImageBuffer&&)=delete; 
    ImageBuffer& operator=(const ImageBuffer&&)=delete;  
};

(请注意,即使这不是一个不变的,线程 B 也不应该改变 C 或其任何内容(我很想在这里使用const_iterator,但 boost::circular_buffer 不提供......

然而,使用此代码,每次线程 A 添加新元素时,线程 B 都会将整个动画冻结一段时间。答:难道没有更好的方法吗?b. 实现真的是线程安全的吗?

如果你的问题是动画,你担心的是错误的事情。互斥体适合您的目的 - 不必担心停滞。

理想情况下,您需要以 60fps 的速度生成帧。根据您的应用,您可能会以低至 20fps 的速度逃脱。在数字电影出现之前,电影院只显示24fps。

60fps 意味着您有 16 毫秒的时间来渲染帧。在 1GHz 处理器上,时钟周期为 1600 万个。为了避免由于其他进程而导致卡顿,您需要使用不到一半的时间,例如 4-800 万个周期,因此 50% 到 75% 的处理器时间处于空闲状态,并且您只使用 4-8 毫秒的处理器时间来渲染帧。

等待其他线程、互斥锁或其他资源只会在导致您错过下一帧的截止时间时影响您的动画。

如果您的线程必须每隔几帧等待一次,由于互斥锁而等待几万个时钟周期,这不会影响您的渲染,因为在下一帧显示在屏幕上之前您仍然有足够的时间。