I'm attempting to share an image, that is only being used read-only, across threads. Typically I do this sort of thing with boost::shared_ptrs but since cv::Mat is already a reference counting container underneath, I've been attempting to use it in the same manner assuming that it is thread safe based on references to thread safety in reference counting here:
However I've been having having issues that might possibly indicate that they are infact not thread safe; that assignment is non-atomic. Occasionally I'll get a seg-fault inside a reference count increment that implies that the original object has already been destroyed.
So the specific question is:
No, the assignment is not perfectly thread safe.
I wrote a test program that creates two threads. They both contain a shared_ptr to an object that contains a cv::Mat. One thread assigns that cv::Mat to a randomly generated image while the other thread makes a local copy of that cv::Mat.
This crashes immediately with a double-free. If the writing thread overwrites the previous as the copying thread begins copying, it'll copy a cv::Mat who's internal data ptr has just been deleted. When the copying thread's local copy goes out of scope, it attempts to free it again.
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;
}
Specific question, short answer: YES.
You can check the cv::Mat implementation details in core/src/matrix.cpp
and include/.../core/core.hpp
Some code excerpts from OpenCV sources:
if( refcount )
CV_XADD(refcount, 1);
Where CV_XADD is the atomic test-and-increment.
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;
}
Extra
Smart pointers do offer a level of thread safety, but that does not mean they are completely thread-safe in every scenario possible. Specifically, if you try to copy a shared ptr at the same time it is destructed by another thread, you lose. That's not a bug in the implementation but a design trade-off between speed and usefulness.
All major shared ptr implementations (boost, stl) follow this approach.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With