It seems that Helgrind treats atomics the same way as normal read/write operations. So, using atomics will result in lots of false positives.
I don’t know exactly how ThreadSanitizer deals with atomics, but I have found a method called tsan_atomic32_load. I assume that it can distinguish between atomics and normal read/write operations.
In practice, however, the best way seems to be to avoid atomics in order to use tools to detect race conditions. Otherwise, you end up with to many false positives.
There are specialized tools to verify concurrent data structures, e.g., Spin. Although it looks powerful, it seems to be out-of-scope for regular applications.
How do big projects deal with the problem of false-positives? Do they disencourage the use of atomics, do they use suppression files, or do they simply not bother with race detectors?
1
An approach that you haven’t mentioned is to explicitly annotate your code to tell the tool what your custom locking/refcounting code is meant to do. Helgrind has an example of how to do this in its manual:
It is also possible to mark up the effects of thread-safe reference counting using the
ANNOTATE_HAPPENS_BEFORE
,ANNOTATE_HAPPENS_AFTER
andANNOTATE_HAPPENS_BEFORE_FORGET_ALL
, macros. Thread-safe reference counting using an atomically incremented/decremented refcount variable causes Helgrind problems because a one-to-zero transition of the reference count means the accessing thread has exclusive ownership of the associated resource (normally, a C++ object) and can therefore access it (normally, to run its destructor) without locking. Helgrind doesn’t understand this, and markup is essential to avoid false positives.Here are recommended guidelines for marking up thread safe reference counting in C++. You only need to mark up your release methods — the ones which decrement the reference count. Given a class like this:
class MyClass { unsigned int mRefCount; void Release ( void ) { unsigned int newCount = atomic_decrement(&mRefCount); if (newCount == 0) { delete this; } } }
the release method should be marked up as follows:
void Release ( void ) { unsigned int newCount = atomic_decrement(&mRefCount); if (newCount == 0) { ANNOTATE_HAPPENS_AFTER(&mRefCount); ANNOTATE_HAPPENS_BEFORE_FORGET_ALL(&mRefCount); delete this; } else { ANNOTATE_HAPPENS_BEFORE(&mRefCount); } }
ThreadSanitizer says:
ThreadSanitizer supports DynamicAnnotations which can make any tricky synchronization (including lock-less synchronization) to be ThreadSanitizer-friendly.
As far as coding style and big projects go:
- A search for
ANNOTATE_HAPPENS_BEFORE
in the Chromium source turns up plenty of results (e.g. this atomic refcount code), so it’s definitely possible for a large project to use this approach. - Putting these annotations in your coding style is probably a good idea if you intend to use hand-rolled synchronisation primitives.
At the same time, having to use these annotations a lot indicates that you have a lot of custom thread synchronisation code, which might be bad in itself.