After we build and run our application, we can see that its output is similar to the output of the thread application. However, there are also noticeable differences:
Firstly, the output is not garbled. Secondly, we can see a clear order—no worker is interrupted by another worker, and each begin is followed by the corresponding end. The difference lies in the highlighted fragments of the source code. We create a global mutex m:
std::mutex m;
Then, we use lock_guard to protect our critical section of code, which starts from the line that prints Worker X begins and ends at the line that prints Worker X ends.
lock_guard is a wrapper on top of a mutex that uses an RAII (short for Resource Acquisition Is Initialization) technique to automatically lock the corresponding mutex in the constructor when the lock object is defined, and unlock it in the destructor after reaching the end of its scope. That is why we add extra curly braces to define the scope of our critical section:
{
std::lock_guard<std::mutex> g(m);
std::cout << "Worker " << index << " begins" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(50));
std::cout << "Worker " << index << " ends" << std::endl;
}
Though it is possible to lock and unlock the mutex explicitly, by calling its lock and unlock methods, it is not recommended. Forgetting to unlock a locked mutex leads to multithreading synchronization issues that are hard to detect and hard to debug. The RAII approach unlocks mutexes automatically, making code safer, easier to read, and easier to understand.