Concurrency
Multithreading and synchronization in modern C++
Thread Basics
#include <thread>
#include <future>
// Creating threads
std::thread t1([]() {
std::cout << "Hello from thread" << std::endl;
});
t1.join(); // Wait for completion
// Thread with arguments
void worker(int id, const std::string& name);
std::thread t2(worker, 42, "Worker");
t2.join();
// Async - simpler thread management
auto future = std::async(std::launch::async, []() {
return compute_something();
});
int result = future.get(); // Blocks until ready
// Thread pool with jthread (C++20)
std::vector<std::jthread> workers;
for (int i = 0; i < 4; ++i) {
workers.emplace_back([i]() {
// Work on task i
});
}
// jthread automatically joins on destructionSynchronization
#include <mutex>
#include <shared_mutex>
std::mutex mtx;
int shared_data = 0;
// Basic locking
{
std::lock_guard<std::mutex> lock(mtx);
++shared_data;
} // Automatically unlocks
// Unique lock - more flexible
{
std::unique_lock<std::mutex> lock(mtx);
// Can unlock early
lock.unlock();
// Do something without lock...
lock.lock();
}
// Shared mutex - multiple readers, one writer
std::shared_mutex rw_mtx;
// Readers
std::shared_lock<std::shared_mutex> read_lock(rw_mtx);
// Multiple threads can hold shared_lock simultaneously
// Writer
std::unique_lock<std::shared_mutex> write_lock(rw_mtx);
// Exclusive accessCondition Variables
#include <condition_variable>
std::mutex mtx;
std::condition_variable cv;
std::queue<int> queue;
bool done = false;
// Producer
void producer() {
for (int i = 0; i < 10; ++i) {
{
std::lock_guard<std::mutex> lock(mtx);
queue.push(i);
}
cv.notify_one(); // Notify consumer
}
{
std::lock_guard<std::mutex> lock(mtx);
done = true;
}
cv.notify_all();
}
// Consumer
void consumer() {
while (true) {
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, []() {
return !queue.empty() || done;
});
while (!queue.empty()) {
int value = queue.front();
queue.pop();
lock.unlock();
process(value);
lock.lock();
}
if (done && queue.empty()) break;
}
}Atomic Operations
#include <atomic>
std::atomic<int> counter{0};
// Lock-free increment
counter.fetch_add(1); // ++counter
counter.fetch_sub(1); // --counter
// Compare and swap (CAS)
int expected = 0;
bool success = counter.compare_exchange_strong(
expected, 1
);
// If counter == expected, sets counter to 1 and returns true
// Otherwise, sets expected to counter value and returns false
// Memory ordering
std::atomic<int> flag{0};
int data = 0;
// Thread 1
data = 42;
flag.store(1, std::memory_order_release);
// Thread 2
if (flag.load(std::memory_order_acquire) == 1) {
// Guaranteed to see data == 42
}