Quick summary ↬
In C++, a mutex (short for mutual exclusion) is a synchronization primitive used to protect shared resources from simultaneous access by multiple threads. It ensures that only one thread can access the protected resource at a given time, preventing data races and maintaining thread safety.
Introduction
Mutexes provide two fundamental operations:
Locking: A thread can lock a mutex to gain exclusive access to the protected resource. If the mutex is already locked by another thread, the thread attempting to lock it will be blocked until the mutex becomes available.
Unlocking: Once a thread has finished accessing the protected resource, it must unlock the mutex to release its ownership. This allows other threads to acquire the mutex and access the resource.
Consider the below code,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
|
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
// mutex,
// recursive_mutex,
// shared_mutex,
// timed_mutex,
// shared_timed_mutex,
// recursive_timed_mutex
// Example using std::mutex
std::mutex mutex1;
void mutexExample()
{
std::lock_guard<std::mutex> lock(mutex1);
std::cout << "Thread " << std::this_thread::get_id() << " acquired std::mutex." << std::endl;
// Critical section
std::this_thread::sleep_for(std::chrono::seconds(1));
}
// Example using std::recursive_mutex
std::recursive_mutex recursiveMutex;
void recursiveMutexExample(int depth)
{
std::lock_guard<std::recursive_mutex> lock(recursiveMutex);
std::cout << "Thread " << std::this_thread::get_id() << " acquired std::recursive_mutex (depth: " << depth << ")." << std::endl;
// Recursive call
if (depth > 0)
recursiveMutexExample(depth - 1);
}
// Example using std::shared_mutex
std::shared_mutex sharedMutex;
int sharedData = 0;
void sharedMutexExample(bool writeAccess)
{
if (writeAccess)
{
std::unique_lock<std::shared_mutex> lock(sharedMutex);
sharedData++;
std::cout << "Thread " << std::this_thread::get_id() << " acquired std::shared_mutex (write access). Data: " << sharedData << std::endl;
}
else
{
std::shared_lock<std::shared_mutex> lock(sharedMutex);
std::cout << "Thread " << std::this_thread::get_id() << " acquired std::shared_mutex (read access). Data: " << sharedData << std::endl;
}
}
int main()
{
// Example with std::mutex
std::thread t1(mutexExample);
std::thread t2(mutexExample);
t1.join();
t2.join();
// Example with std::recursive_mutex
std::thread t3([&]() { recursiveMutexExample(3); });
t3.join();
// Example with std::shared_mutex
std::thread t4([&]() { sharedMutexExample(false); });
std::thread t5([&]() { sharedMutexExample(true); });
std::thread t6([&]() { sharedMutexExample(false); });
t4.join();
t5.join();
t6.join();
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
#include <iostream>
#include <thread>
#include <mutex>
#include <shared_mutex>
#include <chrono>
// Example using std::shared_timed_mutex
std::shared_timed_mutex sharedTimedMutex;
int sharedData = 0;
void sharedTimedMutexExample(bool writeAccess)
{
if (writeAccess)
{
if (sharedTimedMutex.try_lock_for(std::chrono::seconds(2)))
{
sharedData++;
std::cout << "Thread " << std::this_thread::get_id() << " acquired std::shared_timed_mutex (write access). Data: " << sharedData << std::endl;
sharedTimedMutex.unlock();
}
else
{
std::cout << "Thread " << std::this_thread::get_id() << " failed to acquire std::shared_timed_mutex (write access)." << std::endl;
}
}
else
{
if (sharedTimedMutex.try_lock_shared_for(std::chrono::seconds(2)))
{
std::cout << "Thread " << std::this_thread::get_id() << " acquired std::shared_timed_mutex (read access). Data: " << sharedData << std::endl;
sharedTimedMutex.unlock_shared();
}
else
{
std::cout << "Thread " << std::this_thread::get_id() << " failed to acquire std::shared_timed_mutex (read access)." << std::endl;
}
}
}
int main()
{
// Example with std::shared_timed_mutex
std::thread t1([&]() { sharedTimedMutexExample(false); });
std::thread t2([&]() { sharedTimedMutexExample(true); });
std::thread t3([&]() { sharedTimedMutexExample(false); });
t1.join();
t2.join();
t3.join();
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
#include <iostream>
#include <thread>
#include <mutex>
#include <chrono>
// Example using std::timed_mutex
std::timed_mutex timedMutex;
void timedMutexExample()
{
if (timedMutex.try_lock_for(std::chrono::seconds(2)))
{
std::cout << "Thread " << std::this_thread::get_id() << " acquired std::timed_mutex." << std::endl;
// Critical section
std::this_thread::sleep_for(std::chrono::seconds(3));
timedMutex.unlock();
}
else
{
std::cout << "Thread " << std::this_thread::get_id() << " failed to acquire std::timed_mutex." << std::endl;
}
}
// Example using std::recursive_timed_mutex
std::recursive_timed_mutex recursiveTimedMutex;
void recursiveTimedMutexExample(int depth)
{
if (recursiveTimedMutex.try_lock_for(std::chrono::seconds(1)))
{
std::cout << "Thread " << std::this_thread::get_id() << " acquired std::recursive_timed_mutex (depth: " << depth << ")." << std::endl;
// Recursive call
if (depth > 0)
recursiveTimedMutexExample(depth - 1);
recursiveTimedMutex.unlock();
}
else
{
std::cout << "Thread " << std::this_thread::get_id() << " failed to acquire std::recursive_timed_mutex." << std::endl;
}
}
int main()
{
// Example with std::timed_mutex
std::thread t1(timedMutexExample);
std::thread t2(timedMutexExample);
t1.join();
t2.join();
// Example with std::recursive_timed_mutex
std::thread t3([&]() { recursiveTimedMutexExample(3); });
t3.join();
return 0;
}
|
Using a mutex is essential in multi-threaded programs to protect shared resources and avoid race conditions that can lead to unpredictable behavior or data corruption. By properly synchronizing access to shared data with mutexes, you can ensure the integrity and consistency of your program’s execution.