C++ Concurrency Basic¶
Multiprocessing and Multithreading¶
Multiprocessing happens when there are multiple processes running and they inter-process communicate.
Multithreading happens when multiple threads are running and they use shared memory.
Since creating another process takes time and more system resources, multithreading is faster to start and costs less.
But multithreading is harder to implement and it cannot be run on distributed computing.
Thread¶
void sleep(int s) {
std::this_thread::sleep_for (std::chrono::seconds(n));
std::cout << "slept for " << s << "seconds" << std::endl;
}
void func() {
}
void funcStr(string& s) {
}
struct Functor {
Functor(int v) : v(v) {}
int operator()(int x) { return v = v + x; }
private:
int v;
};
int main() {
std::thread t1(func); // thread t1 starts running
// t1.join(); // main thread (calling thread) will wait for t1 to finish
t1.detach(); // t1 is detached, executed independently
std::thread (sleep,10).detach(); // if main thread exit before 10 seconds
// will not show the print statement
//t1.join(); // trying to join a detached thread, will crash
if(t1.joinable()) { // check
t1.join();
}
/*
* thread can also be created using any callable objects
* such as functors and lambda functions
*/
Functor f1(0);
std::thread t2(f1,1); // note f1 is a "closure"
//std::thread t3(Functor()); // not work, this is a function declaration
std::thread t3((Functor()));
/*
* more efficient parameter
*/
// pass by reference
// constructors of thread take *decay copies*
string s = "";
std::thread ts(funcStr, std::ref(s)); // we can use either ref or ptr (ptr way not shown)
// but 2 threads share the same memory, potential data race
// if we don't want to pass by value and we don't need to access s in calling thread later
// we can move s to child thread
std::thread ts(funcStr, std::move(s)); // both efficient and safe
/*
* thread is not copy-able
*/
std::thread t1_move = std::move(t1); // t1 now is "empty", its thread is now owned by t1_move
/*
* thread id
*/
std::cout << std::this_thread::get_id() << std::endl();
/*
* oversubscription
* it happens when number of threads goes beyond the number supported
* by the hardware, leading to too much context switching, resulting
* in bad performance
*/
std::cout << std::thread::hardware_concurrency() << std::endl;
}
A thread can be joined or detached only once.
If a thread is neither joined nor detached before it goes out of scope, it will be destroyed (terminate() will be called) and potentially will not do what the caller expects.
Why .join is still necessary when all other thread have finished before the main thread?
void func() {
}
void demo_join() {
// we always want to join the thread here
// method 1: try catch
std::thread t1(func); // thread t1 starts running
// join right away makes no sense for multithreading
// do some work before join
try {
//...
} catch (...) { // bad catch design, for demo only
t1.join();
throw;
}
t1.join();
// method 2: RAII
// create a wrapper class whose destructor calls t1.join()
// https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization
}
int main() {
demo_join();
}