Multithreading is a difficult and subtle subject, with many pitfalls that are obvious only in hindsight. In this chapter we have learned:
volatile, while useful for dealing directly with hardware, is insufficient for thread-safety. std::atomic<T> for scalar T (up to the size of a machine register) is the right way to access shared data without races and without locks. The most important primitive atomic operation is compare-and-swap, which in C++ is spelled compare_exchange_weak.
To force threads to take turns accessing shared non-atomic data, we use std::mutex. Always lock mutexes via an RAII class such as std::unique_lock<M>. Remember that although C++17 class template argument deduction allows us to omit the <M> from these templates' names, that is just a syntactic convenience; they remain template classes.
Always clearly indicate which data is...