preface
The introduction of asynchrony and multithreading in development to improve program performance meant that thread-safety was a barrier to multithreading, resulting in thread locks, which, if not used well, posed a risk of deadlocks
The following is an introduction to several locks commonly used in ios, and the implementation of read and write locks
Case demo
Common multithreaded locks
Common locks in ios include OSSpinLock, Semaphore, pthread_mutex, NSLock, NSCondition, NSConditionLock, pthread_mutex(Recursive), and NSRecurs IveLock, synchronized
Here is a case diagram of the previous generation testing lock performance (it may be slightly off) :
Because OSSpinLock is no longer safe, here to give up the introduction, the case also put him to delete 😂
When we choose locks, if we only use the effect of mutex, then we can choose the first one according to the performance. If we need some other features of locks, then we need to choose according to the need, not too limited to the performance, after all, the implementation of features and project maintenance is also very important
The use of other locks is shown below
Semaphores (semaphore)
The semaphore implementation of locking is slightly different from the others in that it uses a signal value to decide whether to block the current thread
Wait reduces the signal value by 1, signal increases the signal value by 1
When the wait operation causes the semaphore value to be less than 0, the thread is blocked, blocked and dormant. When the signal operation causes the semaphore value to be increased, the blocked thread will be awakened in order, so that the lock function can be implemented.
- (void)semaphore {
_semaphore = dispatch_semaphore_create(1);
}
// The wait operation reduces the signal value by 1, and the signal operation increases the signal value by 1
// When the semaphore value is less than 0, the thread is blocked and hibernated. When the semaphore value is increased with the signal, the blocked thread is awakened in order
- (void)semaphoreUpdate {
// Wait can be interpreted as a lock operation. A value less than 0 will hibernate the thread in which the wait is running
// The second parameter is forever. You can set a timeout period. When the waiting time is reached, the unlock will be automatically unlockeddispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER); The code between wait and SingNAL is thread-safe _money++;// Signal can be unlocked
dispatch_semaphore_signal(_semaphore);
}
Copy the code
Pthread mutex
#import
Pthread_mutex_trylock is an attempt to lock, and if it is not locked, it succeeds and returns 0. This applies to low-priority, intermittent calls
Note: Other partial locks also have tryLock functionality, such as NSLock, NSRecursiveLock, NSConditionLock
#pragma mark --pthread mutex - (void)pthreadMutex {
pthread_mutex_init(&_pMutexLock, NULL);
// Destroy it in an appropriate place after use, such as dealloc
// pthread_mutex_destroy(&_pMutexLock);
}
- (void)pthreadMutexUpdate {
// Lock code interval operations to avoid multi-threaded access
pthread_mutex_lock(&_pMutexLock);
_money++;
// Unlock the code interval operation
pthread_mutex_unlock(&_pMutexLock);
}
- (void)pthreadMutexSub {
// Reduce the value
[NSThread detachNewThreadWithBlock:^{
// If the number is greater than 100, it starts to decrease
while (self->_money > 10000) {
// Try to lock, if it can lock, then lock, return zero, otherwise return a non-zero number
// If the lock fails, the hibernation is executed to avoid resource snatching. The priority of this task is decreased indirectly
// Some other locks also have this function, such as NSLock, NSRecursiveLock, NSConditionLock
if (pthread_mutex_trylock(&self->_pMutexLock) == 0) {
self->_money--;
/ / unlock
pthread_mutex_unlock(&self->_pMutexLock);
}else {
[NSThread sleepForTimeInterval:1]; }}}]; }Copy the code
NSLock mutex
NSLock follows the NSLocking protocol and is one of the common mutex locks. It is an API in the OC framework and is easy to use. It is said to be a lock encapsulated by pThread
The tryLock method also attempts to lock and returns true on success or false on failure
LockBeforeDate :(NSDate *)limit locks before a time. This means that the lock is automatically unlocked when the lock date is up to a specified time.
#pragma mark --NSLock mutex - (void)NSLock {
_lock = [[NSLock alloc] init];
}
- (void)NSLockUpdate {
// Lock the code interval to avoid simultaneous access by multiple threads
[_lock lock];
_money++;
// Unlock the code interval
[_lock unlock];
}
Copy the code
NSCondition lock
NSCondition is a slightly heavier lock, which I understand to be a situational lock (another reason to distinguish NSConditionLock). It is suitable for some special scenarios and also follows the NSLocking protocol, which is also a mutex
In addition, we have added the “wait” and “signal” functions, which are used in the same way as semaphore, to control the blocking and releasing of threads. In addition, we have added the “broadcast” function, which can release all threads that are blocked due to wait
As shown in the following, NSCondition is used to implement a producer and consumer case (producers and consumers are the same people, so it needs to be implemented with a lock, and in order to ensure that the money immediately buy what they want to buy, using semaphore to ensure that the money is not blocked and waiting, and the money is immediately released to buy buy buy).
This is equivalent to using both NSLock and Semaphore functionality
#pragma mark -- NSCondition implements the NSLocking protocol and supports the default mutex lock, unlock - (void)NSCondition {
_condition = [[NSCondition alloc] init];
}
// Scenario locking also adds semaphore mechanisms such as wait and Signal to enable the production of consumer patterns
// Producer: As mom and Dad earn a day's money, their savings increase
- (void)conditionPlusMoney {
[_condition lock];
// The semaphore has been increased
if (_money++ < 0) {
[_condition signal];Â Â // Release the first blocked thread
    //[_condition broadcast]; // Release all blocked threads
}
[_condition unlock];
}
// The customer, the service has savings, and when the money is received, it immediately unlocks the spending skill (money--).
- (void)conditionSubMoney {
[_condition lock];
if (_money == 0) {
// The semaphore reduces congestion. You want to buy something, but you don't have enough money. You stop spending money and wait until you get paid
[_condition wait];
}
// Because of the previous wait, when the signal is unlocked, it will go here and start buying the desired item, saving the value of --
_money--;
[_condition unlock];
}
Copy the code
NSConditionLock
NSConditionLock is called conditional lock. It follows the NSLocking protocol, i.e. it has the normal mutex function
In addition, the conditional statement is added as the core function, that is, the specified conditions are met to unlock, so it is a heavy weight lock, it can also be understood as an NSCondition evolution, if you understand the NSCondition producer-consumer pattern, this will soon become clear
LockWhenCondition :(NSInteger) Condition: locks the condition when the condition is the passed condition
UnlockWithCondition :(NSInteger) Condition: updates the value of the condition and unlocks the lock on the specified condition
The following uses an asynchronous queue to implement dependencies similar to the NSOperation Settings, as shown below (print results 1, 4, 3, 2) :
#pragma mark -- NSConditionLock, implements NSLocking protocol, supports default mutex lock, unlock - (void)NSConditionLock {
_conditionLock = [[NSConditionLock alloc] initWithCondition:1]; // Can change the test value to 0 test result
// Lock, and unlock only when the condition is the incoming condition
//lockWhenCondition:(NSInteger)condition
// Update the value of the condition and unlock the lock on the specified condition
//unlockWithCondition:(NSInteger)condition
}
// Multiple queues perform conditional locking
ConditionLock (); // conditionLock ()
// Is it possible to set a task queue with dependencies via GCD
- (void)NSConditionLockUpdate {
// Create a concurrent queue
dispatch_queue_t queue =
dispatch_queue_create("Test NSConditionLock", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
if ([self->_conditionLock tryLockWhenCondition:1]) {
NSLog(@"The first.");
// Default initial conditon bit 1, all can go here
// Then after unlocking and setting the initial value to 4, unlock the thread whose condition is set to 4
[self->_conditionLock unlockWithCondition:4];
}else {
[self->_conditionLock lockWhenCondition:0];
NSLog(@"The first other");
[self->_conditionLock unlockWithCondition:4]; }});// Since the initial conditon value is 1, none of the following three threads satisfies the condition
// Lock until condition adjusts to the current thread's condition
dispatch_async(queue, ^{
// Condition is set to 3 to unlock the current thread
[self->_conditionLock lockWhenCondition:2];
NSLog(@"The second.");
// After the execution is complete, unlock the device and set the condition to 1, setting the initial default value for the next use
[self->_conditionLock unlockWithCondition:1];
});
dispatch_async(queue, ^{
// Condition is set to 3 to unlock the current thread
[self->_conditionLock lockWhenCondition:3];
NSLog(@"The third.");
// Set condition to 3 and unlock 3
[self->_conditionLock unlockWithCondition:2];
});
dispatch_async(queue, ^{
// Condition is set to 4 to unlock the current thread
[self->_conditionLock lockWhenCondition:4];
NSLog(@"Fourth.");
// Set condition to 3 and unlock 3
[self->_conditionLock unlockWithCondition:3];
});
}
Copy the code
The above process can be simplified to the following steps:
1. Create an asynchronous queue for adding subsequent task dependencies
2. Add subtask modules step by step in different threads with clear dependencies, that is, the execution order is 1, 4, 3 and 2
3. Use lockWhenCondition: Start setting the dependency by setting the condition that its task is unlocked to its unique condition number for easy unlocking
4. Block the thread if the condition parameter in NSCondition is different from the tCondition set by the thread. Unlock by waiting for the condition in the NSCondition to change to the specified value (by unlockWithCondition: Changing the condition value)
That is: Condition is initialized to 1 by default, and only task 1 can execute. When task 1 executes unlockWithCondition:4, condition is set to 4 and blocked task 4 is unlocked. Similarly, after task 4 is executed, Set condition to 3, unlock task 3, and so on
5. Perform tasks 1, 4, 3, and 2 based on the configured dependencies
pthread_mutex(recursive)
It is a recursive lock based on pThread framework, and it is also a recursive lock based on pThread mutex. That is, under the same thread, the lock is applied when recursively invoked, but the current thread will not be blocked. When another thread arrives, it will be blocked because of the lock applied by the first thread
#pragma mark --pthread recursion lock - (void)pthreadMutexRecursive {
// Initialize the recursive function of the lock
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
// When the mutex is initialized, bind the recursive lock function module
pthread_mutex_init(&_pMutexLock, &attr);
// Destroy it in an appropriate place after use, such as dealloc
// pthread_mutexattr_destroy(&attr);
// pthread_mutex_destroy(&_pMutexLock);
}
// Use a recursive lock. If you use a normal lock, the lock is already deadlocked and cannot be released
// Recursive locks exist in the same thread, not mutually exclusive, only other threads of the lock mutually exclusive, thus avoiding deadlock
- (void)pthreadMutexRecursiveUpdate {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {static void (^recursiveBlock)(double count);
recursiveBlock = ^(double count){
pthread_mutex_lock(&self->_pMutexLock);
if (count-- > 0) {
self->_money++;
recursiveBlock(count);
}
pthread_mutex_unlock(&self->_pMutexLock);
};
recursiveBlock(1000);
});
}
Copy the code
NSRecursiveLock recursive locking
Like pthread_mutex(recursive), NSRecursiveLock is a recursive lock, which follows the NSLocking protocol, that is, it has normal mutex functionality in addition to recursive locking
It is used in the same way as pthread_mutex(recursive) as shown below
// Use a recursive lock. If you use a normal lock, the lock is already deadlocked and cannot be released
// Recursive locks exist in the same thread, not mutually exclusive, only other threads of the lock mutually exclusive, thus avoiding deadlock
- (void)NSRecursiveLockUpdate {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {static void (^recursiveBlock)(double count);
recursiveBlock = ^(double count){
[self->_recursive lock];
TryLock: //tryLock: //tryLock: //tryLock
//[self->_recursive tryLock];
if (count-- > 0) {
self->_money++;
recursiveBlock(count);
}
[self->_recursive unlock];
};
recursiveBlock(1000);
});
}
Copy the code
synchronized
Synchronized execution, which prevents multiple threads from working on the same piece of code at the same time, is one of the most inefficient, but beloved by many because it is easy to use
The implementation is shown below
#pragma mark -- synchronized - (void)synchronized {
// This code will not be executed directly by multiple threads
// This means that the tasks are placed on a synchronous queue to be executed.@synchronized (self) { self->_money++; }}Copy the code
Read-write lock
Read/write locks, also known as RW locks or readWrite locks, are commonly used in ios development, but they are not commonly used (usually for database operations).
The specific operations are as follows: Multiple read and single write. That is, write operations can only be performed sequentially and cannot be read while writing. Read operations must support multi-threaded operations and cannot be written while reading
I’m sure you’ve seen this before. The system’s property is set to auto, which literally means atomic, and it actually fails to guarantee multithreaded safety in the property field (because the old value is assigned unlocked and the old address is released multiple times).
Therefore, no matter want to understand its implementation, or the development of standby, are more learning
There are two implementation methods: PThread and GCD barrier
Pthread read-write lock
#import
Simple implementation, according to their own program needs, choose the appropriate location of the lock initialization
// Initialize the pThread read/write lock
- (void)setupPhreadRW {
pthread_rwlock_init(&_lock, NULL);
// Destroy the read/write lock after use
//pthread_rwlock_destroy(&_lock);} #pragma mark -- set by pthread read/write lock -- (void)setLock1:(NSString *)lock1 {
pthread_rwlock_wrlock(&_lock);
_lock1 = lock1;
pthread_rwlock_unlock(&_lock);
}
- (NSString *)lock1 {
NSString *lock1 = nil;
pthread_rwlock_rdlock(&_lock);
lock1 = [_lock1 copy]; // Copy to the new address to avoid unlocking the old value
pthread_rwlock_unlock(&_lock);
return lock1;
}
Copy the code
GCD barrier read and write locks
You’ve all heard of the barrier function in GCD. In a newly created queue, the barrier function ensures that the contents of the barrier are not specified until the previous asynchronous queue has finished executing, and that the tasks that follow the barrier are not specified until the previous asynchronous queue has finished executing. And a queue can have multiple barriers
Therefore, this feature can be used to complete a read-write lock, where the barrier block acts as a write operation module
As shown in the following code, while not as good to use as pThread because of the need to introduce a new creation queue, the idea can sprout at just the right time
- (void)setupGCDRW {
_queue = dispatch_queue_create("RWLockQueue", DISPATCH_QUEUE_CONCURRENT); } #pragma mark -- this is implemented by GCD's barrier function// Use the barrier function of GCD. The drawback is that you need to use a custom queue to implement it. In addition, the get method cannot override the system, and can only get the value in the way of callback
// The barrier function is disabled when you use a global queue. Global queues are not blocked, and some tasks in the system are executed
- (void)setLock2:(NSString *)lock2 {
dispatch_barrier_async(_queue, ^{
self->_lock2 = lock2;
});
}
- (void)getLock2WithBlock:(void(^)(NSString *))block {
dispatch_async(_queue, ^{
block(self->_lock2);
});
}
Copy the code
The last
I believe reading this article can bring you more goods
Finally, can you write a complete read/write lock based on the features of the read/write lock using existing locks?