Concurrent models

Thread usage

void func1() { … }

int main() {
  std::thread t1(func1);
  // t1.join(); // main thread waits for t1 to finish
  t1.detach() // t1 runs freely -> daemon process => C++ lib is responsible to reclaim resources
  // once detach, cannot join again!
  // t1.joinable() -> check if t1 is joinable
}

std::thread can also be constructed with any callable object, for example functor:

class Functor {
public:
  void operator(string &msg)() {
    // …
  }
};

int main() {
  std::string s = “hello”;
  
  // need extra parentheses to avoid “Functor()”
  // being interpreted as function call
  // std::thread t1((Functor())); // if operator() doesn’t take any param
  
  // s is being passed by value because param to threads
  // are always pass by value!!
  // std::thread t1((Functor()), s); => copying!
  
  // need to use ref wrapper to pass by ref
  std::thread t1((Functor()), std::ref(s));
  
  t1.join();
}

std::thread is a move-only object, cannot be copied!

Oversubscription: creating more threads than CPU can support!

std::thread::hardware_concurrency() returns the number of concurrent thread supported.

std::thread memory for each thread is managed by POSIX native thread library…

Jthread

same behavior with std::thread but auto join when destroyed

Race condition

Race condition is when the result of a program depends on the order of execution of threads.

Mutex

std::mutex mu;

// unsafe usage:
// mu.lock();
// // do something => but if exception raises here, mutex is locked forever!
// mu.unlock();

// solution: use lock guard
{ // put within scope, mutex unlocked whenever lock guard is out of scope
std::lock_guard<std::mutex> guard(mu); // RAII
// do something
}

Implementation

std::mutex implements 2 phases: