1. Concept
Scoped locking is not a type of lock, but a pattern of lock use. The term is Douglas C. Schmidt in his paper in 1998 Scoped Locking Proposed and used in ACE framework. However, as a design idea, this lock mode should have been widely used in the industry earlier.
Area locks are actually RAII Specific application of mode on lock. RAII(Resource Acquisition Is Initialization) is translated into Chinese as "resource acquisition is initialization", which was first invented by the inventor of C + + Bjarne Stroustrup It is proposed to solve the problem of resource allocation and destruction in C + +. The basic meaning of RAII is that the resources (such as memory, file handle, etc.) in C + + should be managed by the object. The resources are initialized in the constructor of the object and released in the destructor of the object. The smart pointer in STL is a specific application of RAII. RAII is so widely used in C + +, it can even be said that a tailor who can't RAII is not a good programmer.
2. Problems
First look at the following program. Cache is a cache class that may be accessed by multiple threads. The update function inserts the string value into the cache. If the insertion fails, it returns - 1.
Cache *cache = new Cache; ThreadMutex mutex; int update(string value) { mutex.lock(); if (cache == NULL) { mutex.unlock(); return -1; } If (cache.insert(value) == -1) { mutex.unlock(); return -1; } mutex.unlock(); return 0; }
From this program, we can see that in order to ensure that the program will not deadlock, every time the function needs to return, we need to call the unlock function to release the lock. Not only that, suppose cache When an exception is thrown inside the insert (value) function, the program will exit automatically, and whether the lock can still be released. In fact, not only return, goto, continue, break statements and unhandled exceptions in the program require the programmer to check whether the lock needs to be released. Such a program is very error prone.
3. Realization
However, since C + + has a lovely RAII design idea, the problem of resource release is much simpler. Area lock is to encapsulate the lock into an object. Lock initialization is placed in the constructor and lock release is placed in the destructor. In this way, when the lock leaves the scope, the destructor will automatically release the lock. Even if an exception is thrown at runtime, the lock can still be released automatically because the destructor will still run automatically. A typical area lock:
class Thread_Mutex_Guard { public: Thread_Mutex_Guard (Thread_Mutex &lock) : lock_ (&lock) { // If locking fails, - 1 is returned owner_= lock_->lock(); } ~Thread_Mutex_Guard (void) { // If the lock acquisition fails, it will not be released if (owner_ != -1) lock_->unlock (); } private: Thread_Mutex *lock_; int owner_; };
Apply the policy lock to the previous update function as follows:
Cache *cache = new Cache; ThreadMutex mutex; int update(string value) { Thread_Mutex_Guard (mutex) if (cache == NULL) { return -1; } If (cache.insert(value) == -1) { return -1; } return 0; }
The basic area lock is that simple. If you think the strength of the lock is too large, you can use brackets to limit the action area of the lock, so that you can control the strength of the lock. As follows:
{ Thread_Mutex_Guard guard (&lock); ............... // When you leave the scope, the lock is automatically released }
4. Improvement
One disadvantage of the area lock designed above is its poor flexibility. It cannot explicitly release the lock unless it leaves the scope. If an explicit release interface is added to an area lock, one of the most prominent problems is that it may cause the secondary release of the lock, resulting in program errors.
{ Thread_Mutex_Guard guard (&lock); If (...) { //Explicit release (first release) guard.release(); // Automatic release (second release) return -1; } }
In order to avoid the error caused by releasing the lock twice, the area lock needs to ensure that the lock can be released only once. An improved area lock is as follows:
class Thread_Mutex_Guard { public: Thread_Mutex_Guard (Thread_Mutex &lock) : lock_ (&lock) { acquire(); } int acquire() { // Locking failed, return - 1 owner_= lock_->lock(); return owner; } ~Thread_Mutex_Guard (void) { release(); } int release() { // First release if (owner_ != -1) { owner = -1; return lock_->unlock (); } // Second release return 0; } private: Thread_Mutex *lock_; int owner_; };
It can be seen that this scheme will not cause program errors in the case of lock failure or multiple release of locks.
Disadvantages:
Area lock is easy to use, but it also has some inevitable disadvantages
- about Non recursive lock , deadlock may be caused by repeated locking.
- The forced termination or exit of the thread will cause the area lock not to be released automatically. Try to avoid this situation, or use some special error handling design to ensure that the lock will be released.
- The compiler generates a warning that variables are defined but not used. Some compiler options even prevent programs with warnings from compiling. In ACE, in order to avoid this situation, the author defines a macro as follows #define unused_ ARG(arg) { if (&arg) ; } Use the following:
Thread_Mutex_Guard guard (lock_); UNUSED_ARG (guard);