[Interest] Heavily Commented Example: Simple Single Frontend with Two Backends

Thomas McGuire thomas.mcguire at kdab.com
Tue Oct 23 10:37:17 CEST 2012


Hi,

On Monday 22 October 2012 22:44:05 Till Oliver Knoll wrote:
> Am 22.10.2012 um 16:07 schrieb Jan Kundrát <jkt at flaska.net>:
> > On 10/22/12 14:12, Till Oliver Knoll wrote:
> >> So the net effect is that the "worker thread" does "one loop too much"
> >> - but /eventually/ it will see that m_continue has been set to 'false'
> >> and will terminate. And that is exactly what we want and hence "good
> >> enough" (no need for protecting the member m_continue with a mutex!).
> > 
> > Hi Till,
> > you don't need a mutex, but you need a guarantee that the compiler will
> > recognize that you use that variable for inter-thread synchronization.
> 
> Off course I had only single CPU / multicore with synchronised caches in
> mind (*cough* *cough*).
> 
> > Volatile does not do that.
> 
> Seriously, I thought "volatile" would at least force the CPU to always read
> it from (the CPU's own) memory instead of any cache/register?
> 
> So the problem would only occur if you really had multiple CPUs with
> dedicated RAM per CPU, no?

Right, on machines with only one CPU the problem doesn't exist. On machines 
with mulitple CPUs, the problem is always existant, as every CPU has a cache.

> > Qt's QAtomicInt or C++11's std::atomic_bool
> > or std::atomic_flag will work fine.
> 
> So I understand you basically have to explicitly tell the CPU to fetch and
> sync the data from RAM by means of "aquire" and "releasing" memory, so it
> would also work on "real" multi-CPU systems (and dedicated RAM per CPU -
> but since you say the CPU might also cache the data regardless of
> "volatile", I assume you also need to "sync" memory in the "shared RAM"
> case).
> 
> However my actual question is: wouldn't that be also the case for /any/
> data structure I share between threads? So besides making sure that only
> one thread modifies the data (by a mutex) I would *also* have to make sure
> that (possibly) cached instances (in the other CPUs) get invalidated?

Correct, aquire and release semantics are always needed when sharing data 
structures between multiple threads.
QMutex internally uses aquire and release semantics, so as soon as you use 
QMutex to protect your data structures, that takes care or memory barriers.
Basically, calling QMutex::lock() will do an aquire, and QMutex::unlock() a 
release. This will affect all memory, not just the memory locations protected 
by the mutex (after all, the CPU has no way to know which variables you're 
using the mutex for).

> Or in other words: wouldn't there be a need for QAtomicString,
> QAtomicWhatever and any struct or pointer I could think of?

No, no need to use the QAtomic classes when your data is already protected by 
a mutex.
For the original use case here (boolean flag as the only shared variable), 
using QAtomic is of course faster than using a full-blown mutex.
 
> However I dare to put the following statement: As long as you only consider
> "Intel Desktop"-like CPUs with *shared RAM", even if you have multiple
> CPUs (not just "cores"), when you specify your shared data ("shared
> between threads") as "volatile", you are fine.

I wouldn't bet on that. Most of the cases, you'll be lucky, but then, you 
might get the occasional situation in which you aren't lucky, and that will be 
quite hard to debug and to reproduce.
Most of the time, some other place in your program will do a aquire/release 
memory barrier, and thus your variable will get updated, despite not having a 
memory barrier on its own. But then, that relies on others parts of the 
program, which is not a good practice.

> Is that wrong? Uh oh, and now I'm totally confused, because I never
> bothered to declare e.g. shared classes (or rather their member data) as
> "volatile". I mean how is sharing a simple boolean (or integer) different
> from sharing say an instance of a QString? Isn't there then always the
> danger that one thread sees (partly) outdated data (coming from a stale
> cache) if you don't explicitly use "memory aquire" and "-release"?

First of all: QString is not thread-safe according to the documentation. Using 
the same QString instance from two threads will lead to problems such as 
crashes.
If you share data between threads, you need to protected them by a mutex or 
use aquire/release barriers manually. A mutex has implicit aquire/release 
semantics. When using aquire/release barriers properly, the data will be up-
to-date on all CPUs. It doesn't matter if you are using simple types such as 
ints or complex types like structs, since the memory barriers update all 
memory.
Using a mutex is the best first choice: It protects your data against 
concurrent access and makes sure the data updates is visible on all CPUs using 
memory barriers.
If you know what you are doing, you can use the QAtomic classes instead. While 
these will take care of memory barriers, these will not protect you against 
concurrent access. But then again, for the simple case of a boolean flag to 
stop a thread, concurrent access is not a problem.

Forget about volatile here, volatile should not be used for threading.

Regards,
Thomas
-- 
** Qt Developer Conference: http://qtconference.kdab.com/ **

Thomas McGuire | thomas.mcguire at kdab.com | Software Engineer
KDAB (Deutschland) GmbH&Co KG, a KDAB Group company
Tel. Germany +49-30-521325470, Sweden (HQ) +46-563-540090
KDAB - Qt Experts - Platform-independent software solutions
-------------- next part --------------
A non-text attachment was scrubbed...
Name: smime.p7s
Type: application/pkcs7-signature
Size: 3637 bytes
Desc: not available
URL: <http://lists.qt-project.org/pipermail/interest/attachments/20121023/2c019d16/attachment.bin>


More information about the Interest mailing list