C++_Multithreading_thread_mutex - RicoJia/notes GitHub Wiki
========================================================================
========================================================================
-
race condition means: the outcome of a variable in a thread depends on the ordering of two threads.
- Difficult to debug cuz they can totally disappear.
- Will cause undefined behavior
-
One potential problem is: passing a pointer to mutex-protected variables out to caller functions
- Problematic cuz other functions can modify them later without mutexes
-
Mutex and lock free methods. There's also "transactions" to modify data, just like a transaction in data base, which will restart if a trasaction is failed. ========================================================================
========================================================================
-
Basics: create a thread, put a callable in there -> the thread is "joinable" -> join the thread if you want to wait for it to finish, or detach it if you want it to run in the background
std::thread t; std::thread t2 {func, arg}; //a new "thread of execution" t = std::move(t2);
-
ways to create thread
// 1. functor class Functor{public: operator(){}}; std::thread th1((Functor())); //pay attention to the () here std::thread th2(Func()); // ERROR: This is most-vexing parse: std::thread th3{Func()}; // Works :)
// 2. function void func1(){} std::thread th2(func2); obj std::thread(&Class::func, &Obj) // Note: needs pointer, not smart pointer
// lambda expression std:;thread obj([]{cout<<"haha"; });
//Create a thread, running another member object's function:
ws_th_ = std::thread (&util::Webserver::start, &(ws_));
```
- join()
-
clears up any storage allocated to the thread
-
can only be joined once, so this is important::
if(th.joinable())
std::thread th1(func); std::thread th2 = std::move(th1); std::thread th3; std::thread th4(func); th1.join(); // bad: here no thread is associated with the thread object, so join() again will cause the program to terminate. th2.join(); // this is joinable th3.join(); // not joinable // You haven't join th4 yet. If you want to destroy an obj, it has to be joinable!!
-
Check joinability
//good: check if the thread is joinable; if (th1.joinable()) th1.join()
-
If you don't call thread.join() on an obj, thread's destructor will be called at return. And An error for std::terminate will be called
-
- detach()
-
after detach, the thread object is not joinable any more.
-
Daemon/Background threads: these thread objects are no longer associated with their objects.
if (th.joinable()) //same thing with the daemon thread. th.detach();
-
Be very careful with detach: child function may live longer than the main function
void func(){ std::string s{"C++11"}; std::thread t([&s]{ std::cout << s << std::endl;}); // undefined behavior t.detach(); } int main(){ func(); }
-
One app is logger, or you sure something will be done very fast.
- Or a file editor, which starts a thread for a window. the window can terminate on its own. ========================================================================
-
========================================================================
- when referencing data from the outside: smart pointer or ref: make sure the object is stored as a class variable, or at least persists beyond the lifetime of the thread
- std::thread will copy a variable, cuz its ctor has the same mechanism w
std::bind
std::thread(some_func, std::ref(data))
1. pass in argument by reference:
```cpp
func(int& v);
int v = 1;
std::thread t1(func, std::ref(v));
```
-
std::thread
might have no functions to execute
-
So it's an null handle
-
Could be: - default-constructed - Moved thread - joined threads - detached threads
-
Reasons why STL does not implicitly make t join(), or detach()
- we can't join() the thread unconditionally, because the thread may be in an infinitely that never completes
-
This happens when you "double-joining" a thread
```cpp terminate called after throwing an instance of 'std::system_error' what(): Invalid argument ```
-
Cannot copy std::thread, movable only
std::thread ret_th(){ std::thread t3(func); return t3; // func may/may not be done. } int main(){ // Case 1 std::thread t1(func); std::thread t2 = std::move(t1); // func is done, won't see func executed again. t2.join(); //t1 is not joinable //case 2 return a thread auto t3 = ret_th(); t3.join(); //case 4 asssigning a thread to a joinable thread -> termination t1 = std::thread(func); t1 = ret_th(); // error: termination! t1.join(); return 0; }
- Error "terminate called without an active exception" 1. Happens when thread is terminated while still in a joinable state 2. A thread is not joinable when: 3. remember, when a thread is done executing, it's in a JOINABLE state!
-
Be careful with exceptions, conditions that keep the thread still joinable
int main(){ std::thread t1([]{ do_work(); }); if (Some_Condition) t.join(); // if false, t will still be joinable, that will lead to std::terminate }
-
overhead of creating std::threads? - system dependent. So use thread_pool if you can.
-
throw error
resource temporarily not available
?? Must be you're trying to create too many threads!! -
dangerous scenario: after detach, local variable is no more valid
void oops(int some_param) { char buffer[1024]; sprintf(buffer, "%i",some_param); std::thread t(f,3,buffer); //you first have to convert char const* of char[] into std::string, which might finish after the function goes out of scope (undefined behavior) t.detach(); }
-
unjoinable_thread.join()
returns errorinvalid_argument if joinable() is false
========================================================================
========================================================================
-
Solution to not joining a thread at the end of the current function scope: RAII
class RAII_THREAD{ public: RAII_THREAD(int dummy_param, std::thread&& th)th_(std::move(th)){} // Notice that dummy_param comes before th. That's because th might start immediately after they're initialized, and it is dependent on other params!! We want to make sure the other params are already there. ~RAII_THREAD(){if(th.joinable()) th_.join(); } std::thread& get(){return th_;} //analogous to smart pointer RAII_THREAD(RAII_THREAD&&) = default; // rvlaue ref, yes go have it. copy ctor and assignment don't make sense. RAII_THREAD& operator=(RAII_THREAD&&) = default; private: std::thread th_; };
- through get, two other threads might be able to get access to thread and do join(), which might be a race
- Simultaneous calls are usually safe only with const member functions
- The key is to make sure we join the thread when function goes out of scope
-
using RAII, so t1 will be automatically joined
void detect(){ ThreadRAII tr( []{ p.get_future().wait(); //by default, this is std::future, not std::shared_future react(); }); ... // Here you may get an exceptions p.set_value(); }
-
Cautions:
- For threadRAII, You might see multiple destructor msgs, if you're in a dynamically allocated container.
========================================================================
========================================================================
- There's hardware & software threads:
- hardware threads: usually more than one per core.
- Software threads: operating system manages this.
- Possible to have more software threads than hardware threads
- schedulers will schedule. When one thread is blocked, another unblocked thread will be executed.
-
std::thread
is Cpp API for accessing lower level software threads (pthreads, Windows' threads)
-
int i = std::thread::hardware_concurrency();
returns the max available threads.- maybe num of cores on a multi-core machines. And this will be 0 if the info is not available.
-
shared thread_local storage: remember only a static member function / global function can be passed into C as a callback? You can store some thread_local data there:
std::map<int, double> storage; ... storage.at(std::this_thread::get_id()) = ...
-
Make different thread do different work
// thread 1: std::thread::id master_thread_id = std::this_thread::get_id(); // All functions: void func(){ if (std::this_thread::get_thread_id() == master_thread_id){ do_master_work(); } else{ do_common_work(); } }
- returns the handle to the thread, defined by the compiler, working with pthread functions.
========================================================================
========================================================================
-
do
std::lock_guard<T>
calls.lock()
and.unlock()
methods from amutex
. It was designed for mutex. -
scoped_lock
: lock two mutexes in a deadlock-free wayclass Employee{ public: std::mutex mtx_; int id; }; int main(){ Employee e1, e2; std::scoped_lock lock(e1.mtx_, e2.mtx_); cout<<e1.id<<e2.id; }
- deadlock avoidance algorithm is automatically given!
- non-copyable, RAII
-
spinlock vs mutex: mutex will have thread scheduling, when going out of a quantum, other threads will execute on the core. spin lock: always waits in a loop, good when wait time is shorter than the quantum
-
std::timed_mutex
andstd::timed_recursive_mutex
supportstry_lock_for()
andtry_lock_until()
- similarly,
std::unique_lock
has these:-
std::unique_lock::try_lock_for(duration)
,std::try_lock_until(time_point)
-
std::unique_lock(TimeLockable, duration)
,std::unique_lock(TimeLockable, time_point)
-
- similarly,
-
shared_mutex:
-
std::shared_lock
calls lock_shared, try_lock_shared.shared_mutex
has these too. They also havelock
,try_lock
std::shared_timed_mutex m; std::shared_lock<std::shared_timed_mutex> slk(m);
-
std::shared_timed_mutex
: havetry_lock_for()
-