Location>code7788 >text

Various locks in C++

Popularity:329 ℃/2024-11-06 02:12:09

Mutually Exclusive Lock (std::mutex

  • This is the most basic type of lock. It is used to protect a shared resource so that at any given moment, at most one thread can acquire the lock and thus access the protected resource. When a thread acquires a mutual exclusion lock, other threads trying to acquire the lock are blocked until the thread holding the lock releases it.
  • For example, in a multi-threaded program, if multiple threads need to access and modify the same global variable, a mutex lock can be used to ensure that only one thread is able to perform the modification operation at the same time, avoiding erroneous results caused by data contention.
 1 #include <iostream>
 2 #include <mutex>
 3 #include <thread>
 4 
 5 std::mutex m;
 6 int counter = 0;
 7 
 8 void increment() {
 9     m.lock();
10     counter++;
11     std::cout << "Counter value in thread " << std::this_thread::get_id() << " is " << counter << std::endl;
12     ();
13 }
14 
15 int main() {
16     std::thread t1(increment);
17     std::thread t2(increment);
18     ();
19     ();
20     return 0;
21 }
 

Recursive Mutual Exclusion Lock (std::recursive_mutex

  • A recursive mutual exclusion lock allows the same thread to acquire the lock multiple times. It keeps track of how many times the lock has been acquired, adding 1 to the count for each acquisition and subtracting 1 each time the lock is released. When the count reaches 0, the lock is actually released and can be acquired by other threads.
  • Suppose that in a complex chain of function calls, function A calls function B, which in turn calls function A, and all these functions need to access the same protected resource. If you use a normal mutex lock, you will have a deadlock, which can be avoided with a recursive mutex lock because it allows the same thread to acquire the lock multiple times.
 1 #include <iostream>
 2 #include <mutex>
 3 #include <thread>
 4 
 5 std::recursive_mutex rm;
 6 
 7 void recursiveFunction(int count) {
 8     rm.lock();
 9     if (count > 0) {
10         std::cout << "Recursive call with count = " << count << std::endl;
11         recursiveFunction(count - 1);
12     }
13     ();
14 }
15 
16 int main() {
17     std::thread t(recursiveFunction, 3);
18     ();
19     return 0;
20 }

 

Read and write locks (std::shared_mutex) C++17 onwards.

  • Read and write locks are mainly used to distinguish between read and write operations on shared resources. It has two acquisition modes: shared mode (read mode) and exclusive mode (write mode).
  • Multiple threads can acquire read and write locks in shared mode at the same time, which means they can read shared resources at the same time without interfering with each other. However, when a thread wants to acquire a read/write lock in exclusive mode (to perform a write operation), no other thread (either read or write) can acquire the lock until the write operation is completed and the lock is released. This mechanism can improve the concurrent performance of a program in scenarios where there are a large number of read operations and a small number of write operations. For example, in a caching system, multiple threads may frequently read data in the cache, and write operations are performed only when the cached data needs to be updated, and the use of read and write locks can handle this situation well.
 1 #include <iostream>
 2 #include <shared_mutex>
 3 #include <thread>
 4 #include <vector>
 5 
 6 std::shared_mutex smtx;
 7 int shared_data = 0;
 8 
 9 void read_data() {
10     std::shared_lock<std::shared_mutex> lock(smtx);
11     std::cout << "Read data: " << shared_data << std::endl;
12 }
13 
14 void write_data(int new_value) {
15     std::unique_lock<std::shared_mutex> lock(smtx);
16     shared_data = new_value;
17     std::cout << "Wrote data: " << shared_data << std::endl;
18 }
19 
20 int main() {
21     std::vector<std::thread> read_threads;
22     for (int i = 0; i < 5; i++) {
23         read_threads.push_back(std::thread(read_data));
24     }
25     std::thread write_thread(write_data, 10);
26     for (auto& t : read_threads) {
27         ();
28     }
29     write_thread.join();
30     return 0;
31 }

 

Timed Mutual Exclusion Lock (std::timed_mutex

  • Timed mutex locks arestd::mutexThe Extension. In addition to havingstd::mutexIn addition to its basic functionality, it allows threads to set a timeout when attempting to acquire a lock.
  • If a lock cannot be acquired within the specified timeout period, the thread does not wait forever, but can perform other actions or return an error message. This is useful in time-sensitive scenarios, such as in a real-time system where a thread can't block indefinitely because it's waiting for a lock, but needs to give up on acquiring it after a certain amount of time and do other processing.
 1 #include <iostream>
 2 #include <chrono>
 3 #include <thread>
 4 #include <mutex>
 5 
 6 std::timed_mutex tm;
 7 
 8 void tryLockFunction() {
 9     if (tm.try_lock_for(std::chrono::seconds(1))) {
10         std::cout << "Acquired lock" << std::endl;
11         std::this_thread::sleep_for(std::chrono::seconds(2));
12         ();
13     } else {
14         std::cout << "Could not acquire lock in time" << std::endl;
15     }
16 }
17 
18 int main() {
19     std::thread t1(tryLockFunction);
20     std::thread t2(tryLockFunction);
21     ();
22     ();
23     return 0;
24 }

 

Recursively timed mutex locks (std::recursive_timed_mutex

  • This is a lock that combines the features of recursive mutual exclusion locks and timed mutual exclusion locks. It allows the same thread to acquire the lock multiple times, and you can set a timeout when acquiring the lock.
  • After a thread acquires such a lock multiple times, it needs to release the lock the same number of times before the lock is actually released, and the thread can take action if it fails to acquire the lock within the timeout period during the acquisition process.
 1 #include <iostream>
 2 #include <chrono>
 3 #include <thread>
 4 #include <mutex>
 5 
 6 std::recursive_timed_mutex rtm;
 7 
 8 void recursiveTryLockFunction(int count) {
 9     if (rtm.try_lock_for(std::chrono::seconds(1))) {
10         std::cout << "Recursive acquired lock, count = " << count << std::endl;
11         if (count > 0) {
12             recursiveTryLockFunction(count - 1);
13         }
14         ();
15     } else {
16         std::cout << "Could not recursively acquire lock in time" << std::endl;
17     }
18 }
19 
20 int main() {
21     std::thread t(recursiveTryLockFunction, 3);
22     ();
23     return 0;
24 }

 

Spin lock (usually used)std::atomic_flag(Realization)

  • Spin locks are a busy-waiting locking mechanism. When a thread tries to acquire a spin lock and the lock is already occupied, the thread does not go into a blocking state, but instead keeps checking to see if the ("spin") lock has been released.
  • Spin locks may perform better with shorter wait times because it avoids the overhead of thread switching. However, if the wait time is too long, it can lead to a waste of CPU resources as the thread keeps hogging CPU resources for checking. It is generally used in underlying code or in scenarios where performance requirements are extremely high and wait times are expected to be short.
 1 #include <iostream>
 2 #include <atomic>
 3 #include <thread>
 4 
 5 std::atomic_flag spinLock = ATOMIC_FLAG_INIT;
 6 
 7 void criticalSection() {
 8     while (spinLock.test_and_set()) {
 9         // spin waiting
10     }
11     std::cout << "Entered critical section" << std::endl;
12     // critical zone operation
13     ();
14 }
15 
16 int main() {
17     std::thread t1(criticalSection);
18     std::thread t2(criticalSection);
19     ();
20     ();
21     return 0;
22 }

 

Conditional variables (std::condition_variable) used in conjunction with mutex locks for synchronization (conditionals are not technically locks, but are often used together in thread synchronization scenarios)

  • A condition variable is not a lock per se, but it is often used with a mutual exclusion lock to enable synchronization between threads. It allows a thread to wait for a certain condition to be met before continuing execution.
  • For example, in a producer-consumer model, a consumer thread can use a conditional variable to wait when the buffer is empty until the producer thread produces a product and notifies the consumer thread. Mutual exclusion locks are used to protect the buffer as a shared resource, and conditional variables are used to enable communication and synchronization between threads.
 1 #include <iostream>
 2 #include <thread>
 3 #include <mutex>
 4 #include <condition_variable>
 5 #include <queue>
 6 
 7 std::mutex mtx;
 8 std::condition_variable cv;
 9 std::queue<int> buffer;
10 const int bufferSize = 5;
11 
12 void producer() {
13     for (int i = 0; i < 10; ++i) {
14         std::unique_lock<std::mutex> lock(mtx);
15         while (() == bufferSize) {
16             (lock);
17         }
18         (i);
19         std::cout << "Produced: " << i << std::endl;
20         cv.notify_all();
21     }
22 }
23 
24 void consumer() {
25     for (int i = 0; i < 10; ++i) {
26         std::unique_lock<std::mutex> lock(mtx);
27         while (()) {
28             (lock);
29         }
30         int data = ();
31         ();
32         std::cout << "Consumed: " << data << std::endl;
33         cv.notify_all();
34     }
35 }
36 
37 int main() {
38     std::thread producerThread(producer);
39     std::thread consumerThread(consumer);
40     ();
41     ();
42     return 0;
43 }