Four principles of thread synchronization
- Minimum sharing of objects, reducing the need for synchronization
- Use advanced concurrency components, such as TaskQueue, producer consumer queue, CountDownLatch, etc
- When you have to use the underlying synchronization primitive, only use non recursive mutexes and conditional variables. Use read-write locks with caution and do not use semaphores
- In addition to using atomic integers, do not write your own lock free code, and do not use kernel level synchronization primitives
Mutex (mutex)
Use principle
Use RAII to package the creation, destruction, locking and unlocking of mutex
- It ensures that the effective period of the lock is equal to one scope locking, which is helpful for positioning when a deadlock occurs
- It ensures that lock and unlock are not called manually
- It ensures that locks are not called across processes and threads
- It ensures that you don't forget to unlock and repeatedly lock
- Use only non recursive mutex (i.e. non reentrant mutex)
- Each time you construct a Guard object, think about the locks already held along the way (function call stack) to prevent deadlock caused by different order of chains
Why only use non recursive mutex
- The same thread can repeatedly lock recursive mutex, but it cannot repeatedly lock non recursive mutex
- Locking non recursive mutex multiple times in the same county will lead to deadlock
- There is little difference in performance between the two
reason
- Non recursive locks can expose programming problems early
deadlock
- In addition to deadlocks between threads, a single thread can also cause deadlocks
- When ensuring the use of Scoped Locking, the reason can be obtained by analyzing the call stack
Conditional variable
BlockingQueue implemented using conditional variables
muduo::MutexLock mutex; muduo::Condition cond(mutex); std::deque<int> queue; int dequeue(){ MutexLockGuard lock(mutex); while(queue.empty()){ cond.wait(); // unlock mutex and wait } assert(!queue.empty()); int top = queue.front(); queue.pop_front(); return top; } void enqueue(int x){ { MutexLockGuard lock(mutex); queue.push_back(x); } cond.notify(); // wakeup the wait side }
- notify() is called every time you join instead of notifyall() to avoid the crowd shock effect
- Not only notify() when 0 - > 1, but every time you add a notification, it can wake up multiple consumers efficiently, otherwise only one is awakened
- See details https://book.douban.com/annot...
Countdownlatch is implemented using conditional variables
class CountDownLatch : boost::noncopyable{ public: explicit CountDownLatch(int count); void wait(); void countDown(); private: mutable MutexLock mutex_; Condition condition_; int count_; } void CountDownLatch::wait(){ MutexLockGuard lock(mutex_); whilt(count_>0){ condition_.wait(); } } void CountDownLatch::countDown(){ MutexLockGuard lock(mutex_); --count_; if(count_ == 0){ condition_.notifyAll(); } }
Encapsulate the implementation of MutexLock, MutexLockGuard and Condition
class MutexLock : boost::noncopyable{ public: MutexLock() :holder_(0) { pthread_mutex_init(&mutex_NULL); } ~MutexLock() { assert(hoilder_ == 0); pthread_mutex_destory(&mutex); } bool isLockedByThisThread(){ return holder_ == CurrentThread::tid(); } void assertLocked(){ assert(isLockedByThisThread()); } void lock(){ pthread_mutex_lock(&mutex); holder_ = CurrentThread::tid(); } void unlock(){ holder_ = 0; pthread_mutex_unlock(); } pthread_mutex_t* getPthreadMutex(){ return &mutex; } private: pthread_mutex_t mutex_; pid_t holder_; } class MutexLockGuard : boost::noncopyable{ public: explicit MutexLockGuard(MutexLock& mutex) :mutex_(mutex) { mutex_.lock(); } ~MutexLockGuard(){ mutex_.unlock; } private: MutexLock& mutex_; } #define MutexLockGuard(x) static_assert(false,"Missing mutex guard variable name!") class Condition : boost::noncopyable{ public: explicit Condition(MutexLock& mutex) :mutex_(mutex) { pthread_cond_init(&pcond_,NULL); } ~Condition(){ pthread_cond_destory(&pcond_); } void wait(){ pthread_cond_wait(&pcond_,mutex_.getPthreadMutex()); } void notify(){ pthread_cond_signal(&pcond_); } void notifyAll(){ pthread_cond_boardcast(&pcond); } private: MutexLock& mutex_; pthread_cond_t pcond_; }
Summary
- Four principles of thread synchronization: try to use advanced synchronization facilities
- For other tasks, common mutexes and conditional variables are used, and RAII technique and scoped locking are used