Concurrency and multithreading are essential for modern software development, allowing programs to perform multiple tasks seemingly simultaneously, improving responsiveness and efficiency. C++ provides a rich set of tools in its standard library (primarily introduced in C++11) to support concurrent programming.
std::thread
)The std::thread
class represents a single thread of execution. You can create new threads to execute functions concurrently.
join()
: The calling thread waits for the thread associated with the std::thread
object to complete its execution. You should typically call join()
before the std::thread
object goes out of scope to ensure proper resource management.detach()
: The std::thread
object is detached from the calling thread, and the new thread continues to run independently in the background. You lose direct control over its execution and need to ensure that the detached thread manages its resources properly.#include <iostream>
#include <thread>
#include <chrono>
void workerFunction(int id) {
std::cout << "Worker thread " << id << " started." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(2));
std::cout << "Worker thread " << id << " finished." << std::endl;
}
int main() {
std::thread t1(workerFunction, 1);
std::thread t2(workerFunction, 2);
std::cout << "Main thread continues." << std::endl;
t1.join(); // Wait for t1 to finish
std::cout << "Thread 1 joined." << std::endl;
t2.detach(); // t2 will run independently
std::cout << "Thread 2 detached." << std::endl;
// Be cautious with detached threads; ensure they don't access invalid memory
std::this_thread::sleep_for(std::chrono::seconds(3)); // Allow detached thread to potentially finish
std::cout << "Main thread finished." << std::endl;
return 0;
}
Mutexes are fundamental synchronization primitives used to protect shared data from race conditions by ensuring that only one thread can access a critical section of code at a time.
std::mutex
A basic mutex that can be locked and unlocked.
std::lock_guard
A RAII (Resource Acquisition Is Initialization) wrapper for a mutex. It automatically locks the mutex upon construction and automatically unlocks it when the std::lock_guard
object goes out of scope, even if exceptions are thrown.
std::unique_lock
A more flexible RAII wrapper for a mutex than std::lock_guard
. It allows deferred locking, time-constrained locking attempts, recursive locking (for std::recursive_mutex
), and transfer of ownership of the lock.
#include <iostream>
#include <thread>
#include <mutex>
std::mutex mtx;
int sharedCounter = 0;
void incrementWithLockGuard() {
std::lock_guard<std::mutex> lock(mtx); // Lock acquired upon construction, released on destruction
sharedCounter++;
std::cout << "Counter (lock_guard) incremented by thread " << std::this_thread::get_id() << " to: " << sharedCounter << std::endl;
}
void incrementWithUniqueLock() {
std::unique_lock<std::mutex> lock(mtx); // Lock acquired upon construction
sharedCounter++;
std::cout << "Counter (unique_lock) incremented by thread " << std::this_thread::get_id() << " to: " << sharedCounter << std::endl;
lock.unlock(); // Explicitly unlock if needed
// ... perform operations without the lock ...
lock.lock(); // Re-acquire the lock
sharedCounter++;
std::cout << "Counter (unique_lock) incremented again by thread " << std::this_thread::get_id() << " to: " << sharedCounter << std::endl;
}
int main() {
std::thread t1(incrementWithLockGuard);
std::thread t2(incrementWithUniqueLock);
t1.join();
t2.join();
std::cout << "Final counter value: " << sharedCounter << std::endl;
return 0;
}
C++ provides mechanisms to perform tasks asynchronously, allowing the main thread to continue execution without waiting for the result immediately.
std::async
std::async
runs a function (or callable object) in a separate thread and returns a std::future
that can be used to retrieve the result.
std::future
A std::future
represents the result of an asynchronous operation. It provides a way to wait for the result to become available and to retrieve it (using get()
).
std::promise
A std::promise
provides a way to set a value (or an exception) that will be made available to a std::future
associated with the same promise.
#include <iostream>
#include <future>
#include <thread>
#include <chrono>
int calculateSquare(int num, std::promise<int> resultPromise) {
std::cout << "Calculating square of " << num << " in another thread." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
int result = num * num;
resultPromise.set_value(result); // Set the result for the future
return result;
}
int main() {
std::promise<int> squarePromise;
std::future<int> squareFuture = squarePromise.get_future();
std::thread calculationThread(calculateSquare, 5, std::move(squarePromise));
std::cout << "Main thread waiting for the square." << std::endl;
int square = squareFuture.get(); // Wait for and get the result from the promise
std::cout << "Square is: " << square << std::endl;
calculationThread.join();
std::future<int> asyncFuture = std::async(std::launch::async, [](int x){
std::cout << "Calculating cube of " << x << " asynchronously." << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
return x * x * x;
}, 3);
std::cout << "Main thread waiting for the cube." << std::endl;
int cube = asyncFuture.get();
std::cout << "Cube is: " << cube << std::endl;
return 0;
}
std::atomic<T>
)Atomic operations are operations that are performed indivisibly, meaning they cannot be interrupted by other threads. The std::atomic
template provides atomic versions of built-in types, allowing for thread-safe operations without the need for explicit locking in simple cases.
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
std::atomic<int> atomicCounter(0);
void incrementAtomicCounter(int iterations) {
for (int i = 0; i < iterations; ++i) {
atomicCounter++; // Atomic increment
}
}
int main() {
std::vector<std::thread> threads;
int numThreads = 4;
int iterations = 10000;
for (int i = 0; i < numThreads; ++i) {
threads.emplace_back(incrementAtomicCounter, iterations);
}
for (auto& thread : threads) {
thread.join();
}
std::cout << "Final atomic counter value: " << atomicCounter << std::endl;
std::cout << "Expected value: " << numThreads * iterations << std::endl;
return 0;
}
Understanding and utilizing these concurrency and multithreading tools in C++ is crucial for building high-performance and responsive applications.