Whenever feasible use tools to validate your concurrent code
Experience shows that concurrent code is exceptionally hard to get right and that compile-time checking, run-time checks, and testing are less effective at finding concurrency errors than they are at finding errors in sequential code. Subtle concurrency errors can have dramatically bad effects, including memory corruption, deadlocks, and security vulnerabilities.
Example
???
Note
Thread safety is challenging, often getting the better of experienced programmers: tooling is an important strategy to mitigate those risks. There are many tools "out there", both commercial and open-source tools, both research and production tools. Unfortunately people's needs and constraints differ so dramatically that we cannot make specific recommendations, but we can mention:
and some older versions of GCC have some support for static annotation of thread safety properties. Consistent use of this technique turns many classes of thread-safety errors into compile-time errors. The annotations are generally local (marking a particular data member as guarded by a particular mutex), and are usually easy to learn. However, as with many static tools, it can often present false negatives; cases that should have been caught but were allowed.
- Static enforcement tools: both clang
is a powerful example of dynamic tools: it changes the build and execution of your program to add bookkeeping on memory access, absolutely identifying data races in a given execution of your binary. The cost for this is both memory (5-10x in most cases) and CPU slowdown (2-20x). Dynamic tools like this are best when applied to integration tests, canary pushes, or unit tests that operate on multiple threads. Workload matters: When TSAN identifies a problem, it is effectively always an actual data race, but it can only identify races seen in a given execution.
- dynamic enforcement tools: Clang's Thread Sanitizer (aka TSAN)
Enforcement
It is up to an application builder to choose which support tools are valuable for a particular application.
CP.con: Concurrency
This section focuses on relatively ad-hoc uses of multiple threads communicating through shared data.
- For parallel algorithms, see parallelism
- For inter-task communication without explicit sharing, see messaging
- For vector parallel code, see vectorization
- For lock-free programming, see lock free
Concurrency rule summary:
- CP.20: Use RAII, never plain
lock()/unlock() - CP.21: Use
std::lock()orstd::scoped_lockto acquire multiplemutexes - CP.22: Never call unknown code while holding a lock (e.g., a callback)
- CP.23: Think of a joining
threadas a scoped container - CP.24: Think of a
threadas a global container - CP.25: Prefer
gsl::joining_threadoverstd::thread - CP.26: Don't
detach()a thread - CP.31: Pass small amounts of data between threads by value, rather than by reference or pointer
- CP.32: To share ownership between unrelated
threads useshared_ptr - CP.40: Minimize context switching
- CP.41: Minimize thread creation and destruction
- CP.42: Don't
waitwithout a condition - CP.43: Minimize time spent in a critical section
- CP.44: Remember to name your
lock_guards andunique_locks - CP.50: Define a
mutextogether with the data it guards. Usesynchronized_value<T>where possible - ??? when to use a spinlock
- ??? when to use
try_lock() - ??? when to prefer
lock_guardoverunique_lock - ??? Time multiplexing
- ??? when/how to use
new thread