Race Condition: A situation like this, where several processes access and manipulate the same data concurrently and the outcome of the execution depends on the particular order in which the access takes place, is called a race condition.
Mutex locks (or spinlocks) are generally considered the simplest of synchronization tools.
The main disadvantage of the implementation given here is that it requires busy waiting.
In the C++11 threading library, the mutexes are in the <mutex> header file. The class representing a mutex is the std::mutex class.
There are two important methods of mutex: lock() and unlock()
// mutex::lock/unlock #include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex std::mutex mtx; // mutex for critical section void print_thread_id (int id) { // critical section (exclusive access to std::cout signaled by locking mtx): mtx.lock(); std::cout << "thread #" << id << '\n'; mtx.unlock(); } int main (){ std::thread threads[10]; // spawn 10 threads: for (int i=0; i<10; ++i) threads[i] = std::thread(print_thread_id,i+1); for (auto& th : threads) th.join(); return 0; }
std::lock_guard
std::lock_guard is a class template, which implements the RAII for mutex.
It wraps the mutex inside it’s object and locks the attached mutex in its constructor. When it’s destructor is called it releases the mutex.
// mutex::lock/unlock #include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex std::mutex mtx; // mutex for critical section void print_thread_id (int id) { // critical section (exclusive access to std::cout signaled by locking mtx): std::lock_guard<std::mutex> lockGuard(mtx); std::cout << "thread #" << id << '\n'; // Once function exits, then destructor of lockGuard Object will be called. // In destructor it unlocks the mutex. } int main (){ std::thread threads[10]; // spawn 10 threads: for (int i=0; i<10; ++i) threads[i] = std::thread(print_thread_id,i+1); for (auto& th : threads) th.join(); return 0; }
std::unique_lock
lock_guard
and unique_lock
are pretty much the same thing; lock_guard
is a restricted version with a limited interface.
A lock_guard
always holds a lock from its construction to its destruction. A unique_lock
can be created without immediately locking, can unlock at any point in its existence, and can transfer ownership of the lock from one instance to another.
So you always use lock_guard
, unless you need the capabilities of unique_lock
.
A condition_variable
needs a unique_lock
.
// mutex::lock/unlock #include <iostream> // std::cout #include <thread> // std::thread #include <mutex> // std::mutex std::mutex mtx; // mutex for critical section void print_thread_id (int id) { // critical section (exclusive access to std::cout signaled by locking mtx): std::unique_lock<std::mutex> lock(mtx); std::cout << "thread #" << id << '\n'; } int main (){ std::thread threads[10]; // spawn 10 threads: for (int i=0; i<10; ++i) threads[i] = std::thread(print_thread_id,i+1); for (auto& th : threads) th.join(); return 0; }
Reference
http://www.cplusplus.com/reference/mutex/mutex/lock/
https://thispointer.com//c11-multithreading-part-4-data-sharing-and-race-conditions/
https://thispointer.com//c11-multithreading-part-5-using-mutex-to-fix-race-conditions/
https://stackoverflow.com/questions/20516773/stdunique-lockstdmutex-or-stdlock-guardstdmutex