Sharing data between threads
Modify data
invariant, definition can be found here.
threads modifying data may break invariant(see the example of changing the doubly linked list)
problematic race condition: typically occur where completing an operation requires modification of two or more distinct pieces of data
data race(will be introduced in the future)
Solutions:
- only the thread performing a modification can see the intermediate states where the invariants are broken. (mutex)
- Change the data structure, makes it an indivisible change(lock-free programming).
- Handle the update as a transaction(like database)
Protecting shared data with mutexes
access the data structure as mutually exclusive — use mutex
The mutex has its own problems: deadlock, protecting too much or too little data.
Besides, pointers may ruin the data protection. Programmers should follow: Don’t pass pointers and references to protected data outside the scope of the lock, whether by returning them from a function, storing them in externally visible memory, or passing them as arguments to user-supplied functions.
Example: a stack shared by multiple threads, empty() and top()
TODO: some options to avoid race conditions
thread-safe stack: see listing 3.5, but need to watch out:
- delete some operator/functions
- add a mutable class variable mutex
- lock_guard in every operator function
std::lock—a function that can lock two or more mutexes at once without risk of deadlock
hierarchical lock
std::unique_lock, more flexible than lock_guard(eg. try_to_lock), automatically unlock(will judge by itself)
std::adopt_lock vs std::defer_lock
std::unique_lock contains a flag to indicate the ownership of the mutex, which increases the cost of this class.
std::call_once and std::once_flag to make sure initialization is done once
std::shared_timed_mutex(C++14), std::shared_mutex(C++17)