Multithreading is often used in the usual development, in the process of using multithreading, it will inevitably encounter the problem of resource competition, then how do we avoid this problem?
What is thread safety?
When one thread accesses data, no other thread can access it until it finishes. Simply put, there is only one thread operating on the same data at any one time. Only when this is done can the data not be affected by other threads. Thread insecurity means that multiple threads can access the data at the same time without getting the desired results.
For example, when one thread is writing a file and another thread is reading a file, in theory, the result can be unexpected.
How to ensure thread safety?
A lock mechanism is usually used to ensure thread-safety, that is, only the same thread can access the same data source at the same time. YY OSSpinLock is no longer safe. The blog lists various locks and performance comparisons:
The performance comparison here is only the time spent immediately unlocking the lock, not the time spent in the race.
Lock introduction and simple use
1.@synchronized
@synchronized is the most common lock on iOS, and its usage is simple:
- (void)viewDidLoad { [super viewDidLoad]; [self synchronized]; } - (void)synchronized { NSObject * cjobj = [NSObject new]; Dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{@synchronized(cjobj){NSLog(@" thread 1 starts "); sleep(3); NSLog(@" thread 1 ends "); }}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); @synchronized(cjobj){NSLog(@" thread 2"); }}); } Console output: 2017-10-18 11:35:16.460210+0800 Thread Lock[24855:431100] Thread 1 start 2017-10-18 11:35:16.460210+0800 2017-10-18 11:35:16.460434+0800 Thread Lock[24855:431101] Thread 2Copy the code
As can be seen from the console output time above, the output of the content of thread 2 is only after all the output of thread 1. “End of thread 1” and “thread 2” are both output 3 seconds after “start of thread 1”.
The cjobj used by the @synchronized(cjobj) command is the unique identifier of the lock. Only when the identifier is the same, the lock is mutually exclusive. If the @synchronized(cjobj) in thread 2 is changed to @synchronized(self), Thread 2 is not blocked. The advantage of @synchronized is that we do not need to explicitly create a lock object in the code to implement the locking mechanism, but as a precaution, the @synchronized block implicitly adds an exception handler to protect the code. This routine automatically releases the mutex when an exception is thrown. So if you don’t want the extra overhead of implicit exception-handling routines, you can consider using lock objects.
@sychronized(cjobj){} Internal cjobj being freed or set to nil does not affect the lock function, but if cjobj was nil to begin with, the lock function would be lost.
2.NSLock
This class is divided into several subclasses: NSLock, NSConditionLock, NSRecursiveLock, NSCondition, and NSLocking protocol.
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
Copy the code
Although NSLock, NSConditionLock, NSRecursiveLock and NSCondition all follow the NSLocking protocol, they are different.
2.1 NSLock
NSLock implements the most basic mutual exclusion. It follows the NSLocking protocol and uses lock and unlock to lock and unlock.
Source code content:
@interface NSLock : NSObject <NSLocking> { @private void *_priv; } - (BOOL)tryLock; - (BOOL)lockBeforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name API_AVAILABLE(MacOS (10.5), ios(2.0), Watchos (2.0), TVOs (9.0)); @endCopy the code
Usage:
- (void)viewDidLoad { [super viewDidLoad]; [self nslock]; } - (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@" thread 1 locked successfully "); sleep(2); [cjlock unlock]; NSLog(@" thread 1 unlocked successfully "); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); [cjlock lock]; NSLog(@" thread 2 locked successfully "); [cjlock unlock]; NSLog(@" Thread 2 unlocked successfully "); }); } Console output: 2017-10-19 15:03:58.868708+0800 Thread Lock 2017-10-19 15:04:00.872714+0800 Thread Lock[39059:846493 Thread Lock[39059:846493] Thread 1 unlocked successfully 2017-10-19 15:04:00.872722+0800 Thread 2 unlocked successfully 2017-10-19 15:04:00.872722+0800 Thread 2 unlocked successfully 2017-10-19 15:04:00.873000+0800 Thread Lock[39059:846492] Thread 2 Unlocked successfully - (void)nslock {nslock * cjLock = [nslock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@" thread 1 locked successfully "); sleep(2); [cjlock unlock]; NSLog(@" thread 1 unlocked successfully "); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{if ([cjLock tryLock]) {NSLog(@" thread 3 locked successfully "); [cjlock unlock]; NSLog(@" Thread 3 unlocked "); }else {NSLog(@" thread 3 failed to lock "); }}); } Console output: 2017-10-19 15:05:38.627767+0800 Thread Lock 2017-10-19 15:05:38.627767+0800 Thread Lock[39118:849171 [39118:849169] Thread 3 Lock failed 2017-10-19 15:05:40.629969+0800 Thread 1 Lock failed - (void)nslock { NSLock * cjlock = [NSLock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@" thread 1 locked successfully "); sleep(2); [cjlock unlock]; NSLog(@" thread 1 unlocked successfully "); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(3); If ([cjlock tryLock]) {NSLog(@" thread 4 locked successfully "); [cjlock unlock]; NSLog(@" Thread 4 unlocked "); }else {NSLog(@" thread 4 failed to lock "); }}); } Console output: 2017-10-19 15:07:14.872279+0800 Thread Lock[39166:851060] 2017-10-19 15:07:16.876108+0800 2017-10-19 15:07:17.876208+0800 Thread Lock[39166:851052] Thread 4 Lock 2017-10-19 15:07:17.876208+0800 Thread 4 Lock 2017-10-19 15:07:851052 15:07:17.876527+0800 Thread Lock[39166:851052] Thread 4 Unlocked successfully - (void)nslock {nslock * cjlock = [nslock new]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@" thread 1 locked successfully "); sleep(2); [cjlock unlock]; NSLog(@" thread 1 unlocked successfully "); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {the if ([cjlock lockBeforeDate: [NSDate dateWithTimeIntervalSinceNow: 10]]) {NSLog (@ "thread lock 5 success"); [cjlock unlock]; NSLog(@" Thread 5 unlocked "); }else {NSLog(@" thread 5 failed to lock "); }}); } Console output: 2017-10-19 15:08:41.708717+0800 Thread Lock 2017-10-19 15:08:41.708717+0800 Thread Lock[39204:852782 708717+0800 Thread Lock[39204:852784] Thread 1 Lock 2017-10-19 15:08:41 15:08:41.708935+0800 thread-lock Thread 5 unlocked successfullyCopy the code
Note: Lock and UNLOCK operations must be performed on the same thread, otherwise the result is uncertain or even deadlock
Summarized from the above contents:
- In addition to the lock and unlock methods, NSLock provides tryLock and lockBeforeDate: two methods.
- TryLock does not block. [cjlock tryLock] returns YES if it is locked, NO if it is not, and all subsequent code is executed.
- If the current thread fails to lock, you can continue with other tasks. Trylock is appropriate. The current thread won’t do any meaningful work until the lock succeeds, so lock, no need to poll trylock. This is true for all of the following locks.
- LockBeforeDate: The method tries to lock before the specified Date, blocks the thread, returns NO if the lock cannot be held before the specified time, and YES if the lock can be held before the specified time.
- Because it is a mutex lock, when one thread accesses, the thread obtains the lock, when other threads access, the operating system will suspend, until the thread releases the lock, other threads can access it, thus ensuring thread safety. But if you lock it twice in a row, you can cause a deadlock problem.
2.2 NSRecursiveLock
NSRecursiveLock is a recursive lock that, as its name implies, can be acquired multiple times by a thread without causing a deadlock. It records the number of times the lock was successfully acquired. For each successful lock acquisition, there must be a corresponding release lock so that a deadlock is not caused. NSRecursiveLock keeps track of how many times it was locked and unlocked. When the balance is reached, the lock is released and other threads can lock successfully.
Source code content:
@interface NSRecursiveLock : NSObject <NSLocking> { @private void *_priv; } - (BOOL)tryLock; - (BOOL)lockBeforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name API_AVAILABLE(MacOS (10.5), ios(2.0), Watchos (2.0), TVOs (9.0)); @endCopy the code
Usage:
- (void)viewDidLoad { [super viewDidLoad]; [self nsrecursivelock]; } - (void)nsrecursivelock{ NSRecursiveLock * cjlock = [[NSRecursiveLock alloc] init]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { [cjlock lock]; NSLog(@"%d locked successfully ",value); if (value > 0) { NSLog(@"value:%d", value); RecursiveBlock(value - 1); } [cjlock unlock]; NSLog(@"%d unlocked successfully ",value); }; RecursiveBlock(3); }); } Console output: 2017-10-19 16:15:40.584213+0800 Thread Lock[39579:894111 Thread Lock[39579:894111] value:3 2017-10-19 16:15:40.584552+0800 Thread Lock[399579:894111 16:15:40.584635+0800 thread-lock [39579:894111] value:2 2017-10-19 16:15:40.584810+0800 thread-lock [39579:894111] 1 2017-10-19 16:15:40.585267+0800 thread-lock [39579:894111] value:1 2017-10-19 16:15:40.585714+0800 Thread Lock[399579 :894111] 0 Successfully unlocked 2017-10-19 16:15:40.585906+0800 Thread Lock[399579 :894111] 0 Successfully unlocked 2017-10-19 16:15:40.586138+0800 Thread-lock [39579:894111] 1 Unlocked successfully 2017-10-19 16:15:40.586217+0800 thread-lock [39579:894111] 2 Unlocked successfully 2017-10-19 16:15:40.586314+0800 Thread Lock[39579:894111] 3 The Lock is unlocked successfullyCopy the code
Summarized from the above contents:
- If NSLock is used, cjLock will be locked first, but before unlocking, it will enter the next layer of recursion, and lock will be requested again, blocking the thread, the thread is blocked, naturally, the following unlock code will not be executed, and a deadlock is formed. The NSRecursiveLock recursive lock is designed to solve this problem.
2.3 NSConditionLock
The mutex defined by NSLock can be locked and unlocked under certain conditions. It is similar to NSLockin that it follows the NSLocking protocol and uses a similar method, except that the condition attribute is added. NSConditionLock can be called conditional lock because each operation has one more method on the condition property, such as tryLock, tryLock whencondition:.
- If condition is equal to condition at initialization, lock will be able to lock correctly.
- UnlockWithCondition: Instead of unlocking the condition when it meets the conditions, the value of the condition is changed after the condition is unlocked.
Source code content:
@interface NSConditionLock : NSObject <NSLocking> { @private void *_priv; } - (instancetype)initWithCondition:(NSInteger)condition NS_DESIGNATED_INITIALIZER; @property (readonly) NSInteger condition; - (void)lockWhenCondition:(NSInteger)condition; - (BOOL)tryLock; - (BOOL)tryLockWhenCondition:(NSInteger)condition; - (void)unlockWithCondition:(NSInteger)condition; - (BOOL)lockBeforeDate:(NSDate *)limit; - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit; @property (nullable, copy) NSString *name API_AVAILABLE(MacOS (10.5), ios(2.0), Watchos (2.0), TVOs (9.0)); @endCopy the code
Usage:
- (void)viewDidLoad { [super viewDidLoad]; [self nsconditionlock]; } - (void)nsconditionlock { NSConditionLock * cjlock = [[NSConditionLock alloc] initWithCondition:0]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cjlock lock]; NSLog(@" thread 1 locked successfully "); sleep(1); [cjlock unlock]; NSLog(@" thread 1 unlocked successfully "); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); [cjlock lockWhenCondition:1]; NSLog(@" thread 2 locked successfully "); [cjlock unlock]; NSLog(@" Thread 2 unlocked successfully "); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(2); If ([cjlock tryLockWhenCondition:0]) {NSLog(@" thread 3 lock successful "); sleep(2); [cjlock unlockWithCondition:2]; NSLog(@" Thread 3 unlocked "); } else {NSLog(@" thread 3 failed to lock "); }}); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^ {the if ([cjlock lockWhenCondition: 2 beforeDate: [NSDate dateWithTimeIntervalSinceNow: 10]]) {NSLog (@ "thread locking 4 success"); [cjlock unlockWithCondition:1]; NSLog(@" Thread 4 unlocked "); } else {NSLog(@" thread 4 failed to lock "); }}); } Console output: 2017-10-19 15:09:44.010992+0800 Thread Lock 2017-10-19 15:09:45.012045+0800 [39230:853946] Thread 1 unlocked successfully 2017-10-19 15:09:46.012692+0800 Thread 3 locked successfully 2017-10-19 15:09:48.016536+0800 Thread Lock Thread 3 unlocked successfully 2017-10-19 15:09:48.016564+0800 Thread 4 locked successfully 2017-10-19 15:09:48.017039+0800 Thread Lock 2017-10-19 15:09:48.017040+0800 Thread Lock[39230:853944 2017-10-19 15:09:48.017215+0800 Thread Lock[39230:853945] Thread 2 unlocked successfullyCopy the code
Summarized from the above contents:
- After thread 1 is unlocked successfully, thread 2 does not unlock successfully. Instead, thread 3 is unlocked successfully after 1 second. This is because the condition of thread 2 is not satisfied, and the condition of thread 2 is 1. So thread 2 failed to lock.
- LockWhenCondition is similar to the lock method. Failure to lock will block the thread, so thread 2 will be blocked.
- TryLockWhenCondition: method returns NO if the condition is not met and does not block the current thread.
- LockWhenCondition: beforeDate: method will within the prescribed period of time has been waiting for condition into 2, and blocks the current thread, until the return NO overtime.
- Lock and unlock calls can be combined at will, that is, lock and lockWhenCondition can be combined at will with unlock and unlockWithCondition.
2.4, NSCondition
NSCondition is a special type of lock that allows scheduling of different threads. A thread is blocked by a condition until another thread satisfies the condition to send a signal to that thread so that it can execute correctly. For example, you can start one thread to download images and one thread to process images. In this case, the thread that needs to process the image, since no image will block, will be given a signal to the thread that needs to process the image when the download thread is finished.
- The NSCondition object actually acts as a lock and a thread checker. After the lock is locked, other threads can also be locked, and then the thread can decide whether to continue running according to the condition, that is, whether the thread is in waiting state. If it is in waiting state, When the lock in another thread executes the signal or broadcast method, the thread is woken up to continue running the subsequent method.
- NSCondition can manually control thread suspension and awakening, and you can use this feature to set dependencies.
Source code content:
@interface NSCondition : NSObject <NSLocking> { @private void *_priv; } - (void)wait; // suspend thread - (BOOL)waitUntilDate:(NSDate *)limit; // When to suspend the thread - (void)signal; // Wake up a suspended thread - (void)broadcast; @property (nullable, copy) NSString *name API_AVAILABLE(MacOS (10.5), ios(2.0), watchos(2.0), tVOs (9.0)); @endCopy the code
Usage:
- (void)viewDidLoad { [super viewDidLoad]; [self nscondition]; } - (void)nscondition { NSCondition * cjcondition = [NSCondition new]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ [cjcondition lock]; NSLog(@" thread 1 thread lock "); [cjcondition wait]; NSLog(@" thread 1 thread wake up "); [cjcondition unlock]; NSLog(@" thread 1 thread unlocked "); }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ [cjcondition lock]; NSLog(@" thread 2 thread lock "); If ([cjcondition waitUntilDate: [NSDate dateWithTimeIntervalSinceNow: 10]]) {NSLog (@ "thread 2 thread awaken"); [cjcondition unlock]; NSLog(@" thread 2 thread unlock "); }}); dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(2); [cjcondition signal]; }); } Console output: 2017-10-19 17:15:48.410757+0800 Thread Lock 2017-10-19 17:15:48.410757+0800 Thread Lock[40011:943638 Thread - Lock [40011-943640] Thread 2 Thread locking the 2017-10-19 17:15:50. 414288 + 0800 Thread - Lock (40011-943638) wakes up the 2017-10-19 Thread 1 Thread 17:15:50.414454+0800 thread-lock [40011:943638] Thread 1 Thread unlock // if [cjcondition signal]; To [cjcondition broadcast]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(2); [cjcondition broadcast]; }); Console output: 2017-10-19 17:18:08.054109+0800 Thread Lock[40056:946099] 2017-10-19 17:18:08.054304+0800 [40056:946096] Thread 1: Lock 2017-10-19 17:18:10.056071+0800 Thread 1: Lock 2017-10-19 17:18:10.056071+0800 Thread 1: Lock 2017-10-19 17:18:10.056071+0800 Thread 2: Lock 2017-10-19 17:18:10.056071+0800 Thread 1: Lock 2017-10-19 17:18:946099 17:18:10.056231+0800 Thread Lock[40056:946099] Thread 1 Thread unlock 2017-10-19 17:18:10.056244+0800 Thread 2 Thread wake up 2017-10-19 17:18:10.056445+0800 thread-lock [40056:946096] Thread 2 Thread unlockedCopy the code
Summarized from the above contents:
- After the lock is added, the wait or waitUntilDate: methods of the condition object are called to block the thread until the condition object has signaled wakeup or timed out.
- The difference between signal and broadcast is that signal is only a semaphore and can only wake up one waiting thread, requiring multiple calls to wake up multiple threads. Broadcast can wake up all waiting threads.
3.dispatch_semaphore
Dispatch_semaphore uses the semaphore mechanism to lock, wait for signals and send signals.
- Dispatch_semaphore is a GCD method for synchronization. There are only three functions associated with it, one for creating semaphores, one for waiting for signals, and one for sending signals.
- The mechanism of dispatch_semaphore is that if one thread receives a signal, the other threads must wait for the signal to be released.
Common related apis:
dispatch_semaphore_create(long value);
dispatch_semaphore_wait(dispatch_semaphore_t _Nonnull dsema, dispatch_time_t timeout);
dispatch_semaphore_signal(dispatch_semaphore_t _Nonnull dsema);
Copy the code
Usage:
- (void)viewDidLoad { [super viewDidLoad]; [self dispatch_semaphore]; } - (void)dispatch_semaphore { dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 6 * NSEC_PER_SEC); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_semaphore_wait(semaphore, overTime); NSLog(@" thread 1 starts "); sleep(5); NSLog(@" thread 1 ends "); dispatch_semaphore_signal(semaphore); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); dispatch_semaphore_wait(semaphore, overTime); NSLog(@" thread 2 starts "); dispatch_semaphore_signal(semaphore); }); } Console output: 2017-10-19 18:30:42.672490 +0800 Thread Lock[40569:993613] Thread 1 start 2017-10-19 18:30:42.673845+0800 [40569:993613] Thread 1 ends 2017-10-19 18:30:42.674165+0800 Thread 2 starts // If the overTime is changed to 3 seconds Console output: 2017-10-19 18:32:32.078186+0800 Thread Lock[40634:995921] Thread 1 start 2017-10-19 18:32:35.082943+0800 2017-10-19 18:32:37.083115+0800 Thread Lock[40634:995921] Thread 1 endsCopy the code
Summarized from the above contents:
- Dispatch_semaphore is similar to NSCondition in that it is a signal-based synchronization, but NSCondition signals can only be sent, not saved (if there is no thread waiting, the sent signal is invalid). Dispatch_semaphore saves sent signals. The core of dispatch_Semaphore is a semaphore of type dispatch_semaphore_T.
- The dispatch_semaphoRE_create (1) method creates a semaphore of type dispatch_semaphore_t with an initial value of 1. Note that the parameter passed in here must be greater than or equal to 0, otherwise dispatch_semaphoRE_CREATE returns NULL.
- dispatch_semaphore_wait(semaphore, overTime); Method determines whether semaphore’s signal value is greater than 0. Greater than zero does not block the thread, consuming a signal for subsequent tasks. If the signal value is 0, the thread will enter the waiting state like NSCondition and wait for other threads to send the signal to wake up the thread to perform the follow-up task, or it will also perform the follow-up task when the overTime time expires.
- dispatch_semaphore_signal(semaphore); Sends a signal, and increments the signal value by one if no waiting thread accepts the signal.
- A dispatch_semaphore_wait (semaphore, overTime); Method will correspond to a dispatch_semaphore_signal(semaphore); Lock UNLOCK can only be accessed by one thread at a time. If the initial semaphore value of dispatch_semaphore is x, X threads can access the protected critical section simultaneously.
4. The pthread_mutex and pthread_mutexes (recursive)
Pthread stands for POSIX thread, which defines a set of cross-platform thread-related apis. POSIX mutex is a super easy to use mutex.
- Just initialize a pthread_mutex_t with pthread_mutex_init,
- Pthread_mutex_lock or pthread_mutex_trylock to lock,
- Pthread_mutex_unlock,
- When finished, remember to call pthread_mutex_destroy to destroy the lock.
Common related apis:
pthread_mutex_init(pthread_mutex_t *restrict _Nonnull, const pthread_mutexattr_t *restrict _Nullable);
pthread_mutex_lock(pthread_mutex_t * _Nonnull);
pthread_mutex_trylock(pthread_mutex_t * _Nonnull);
pthread_mutex_unlock(pthread_mutex_t * _Nonnull);
pthread_mutex_destroy(pthread_mutex_t * _Nonnull);
Copy the code
Usage:
//pthread_mutex - (void)viewDidLoad { [super viewDidLoad]; [self pthread_mutex]; } - (void)pthread_mutex { __block pthread_mutex_t cjlock; pthread_mutex_init(&cjlock, NULL); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ pthread_mutex_lock(&cjlock); NSLog(@" thread 1 starts "); sleep(3); NSLog(@" thread 1 ends "); pthread_mutex_unlock(&cjlock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(1); pthread_mutex_lock(&cjlock); NSLog (@ "thread 2"); pthread_mutex_unlock(&cjlock); }); } Console output: 2017-10-23 14:50:32.842180 +0800 Thread Lock[74478:1647362] Thread 1 start 2017-10-23 14:50:32.846786+0800 2017-10-23 14:50:32.847001+0800 Thread Lock[74478:1647359] Thread 2 //pthread_mutex(recursive) - (void)viewDidLoad { [super viewDidLoad]; [self pthread_mutex_recursive]; } - (void)pthread_mutex_recursive { __block pthread_mutex_t cjlock; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); pthread_mutex_init(&cjlock, &attr); pthread_mutexattr_destroy(&attr); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { pthread_mutex_lock(&cjlock); NSLog(@"%d locked successfully ",value); if (value > 0) { NSLog(@"value = %d", value); sleep(1); RecursiveBlock(value - 1); } NSLog(@"%d unlocked successfully ",value); pthread_mutex_unlock(&cjlock); }; RecursiveBlock(3); }); } // Console output: 2017-10-23 15:31:51.599693+0800 Thread Lock 2017-10-23 15:31:51.599912+0800 [74723:1668089 Thread Lock[74723:1668089] value = 3 2017-10-23 15:31:52.602002+0800 Thread Lock[74723:1668089 15:31:52.602317+0800 Thread Lock[74723:1668089] value = 2 2017-10-23 15:31:53.604669+0800 Thread Lock[74723:1668089] 2017-10-23 15:31:53.604957+0800 Thread-lock [74723:1668089] value = 1 2017-10-23 15:31:54.607778+0800 Thread Lock[74723:1668089] 0 Lock Lock 2017-10-23 15:31:54.608109+0800 Thread Lock[74723:1668089] 0 Lock Lock 2017-10-23 15:31:54.608391+0800 Thread-lock [74723:1668089] 1 Unlocked 2017-10-23 15:31:54.608622+0800 thread-lock [74723:1668089] 2 Unlocked successfully 2017-10-23 15:31:54.608945+0800 Thread Lock[74723:1668089] 3 The Lock is unlocked successfullyCopy the code
Summarized from the above contents:
- It uses lock unlock in the same way as NSLock, and it also has a pthread_mutex_trylock method, and the difference between pthread_mutex_trylock and tryLock is, TryLock returns YES and NO, pthread_mutex_trylock returns 0 on success, and an error message on failure.
- Pthread_mutex (recursive) works like NSRecursiveLock recursive locks. If pthread_mutex_init(&theLock, NULL) is used; The second part of the code above is deadlocked when the lock is initialized, which can be avoided by using recursive locks.
5. OSSpinLock
OSSpinLock is a type of spin lock, similar to mutex, that is used to keep threads safe. However, the difference is different. With a mutex, when one thread acquires the lock, other threads that want to acquire the lock block until the lock is released. However, optional locks are different. Once one thread has acquired the lock, other threads will loop around to see if the lock has been released. Therefore, this lock is more suitable for the lock holder to save a short time.
- Only lock, unlock, try to lock three methods.
Common related apis:
typedef int32_t OSSpinLock; Void OSSpinLock (volatile OSSpinLock *__lock); Bool OSSpinLockTry(volatile OSSpinLock *__lock); Void OSSpinLockUnlock(volatile OSSpinLock *__lock);Copy the code
Usage:
#import <libkern/OSAtomic.h> - (void)viewDidLoad { [super viewDidLoad]; [self osspinlock]; } - (void)osspinlock { __block OSSpinLock theLock = OS_SPINLOCK_INIT; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); NSLog(@" thread 1 starts "); sleep(3); NSLog(@" thread 1 ends "); OSSpinLockUnlock(&theLock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); sleep(1); NSLog (@ "thread 2"); OSSpinLockUnlock(&theLock); }); } // Console output: 2017-10-23 16:02:48.865501+0800 Thread Lock[75025:1684487] Thread 1 start 2017-10-23 16:02:51.868736+0800 2017-10-23 16:02:52.922911+0800 Thread Lock[75025:1684486] Thread 2Copy the code
In iOS 10.0, OSSpinLock was replaced by os_UNfair_lock in < OS /lock.h>.
6.os_unfair_lock
Then Apple created os_UNfair_LOCK, which solved the priority inversion problem.
Common related apis:
Os_unfair_lock_t unfairLock = &(OS_UNFAIR_LOCK_INIT); / / lock os_unfair_lock_lock (unfairLock); BOOL b = OS_UNFAIR_LOCK_trylock (unfairLock); / / unlock os_unfair_lock_unlock (unfairLock);Copy the code
Os_unfair_lock and OSSpinLock are the same.
conclusion
Different locks should be used for different operations, and it is not possible to generalize which locks unlock faster.
- In fact, each kind of lock is basically lock, wait, unlock steps, understand these three steps can help you quickly learn the use of various locks.
- @synchronized is the least efficient, but it’s certainly the easiest to use, so if you don’t have any performance bottlenecks, use @synchronized.
- Os_unfair_lock is available only in iOS10. If you need to use pthread_mutex or dispath_semaphore, you can use OSSpinLock to ensure thread safety. The first two are better choices. This ensures both speed and thread safety.
- For NSLock and its subclasses, speed NSLock < NSCondition < NSRecursiveLock < NSConditionLock.
If you want to understand how each lock works, take a look at this one, which is awesome: Understanding locks in iOS development
OSSpinLock OSSpinLock oN iOS There’s more to know about @synchronized than you ever wanted to know