Synchronization and locks in C++

In multi-threaded programming, synchronization refers to the coordination of thread execution to ensure that threads access shared resources in a safe and predictable manner. One common way to achieve synchronization in C++ is through the use of locks, which provide mutual exclusion and prevent multiple threads from accessing shared resources simultaneously.

C++ provides several types of locks in the standard library’s `` header. Here are some of the most commonly used ones:

– `std::mutex`: A basic mutual exclusion lock that can be locked and unlocked by a single thread at a time.
– `std::recursive_mutex`: A mutual exclusion lock that can be locked and unlocked recursively by the same thread.
– `std::timed_mutex`: A mutual exclusion lock that can be locked for a specified duration of time.
– `std::recursive_timed_mutex`: A timed mutual exclusion lock that can be locked and unlocked recursively by the same thread.

Here’s an example of how to use a `std::mutex` to synchronize access to a shared resource:

c++
#include 
#include 
#include 

std::mutex myMutex;

void threadFunction(int& counter) {
    for (int i = 0; i < 1000000; ++i) {
        std::lock_guard lock(myMutex); // lock the mutex
        ++counter; // access the shared resource
    }
}

int main() {
    int counter = 0;
    std::thread t1(threadFunction, std::ref(counter));
    threadFunction(counter); // call the function directly on the main thread
    t.join(); // wait for the thread to finish
    std::cout << "Counter value: " << counter << '\n';
    return 0;
}

In this example, we define a function `threadFunction()` that will be executed by a new thread. To synchronize access to the shared resource `counter`, we use a `std::lock_guard` object to lock the `myMutex` mutex. This ensures that only one thread can access `counter` at a time.

Note that we pass `counter` to `threadFunction()` as a reference, and use `std::ref()` to explicitly pass it as a reference to the thread constructor. This is necessary because `std::thread` copies its arguments by default, and we want both threads to access the same instance of `counter`.

It's important to note that locking a mutex can introduce overhead and potentially block threads if they are competing for the same resource. To minimize this overhead, you should try to keep the critical section (the code inside the locked region) as short as possible.

In addition to locks, C++ also provides other synchronization primitives like condition variables (`std::condition_variable`) and semaphores (`std::semaphore`). These tools can be used to implement more complex synchronization patterns and avoid deadlocks and livelocks.

Overall, synchronization and locksare powerful tools for managing access to shared resources in multi-threaded programs. By carefully controlling access to critical sections, you can avoid race conditions, data corruption, and other threading issues that can cause bugs and crashes in your code.