MultiThread Race Condition for Variables - GitMasterNikanjam/C_WiKi GitHub Wiki
in a multithreaded C++ program where one thread is writing to a global variable while another thread is reading from it, you should protect against race conditions.
Race conditions occur when the outcome of the program depends on the relative timing of events in different threads, and it can lead to unpredictable behavior and bugs. In this scenario, if one thread is writing to the global variable while another thread is reading from it, there's a risk that the reading thread might access an inconsistent or partially updated value if there is no synchronization mechanism in place.
To protect against race conditions, you can use synchronization mechanisms such as mutexes, semaphores, or atomic operations. In C++, you can use std::mutex to protect shared resources:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int globalVariable = 0;
void writerFunction() {
// Lock the mutex before writing to the global variable
mtx.lock();
globalVariable = 42; // Write to the global variable
mtx.unlock(); // Unlock the mutex
}
void readerFunction() {
// Lock the mutex before reading from the global variable
mtx.lock();
std::cout << "The value of globalVariable is: " << globalVariable << std::endl;
mtx.unlock(); // Unlock the mutex
}
int main() {
std::thread writerThread(writerFunction);
std::thread readerThread(readerFunction);
writerThread.join();
readerThread.join();
return 0;
}
In this example, both the writer thread and the reader thread lock the mutex before accessing the global variable. This ensures that only one thread can access the variable at a time, preventing race conditions. It's important to unlock the mutex after accessing the shared resource to allow other threads to acquire the lock.
you can use std::lock_guard to simplify the code by automatically locking and unlocking the mutex. Here's the modified example using std::lock_guard:
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int globalVariable = 0;
void writerFunction() {
// Use std::lock_guard to automatically lock and unlock the mutex
std::lock_guard<std::mutex> lock(mtx);
globalVariable = 42; // Write to the global variable
}
void readerFunction() {
// Use std::lock_guard to automatically lock and unlock the mutex
std::lock_guard<std::mutex> lock(mtx);
std::cout << "The value of globalVariable is: " << globalVariable << std::endl;
}
int main() {
std::thread writerThread(writerFunction);
std::thread readerThread(readerFunction);
writerThread.join();
readerThread.join();
return 0;
}
In this version, std::lock_guard is used to create a lock guard object lock, which locks the mutex mtx upon construction and unlocks it upon destruction (when it goes out of scope). This ensures that the mutex is always properly locked and unlocked, even in the presence of exceptions or early returns.
A shared lock (also known as a shared mutex or reader-writer lock) allows multiple threads to acquire a lock in read mode simultaneously, as long as no thread is holding the lock in write mode. This can be useful in scenarios where multiple threads need to read a shared resource concurrently but only one thread at a time should be allowed to write to it.
In C++, you can use std::shared_mutex (or std::shared_timed_mutex for timed locking) to implement a shared lock. However, std::shared_mutex is available only in C++17 and later.
Let's adapt the previous example to use std::shared_mutex:
#include <iostream>
#include <thread>
#include <shared_mutex>
std::shared_mutex smtx;
int globalVariable = 0;
void writerFunction() {
// Use std::unique_lock to write to the global variable
std::unique_lock<std::shared_mutex> lock(smtx);
globalVariable = 42; // Write to the global variable
}
void readerFunction() {
// Use std::shared_lock to read from the global variable
std::shared_lock<std::shared_mutex> lock(smtx);
std::cout << "The value of globalVariable is: " << globalVariable << std::endl;
}
int main() {
std::thread writerThread(writerFunction);
std::thread readerThread(readerFunction);
writerThread.join();
readerThread.join();
return 0;
}
In this version, std::unique_lock is used for writing (to get exclusive access), and std::shared_lock is used for reading (to allow shared access). This ensures that multiple reader threads can concurrently access the global variable, while writer threads will have exclusive access to it.
Using std::shared_mutex is a good approach if your scenario involves frequent reading and occasional writing to a shared resource, as it allows for more concurrency compared to a simple mutex lock. However, keep in mind that std::shared_mutex incurs a slightly higher overhead compared to std::mutex, so you should use it judiciously based on your specific requirements.
In a scenario where only one thread reads while another writes, using a shared lock isn't necessary. A shared lock is useful when multiple threads might read simultaneously while ensuring exclusive access for writing.
For the case of one thread reading and one thread writing, you can simply use a standard mutex lock to ensure exclusive access to the shared resource. Here's how you would modify the example:
In this case, using a std::mutex ensures that only one thread (either the reader or the writer) can access the global variable at any given time. So, there's no need for the added complexity of a shared lock.