11

我正在尝试跨线程共享图像,该图像仅以只读方式使用。通常我用 boost::shared_ptrs 做这种事情,但是由于 cv::Mat 已经是下面的引用计数容器,我一直在尝试以相同的方式使用它,假设它是基于对线程安全的引用的线程安全的在这里引用计数:

但是,我遇到的问题可能表明它们实际上不是线程安全的;该分配是非原子的。有时我会在引用计数增量中得到一个段错误,这意味着原始对象已经被销毁。

所以具体的问题是:

  • cv::Mat 分配是原子的吗?
4

2 回答 2

8

不,分配不是完全线程安全的。

我编写了一个创建两个线程的测试程序。它们都包含指向包含 cv::Mat 的对象的 shared_ptr。一个线程将该 cv::Mat 分配给一个随机生成的图像,而另一个线程制作该 cv::Mat 的本地副本。

这会立即因双重释放而崩溃。如果写入线程在复制线程开始复制时覆盖了先前的线程,它将复制一个 cv::Mat,其内部数据 ptr 刚刚被删除。当复制线程的本地副本超出范围时,它会尝试再次释放它。

volatile bool g_done = false;

struct Object
{
   cv::Mat cvMask;
};

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
void thread1(boost::shared_ptr<Object> sharedObj)
{
   while(!g_done)
   {
      sharedObj->cvMask = cv::Mat::ones(1 + (rand()% 1024), 1+(rand()%768), CV_8UC1);
   }
}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
void thread2(boost::shared_ptr<Object> sharedObj)
{
   while(!g_done)
   {
      cv::Mat localCopy = sharedObj->cvMask;
   }
}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
void sigHandler(int signum)
{
   fprintf(stderr, "Quitting...\n");
   g_done = true;
}

////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
int main(int argc, char** argv)
{
   signal(SIGINT, sigHandler);

   boost::shared_ptr<Object> sharedObj(new Object);
   sharedObj->cvMask = cv::Mat::ones(1024,768, CV_8UC1);

   boost::thread* t1 = new boost::thread(boost::bind(&thread1, _1), sharedObj);
   boost::thread* t2 = new boost::thread(boost::bind(&thread2, _1), sharedObj);

   while(!g_done)
   {
      usleep(1e6);
   }

   t1->join();
   t2->join();
   delete t1;
   delete t2;

   return 0;
}
于 2016-10-27T22:49:56.963 回答
5

具体问题,简短回答:是的。

您可以检查 cv::Mat 实现细节core/src/matrix.cppinclude/.../core/core.hpp

OpenCV 来源的一些代码摘录:

 if( refcount )
        CV_XADD(refcount, 1);

其中 CV_XADD 是原子测试和增量。

inline void Mat::addref()
{ if( refcount ) CV_XADD(refcount, 1); }

inline void Mat::release()
{
    if( refcount && CV_XADD(refcount, -1) == 1 )
        deallocate();
    data = datastart = dataend = datalimit = 0;
    size.p[0] = 0;
    refcount = 0;
}

额外的

智能指针确实提供了一定程度的线程安全,但这并不意味着它们在所有可能的情况下都是完全线程安全的。具体来说,如果您尝试在共享 ptr 被另一个线程破坏的同时复制它,您将失败。这不是实现中的错误,而是速度和实用性之间的设计权衡。

所有主要的共享 ptr 实现(boost、stl)都遵循这种方法。

于 2012-05-19T17:57:44.267 回答