3

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?

  • 3
    properly writing a threads safe function using atomics is very hard, and much more intricate than using plain mutexes. I'm not surprised helgrind doesn't want to bother trying to verify them – ratchet freak Oct 2 '13 at 14:53
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 and ANNOTATE_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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.