Threads are created using std::thread
template< class F, class... Args >
explicit thread( F&& f, Args&&... args );
The constructor's first argument may be any of the following
operator())
After f it takes a variadic number of arguments to
f
std::thread::join()std::this_thread::sleep_for(const std::chrono::time_point& sleep_time) #include <iostream>
#include <string>
#include <thread>
using std::chrono::operator""ms; // allows time point literals in `ms`
void function(std::string name); // you may use any valid function name
int main() {
std::thread first(function, "first"); // create and start thread
std::thread second(function, "second"); // create and start thread
std::cout << "Message from main()\n";
first.join(); // wait for `first` to finish (mandatory in C++11)
second.join(); // wait for `second` to finish (mandatory in C++11)
std::cout << "Main thread: all worker (background) threads completed\n";
return 0; // without `join()`, the program would crash when main(.) ends
}
void function(std::string name) {
for (int i = 0; i < 10; ++i) {
std::cout << "Message from " << name << " thread\n";
std::this_thread::sleep_for(200ms);
}
}
Download cpp11thread.cpp
C++20 offers std::jthread which is preferable to
std::thread
std::jthread has the same general behavior as
std::thread
Key advantage: std::jthread
automatically joins
on destruction
template< class F, class... Args >
explicit jthread( F&& f, Args&&... args );
std::jthread is safe if an exception occurs before
join()
#include <iostream>
#include <string>
#include <thread>
using std::chrono::operator""ms; // allows time point literals in `ms`
void function(std::string name, int count);
int main() {
std::jthread first(function, "first", 5); // create and start jthread
std::jthread second(function, "second", 10); // create and start jthread
std::cout << "Message from main()\n";
first.join(); // not mandatory for C++20 jthread, but still allowed
std::cout << "Main thread: first background thread completed\n";
return 0; // manual call to `join()` is not required
}
void function(std::string name, int count) {
for (int i = 0; i < count; ++i) {
std::cout << "Message from " << name << " thread\n";
std::this_thread::sleep_for(200ms);
}
}
Download cpp20thread.cpp
#include <cstdint>
#include <iostream>
#include <thread>
#include <vector>
using std::chrono::operator""ms;
using std::cout, std::endl, std::jthread, std::vector;
class Account {
public:
uint64_t balance() const { return balance_; }
void deposit(uint64_t amount) { balance_ += amount; }
void withdraw(uint64_t amount) { balance_ -= amount; }
private:
uint64_t balance_{0};
};
int main() {
Account account{};
vector<jthread> threads;
for (int i = 0; i < 10; ++i) { // 10 jthreads
threads.push_back(jthread([&]() {
for (int j{0}; j < 100; ++j) { // deposit 100 times 1
account.deposit(1);
std::this_thread::sleep_for(1ms);
}
for (int j{0}; j < 100; ++j) { // withdraw 100 times 1
account.withdraw(1);
}
}));
}
for (auto& th : threads) {
th.join();
}
cout << "balance: " << account.balance() << endl;
}
Download race_condition.cpp
std::atomic)In C++, operations like var++ result in three separate
steps for the CPU:
var from memory into a registerIf a second thread interrupts between these steps, data may get corrupted
Atomic operations combine all three steps to a single, indivisible unit
Based on CPU instructions
Atomics allow thread-safe code without ever "locking" anything
It is not needed to put threads to sleep (no performance penalty)
std::atomic
supports the following operations at the hardware level:
++ and --+=, -=,
&=, |=, ^=
std::atomic::compare_exchange_weak(T& expected, T desired)
,
std::atomic::compare_exchange_strong(T& expected, T desired)
#include <atomic>
#include <cstdint>
#include <iostream>
#include <thread>
#include <vector>
using std::chrono::operator""ms;
using std::cout, std::endl, std::jthread, std::vector;
class Account {
public:
uint64_t balance() const { return balance_; }
void deposit(uint64_t amount) { balance_ += amount; }
void withdraw(uint64_t amount) { balance_ -= amount; }
private:
std::atomic<uint64_t> balance_{0};
};
int main() {
Account account{};
vector<jthread> threads;
for (int i = 0; i < 10; ++i) { // 10 jthreads
threads.push_back(jthread([&]() {
for (int j{0}; j < 100; ++j) { // deposit 100 times 1
account.deposit(1);
std::this_thread::sleep_for(1ms);
}
for (int j{0}; j < 100; ++j) { // withdraw 100 times 1
account.withdraw(1);
}
}));
}
for (auto& th : threads) {
th.join();
}
cout << "balance: " << account.balance() << endl;
}
Download atomic_ops.cpp
std::mutexA lock or mutex (from mutual exclusion) is a synchronization primitive
It prevents state from being modified or accessed by multiple threads at once
If one thread has the lock, all other threads must wait
If a thread hits a locked mutex, the OS puts that thread to sleep
This context switch is expensive in terms of CPU cycles
Beware of deadlocks when using multiple mutexe objects
#include <mutex> // provides std::mutex and std::lock_guard
std::mutex m; // construct the mutex in unlocked state
// ...
std::lock_guard<std::mutex> lock; // acquires ownership of m (calls m.lock())
// critical code
lock's destructor releases ownership of m
(calls m.unlock())
#include <iostream>
#include <mutex> // contains std::mutex and std::lock_guard
#include <thread>
#include <vector>
using std::chrono::operator""ms;
using std::cout, std::endl, std::jthread, std::vector;
class Account {
public:
uint64_t balance() const {
std::lock_guard<std::mutex> lock(m_);
return balance_;
}
void deposit(uint64_t amount) {
std::lock_guard<std::mutex> lock(m_);
balance_ += amount;
}
void withdraw(uint64_t amount) {
std::lock_guard<std::mutex> lock(m_);
balance_ -= amount;
}
private:
mutable std::mutex m_; // 'mutable' allows locking in const functions
uint64_t balance_{0};
};
int main() {
Account account{};
vector<jthread> threads;
for (int i = 0; i < 10; ++i) { // 10 jthreads
threads.push_back(jthread([&]() {
for (int j{0}; j < 100; ++j) { // deposit 100 times 1
account.deposit(1);
std::this_thread::sleep_for(1ms);
}
for (int j{0}; j < 100; ++j) { // withdraw 100 times 1
account.withdraw(1);
}
}));
}
for (auto& th : threads) {
th.join();
}
cout << "balance: " << account.balance() << endl;
}
Download mutex_lock.cpp