Skip to main content

Concurrency

Threads

Include

#include <thread>

Run Function

void run() {
printf("Hello World!");
}

std::thread t(run);

t.join();

Run Class

class Thread {
public:
void operator()() {
printf("Hello World!");
}
}

Thread thread;
std::thread t(thread);

Run Lambda

std::thread([]{
printf("Hello World!");
});
std::this_thread::get_id();

Join

Synchronization point between caller and called thread.

thread.joinable(); // true
thread.join();
thread.joinable(); // false

Detach

thread.detach();

Sleep

std::this_thread::sleep_for(std::chrono::milliseconds(5000));

Yield

Give up current time slice and allow other threads to run.

std::this_thread::yield();

Get Number of Cores

std::thread::hardware_concurrency();

RAII Thread

Resource acquisition is initialization (Constructor acquire resources destructor releases resources)

class RAIIThread() {
public:
explicit RAIIThread(std::thread &t : t_(t)){}
~RAIIThread() {
if (t.joinable()) {
t.join();
}
}
RAIIThread(RAIIThread &const) = delete; // Remove copy constructor
RAIIThread &operator= (RAIIThread& const) = delete; // Remove copy assignment operator
private:
std::thread &t_;
}

Pass Parameters

  • By Value
void run(int x, int y) {

}
int p = 1;
int q = 2;
std::thread(run, p, q);
  • By Reference
void run(int &x) {

}
int q = 2;
std::thread(run, std::ref(p));

Locks

Mutex

#include <mutex>

std::mutex m;

m.lock();
// access resource
m.unlock();

Lock Guard

void run() {
std::lock_guard<std::mutex> lg(m);
// access resource
}

Unique Lock

Like lock guard but doesnt have to lock in the constructor.

void run() {
std::unique_lock<std::mutex> lock1(m1, std::defer_lock);
std::unique_lock<std::mutex> lock2(m2, std::defer_lock);
std::lock(lock1, lock2); // aquire both or wait
// access resource
}

Thread-Safe Stack

template<typename T>
ConcurrentStack {
public:
void push(T element) {
std::lock_guard<std::mutex> lg(mutex_);
stack_.push(std::make_shared<T>(element));
}
std::shared_ptr<T> pop() {
std::lock_guard<std::mutex> lg(mutex_);
if (stack_.empty()) {
throw std::runtime_error("Stack is empty");
}
std::shared_ptr<T> result(stack_.top());
stack_.pop();
return result;
}
bool empty() {
std::lock_guard<std::mutex> lg(mutex_);
return stack_.empty();
}
size_t size() {
std::lock_guard<std::mutex> lg(mutex_);
return stack_.size();
}
private:
std::stack<std::shared_ptr<T>> stack_;
std::mutex mutex_;
}

Condition Variables

Lets the thread wake up when an event happens. If a thread determined that an event is satisfied it can notify one or more threads who are waiting on this condition.

#include <conditional_variable>

int total_distance = 10;
int distance_covered = 0;
std::conditional_variable cv;
std::mutex m;

void keep_moving() {
while (true) {
std::this_thread::sleep_for(std::chrono::milliseconds(1000));
distance_covered++;
if (distance_covered == total_distance) {
cv.notify_one();
}
}
}

void wait_to_arrive() {
std::unique_lock<std::mutex> ul(m);
cv.wait(ul, []{ distance_covered == total_distance; });
// event occured
}

Futures

#include <future>

int find_answer() {
return 5000;
}

std::future<int> the_answer = std::async(std::launch::async, find_answer)
the_answer.get();

Run policies:

  • std::launch::async run on separate thread
  • std::launch::deferred run on current thread
  • std::launch::async | std::launch::deferred compiler can decide where to run