PDA

View Full Version : QVector COW thread-safety [Qt 4.8]



JasonC
17th February 2014, 21:57
Is the copy-on-write behavior of QVector threadsafe?

In particular, if I pass a copy-constructed QVector to a thread, the COW behavior indicates the data itself won't actually be copied until somebody does a write operation on one of instances. In a multi-threaded application is this transparent or does it require extra synchronization?

For example (representative only):


class Example : public QThread {
public:
Example (QVector<int> data) : data_(data) {
}
public void run () {
while (true)
doReadOnlyThingsWith(data_);
}
private:
QVector<int> data_;
}

void elsewhere () {
QVector<int> data = ...;
new Example(data)->start();
new Example(data)->start();
new Example(data)->start();
new Example(data)->start();
writeThingsToDataThatShouldntBeSeenByThreads(data) ;
}

In the above example, multiple threads are using the data (read-only) while the main thread may go on to modify it, but the goal is that the modifications have no effect on the threads (that's why I passed it by value in the first place). Intuitively this should work, but is that really OK? Is the underlying copy-on-write threadsafe?

The reason I ask is I'm trying to track down an infrequent seg fault and the debugger is not yielding helpful information. The only thing I noticed is that some of the threads are in QVector::realloc() when the program crashes, so I'm focusing on my usage of QVector.

Thanks,
J

wysota
17th February 2014, 22:22
Accessing the shared pointer is thread-safe (which means that if two or more objects point to the same data and one of them needs a unique copy of the data so that it can be modified, it is guaranteed that no other thread will delete that pointer). Accessing data behind the pointer is not and you require dedicated synchronization.

JasonC
17th February 2014, 22:43
Thanks. So in other words, where this pattern is safe for primitives and non-COW objects:


int valueForThread;

void threadFunction () {
// do stuff with valueForThread
}

void mainFunction () {
int value = ...;
valueForThread = value; // create a copy for thread
startThread(&threadFunction);
value = ...; // doesn't affect thread
}


This would *not* be OK for a QVector:


QVector<int> valueForThread;

void threadFunction () {
// do stuff with valueForThread
}

void mainFunction () {
QVector<int> value = ...;
valueForThread = value; // create a copy for thread
startThread(&threadFunction);
value = ...; // doesn't affect thread
}


And therefore I need to review the documentation for a Qt object carefully before using it in this way to make sure no behind-the-scenes COW could be happening as a result and, if it is, synchronize accordingly. Correct?

So in the above example I would need to protect 'valueForThread' with a mutex but *also* acquire the mutex when modifying 'value' in mainFunction()?

Does that also mean an alternative is to somehow force a deep copy when doing 'valueForThread = value' so that I don't run into the problem at all and the copies become completely independent?

Thanks.

wysota
18th February 2014, 07:29
The pattern is safe in this context:

Thread #1: vector2 = vector; vector2.append()
Thread #2: vector = QVector<...>()

Which means it is safe if you have two vectors pointing to the same data and you modify one of them. The pattern is not safe if two threads operate on the same object.

gemmell
9th December 2014, 22:16
This thread has confused me.

I have always been under the impression that passing a copy of a QVector/QString etc to another thread would mean that they operate on the same data until one of them makes a change, and then it will do a deep copy in a threadsafe way and now they are operating on different data.

This is all based on this article:
http://marcmutz.wordpress.com/effective-qt/containers/
(under the Copy-On-Write heading).
which explicitly recommends using this mechanism to minimize time in mutex's.

wysota
9th December 2014, 22:46
How would a deep-copy of a potentially very large vector data be made in a thread-safe way without using a mutex? The article explicitly show that an external mutex is required to protect the data structure while it is being copied (lines 26-30). And I'm not sure the code presented there is correct anyway.

anda_skoa
10th December 2014, 10:03
If you know that either thread is going to modify the data, why not create a deep copy in the first place?



void mainFunction () {
QVector<int> value = ...;
valueForThread = value; // create a copy for thread
valueForThread.detatch(); // create deep copy
startThread(&threadFunction);
value = ...; // doesn't affect thread
}


Cheers,
_