#include <thread>
#include <iostream>
#include <string>
void func()
{
std::cout << "Function executed in thread\n";
}
void f(int i, const std::string& s)
{
std::cout << "Function executed with params: " << i << ", " << s << '\n';
}
int main()
{
std::thread t(func); // Call function in a new thread
t.join(); // Main thread waits for thread t to finish
std::thread t2(f, 3, "hello"); // Start f(3, "hello") in a new thread
t2.join(); // Main thread waits for thread t2 to finish
return 0;
}
Protect shared resources from concurrent access by multiple threads. Using lock_guard
automatically manages locking and unlocking of mutexes.
#include <mutex>
#include <list>
std::mutex mtx;
std::list<int> some_list;
void add_to_list(int new_value)
{
std::lock_guard<std::mutex> guard(mtx);
some_list.push_back(new_value);
}
Always lock mutexes in the same order to avoid deadlock.
Condition variables allow synchronization of threads waiting for a specific condition to be met.
#include <condition_variable>
#include <mutex>
#include <queue>
std::queue<int> data_queue;
std::mutex mtx;
std::condition_variable cv;
void data_preparation_thread()
{
std::unique_lock<std::mutex> lk(mtx);
data_queue.push(42);
cv.notify_one();
}
void data_processing_thread()
{
std::unique_lock<std::mutex> lk(mtx);
cv.wait(lk, []{ return !data_queue.empty(); }); // Thread waits for condition
int data = data_queue.front();
data_queue.pop();
}
std::async
and std::future
std::async
allows functions to be executed asynchronously, returning a std::future
object that can hold the function's result.
#include <future>
#include <iostream>
int find_the_answer()
{
return 42;
}
int main()
{
std::future<int> future_result = std::async(find_the_answer);
int result = future_result.get(); // Wait for the result
std::cout << "Result: " << result << '\n';
return 0;
}
Atomic types: std::atomic<int>
ensures that operations on variables are atomic (indivisible).
std::atomic_flag
: Used for simple synchronization mechanisms like spinlock.
#include <atomic>
#include <iostream>
std::atomic<int> counter;
void increment_counter()
{
counter.fetch_add(1); // Safe increment operation
}
int main()
{
counter = 0;
increment_counter();
std::cout << "Counter: " << counter.load() << '\n';
return 0;
}
Define a maximum wait time in synchronization functions.
#include <future>
#include <iostream>
#include <chrono>
int main()
{
std::future<int> f = std::async([]{ return 42; });
if(f.wait_for(std::chrono::milliseconds(100)) == std::future_status::ready) {
std::cout << "Result: " << f.get() << '\n';
} else {
std::cout << "Timeout\n";
}
return 0;
}
chrono
- Time OperationsThe <chrono>
header allows precise time-related operations.
#include <chrono>
#include <iostream>
void do_something()
{
for (volatile int i = 0; i < 100000000; ++i);
}
int main()
{
auto start = std::chrono::high_resolution_clock::now();
do_something();
auto stop = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> duration = stop - start;
std::cout << "Duration: " << duration.count() << " seconds\n";
return 0;
}
A model for communication between threads without sharing data — each thread acts independently, and communication occurs through message passing.
Different memory models and ordering of atomic operations:
memory_order_relaxed
: No additional constraints.memory_order_acquire
, memory_order_release
: Operations related to memory access synchronization.memory_order_seq_cst
: Global order of operations, most restrictive.Memory Fences: Enforce a specific order of operations.
std::atomic_thread_fence(std::memory_order_release);
#include <atomic>
class spinlock_mutex {
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
while(flag.test_and_set(std::memory_order_acquire));
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
std::promise
#include <future>
#include <iostream>
void set_promise_value(std::promise<int>& p)
{
p.set_value(42);
}
int main()
{
std::promise<int> promise;
std::future<int> future = promise.get_future();
std::thread t(set_promise_value, std::ref(promise));
t.join();
std::cout << "Result: " << future.get() << '\n';
return 0;
}
std::atomic_flag
#include <atomic>
#include <iostream>
std::atomic_flag lock_flag = ATOMIC_FLAG_INIT;
void spinlock_lock(std::atomic_flag& lock)
{
while(lock.test_and_set(std::memory_order_acquire));
}
void spinlock_unlock(std::atomic_flag& lock)
{
lock.clear(std::memory_order_release);
}
int main()
{
spinlock_lock(lock_flag);
std::cout << "Critical section\n";
spinlock_unlock(lock_flag);
return 0;
}
std::condition_variable
#include <condition_variable>
#include <iostream>
#include <thread>
#include <queue>
std::condition_variable cv;
std::mutex cv_m;
std::queue<int> data_queue;
void data_preparation_thread()
{
for (int i = 0; i < 5; ++i)
{
std::this_thread::sleep_for(std::chrono::milliseconds(100));
std::lock_guard<std::mutex> lk(cv_m);
data_queue.push(i);
cv.notify_one();
}
}
void data_processing_thread()
{
while (true)
{
std::unique_lock<std::mutex> lk(cv_m);
cv.wait(lk, []{ return !data_queue.empty(); });
int data = data_queue.front();
data_queue.pop();
lk.unlock();
std::cout << "Processed data: " << data << '\n';
if (data == 4) break; // Exit condition
}
}
int main()
{
std::thread t1(data_preparation_thread);
std::thread t2(data_processing_thread);
t1.join();
t2.join();
return 0;
}
Multithreading in C++ allows for concurrent execution of tasks, improving performance and responsiveness of applications. Understanding synchronization mechanisms such as mutexes, condition variables, atomic operations, and using tools like std::async
and std::future
is crucial for writing safe and efficient multithreaded code.