C++ threading
4 min readFeb 25, 2021
Performance matters. Producer-Consumer example.
Basic ingredients
- <thread>
- <mutex>
- <condition_variable>
- <atomic>
- <future>
<mutex>
std::mutex
- mutex is a lockable type used to protect access to a critical section of code
- locking a mutex prevents other threads from locking it until it is unlocked
- consider std::lock_guard or std::unique_lock instead of using mutex.lock() because of exception safety. Those unlocks on destruction automatically. → these are nicer than a custom scoped mutex
std::lock_guard
- On construction, the mutex object is locked by the calling thread, and on destruction, the mutex is unlocked.
- Useful as an object with an automatic duration that lasts until the end of its context.
- Not movable. You can’t pass it around to functions. If you need to pass it around, use
std::unique_lock
std::mutex mtx;void thread1() {
std::lock_guard<std::mutex> lck(mtx);
...
}
std::unique_lock
- A unique lock is an object that manages a mutex object with unique ownership in both states: locked and unlocked.
- On construction or move assignment, the object acquires a mutex object, for whose locking and unlocking operations becomes responsible.
- Guarantees an unlocked status on destruction even it not called explicitly. Therefore it is especially useful as an object with automatic duration, as it guarantees the mutex object is properly unlocked in case an exception is thrown.
- movable, but not copiable.
std::mutex mtx;void thread1() {
std::unique_lock<std::mutex> lck(mtx);
...
}
Static initialization
atomic
shared_mutex / shared_lock
also known as “Read/Write” mutex
The arguments to the thread function are moved or copied by value. If a reference argument needs to be passed to the thread function, it has to be wrapped (e.g., with std::ref or std::cref).
writing is atomic here, but reading is mixed.
<condition_variable>
std::condition_variable
- A condition variable is an object able to block the calling thread until notified to resume.
- It uses a unique_lock (over a mutex) to lock the thread when one of its wait functions is called. The thread remains blocked until woken up by another thread that calls a notification function on the same condition_variable object.
Example
ping pong program
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
using namespace std;
using namespace std::chrono_literals;std::mutex mtx;
std::condition_variable cv;
bool f1turn = true;void f1(const string& s) {
while(true){
std::unique_lock<std::mutex> lk(mtx);
if (f1turn == false) {
cv.wait(lk);
}
for (int i=0; i< 100; i++) {
cout << s;
}
cout << endl;
std::this_thread::sleep_for(2ms);
f1turn = false;
cv.notify_all();
}
}void f2(const string& s) {
while(true){
std::unique_lock<std::mutex> lk(mtx);
if (f1turn == true) {
cv.wait(lk);
}
for (int i=0; i< 100; i++) {
cout << s;
}
cout << endl;
std::this_thread::sleep_for(2ms);
f1turn = true;
cv.notify_all();
}
}// To execute C++, please define "int main()"
int main() {
std::thread t1(f1, "a");
std::thread t2(f2, "b");
t1.join();
t2.join();
}g++ filename.cpp -pthread
once
as opposed to static initialization, this is member initialization.