Shared Variables
Now that we've learnt about threads, let's discuss information sharing between them.
If more than one thread is sharing a variable, you have to coordinate the access. That’s the job for mutexes and locks in C++.
Data race
A data race is a state in which at least two threads access a shared data at the same time, and at least one of the threads is a writer. Therefore the program has undefined behavior.
You can observe very well the interleaving of threads if a few threads write to std::cout
. The output stream std::cout
is, in this case, the shared variable.
//...#include <thread>//...using namespace std;struct Worker{Worker(string n):name(n){};void operator() (){for (int i= 1; i <= 3; ++i){this_thread::sleep_for(chrono::milliseconds(200));cout << name << ": " << "Work " << i << endl;}}private:string name;};// mainthread herb= thread(Worker("Herb"));thread andrei= thread(Worker(" Andrei"));thread scott= thread(Worker (" Scott"));thread bjarne= thread(Worker(" Bjarne"));
The output on std::cout
is uncoordinated.
🔑 The streams are thread-safe
The C++11 standard guarantees that the characters are written atomically. Therefore you don’t need to protect them. You only have to protect the interleaving of the threads on the stream if the characters should be written or read in the right sequence. That guarantee holds for the input and output streams.
std::cout
is in the example the shared variable, which should have exclusive access to the stream.
Mutexes
Mutex (mutual exclusion) m
guarantees that only one thread can access the critical region at one time. They need the header m
locks the critical section by the call m.lock()
and unlocks it by m.unlock().
//...#include <mutex>#include <thread>//...using namespace std;std::mutex mutexCout;struct Worker{Worker(string n):name(n){};void operator() (){for (int i= 1; i <= 3; ++i){this_thread::sleep_for(chrono::milliseconds(200));mutexCout.lock();cout << name << ": " << "Work " << i << endl;mutexCout.unlock();}}private:string name;};// mainthread herb= thread(Worker("Herb"));thread andrei= thread(Worker(" Andrei"));thread scott= thread(Worker (" Scott"));thread bjarne= thread(Worker(" Bjarne"));
Now each thread after each other writes coordinated to std::cout
because it uses the same mutex mutexCout
.