C++ Reactive Programming
上QQ阅读APP看书,第一时间看更新

Mutexes

A mutex is a mechanism used in concurrency control to prevent race conditions. The function of a mutex is to prevent a thread of execution to enter its critical section at the same time another concurrent thread enters its own critical section. It is a lockable object designed to signal when the critical sections of code need exclusive access, thereby restricting other concurrent threads with the same protection in execution as well as memory access. The C++ 11 standard introduced an std::mutex class into the standard library to achieve data protection across concurrent threads.

The std::mutex class consist of the lock() and unlock() functions to create a critical section in code. One thing to keep in mind while using the member functions to create critical sections is that you should never skip an unlock function associated with a lock function to mark the critical section in code.

Now, let's discuss the same code we used for discussing Lambdas with threads. There, we observed that the output of the program was scrambled due to a race condition with a common resource, std::cout, and std::ostream operators. That code is now being rewritten using std::mutex to print the thread index:

#include <iostream> 
#include <thread> 
#include <mutex> 
#include <vector>  
std::mutex m; 
int main() 
{ 
    std::vector<std::thread> threads; 
 
    for (int i = 1; i < 10; ++i) 
    { 
        threads.push_back(std::thread( [i]() { 
            m.lock(); 
            std::cout << "Thread #" << i << std::endl; 
            m.unlock();
        })); 
    }      
    std::for_each(threads.begin(), threads.end(), [](std::thread &t) { 
        t.join(); 
    }); 
} 

The output for the preceding code may look as follows:

Thread #1 
Thread #2 
Thread #3 
Thread #4 
Thread #5 
Thread #6 
Thread #7 
Thread #8 
Thread #9 

In the preceding code, the mutex is used to protect the shared resource, which is the std::cout and cascaded std::ostream operators. Unlike the older example, the addition of a mutex in the code now avoids the scrambled output, but it will appear in a random order. The use of lock() and unlock() functions in the std::mutex class guarantees the output is not garbled. However, the practice to call member functions directly is not recommended, because you need to call unlock on every code path in the function, including the exception scenarios as well. Instead, C++ standard introduced a new template class, std::lock_guard, which implemented the Resource Acquisition Is Initialization (RAII) idiom for a mutex. It locks the supplied mutex in the constructor and unlocks it in the destructor. The implementation of this template class is available in the <mutex> standard header library. The previous example can be rewritten using std::lock_guard as follows:

std::mutex m; 
int main() 
{ 
    std::vector<std::thread> threads;  
    for (int i = 1; i < 10; ++i) 
    { 
        threads.push_back(std::thread( [i]() { 
            std::lock_guard<std::mutex> local_lock(m); 
            std::cout << "Thread #" << i << std::endl; 
        })); 
    }      
    std::for_each(threads.begin(), threads.end(), [](std::thread &t) { 
        t.join(); 
    }); 
}

In the preceding code, the mutex that protects the critical section is at global scope and the std::lock_guard object is local to the Lambda each time thread execution happens. This way, as soon as the object is constructed, the mutex acquires the lock. It unlocks the mutex with the call to destructor when the Lambda execution is over.

RAII is a C++ idiom where the lifetime of entities such as database/file handles, socket handles, mutexes, dynamically allocated memory on the heap, and so on are bounded to the life cycle of the object holding it. You can read more about RAII at the following Wikipedia page: https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization.