The topic
When it comes to locking, we have to mention thread safety, and when it comes to thread safety, as iOS programmers, we have to mention nonatomic and atomic
-
nonatomic
It’s not going to generategetter
,setter
Method add synchronization lock (non-atomic) -
atomic
It’s going to be generatedgetter
、setter
Add synchronization lock (atomicity)
When a setter/getter is modified by atomic, the property is read-write safe. However, read-write safety does not mean thread-safe.
Thread Safety Concept
- Thread safety refers to the locking mechanism used in multi-thread access. When one thread accesses a certain data of the class, it is protected and cannot be accessed by other threads until the thread finishes reading the data. There will be no data inconsistencies or data contamination.
- Thread insecurity means that data access protection is not provided. Multiple threads may change data successively, resulting in dirty data.
validationatomic
Non-thread-safe
- The validation code
#import "ViewController.h" @interface ViewController () @property (strong, atomic) NSString *name; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; //Jack dispatch_async(dispatch_get_global_queue(0, 0), ^{while (1) {self.name = @"Jack"; NSLog(@"Jack is %@", self.name); }}); //Rose dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (1) { self.name = @"Rose"; NSLog(@"Rose is %@", self.name); }}); }Copy the code
- The verification results
2017-11-29 11:21:27.713446+0800 LockDemo[42637:1199500] Jack is Jack 2017-11-29 11:21:27.713487+0800 LockDemo[42637:1199499] Rose is Rose 2017-11-29 11:21:27.713638+0800 LockDemo[42637:1199500] Jack is Jack 2017-11-29 713659+0800 LockDemo[42637.1199499] Rose is Rose 2017-11-29 11:21:27.713840+0800 LockDemo[42637.1199500] Jack Is Jack 2017-11-29 11:21:27.714050+0800 LockDemo[42637:1199499] Rose is Rose 2017-11-29 11:21:27.714205+0800 LockDemo[42637:1199500] Jack is Jack 2017-11-29 11:21:27.718069+0800 LockDemo[42637:1199499] Rose is Rose 2017-11-29 11:21:27. 718069 + 0800 LockDemo [42637-1199500] Jack is Rose 2017-11-29 11:21:27. 718199 + 0800 LockDemo [42637-1199500] Jack 718199+0800 LockDemo[42637:1199499] Rose is Jack 2017-11-29 11:21:27.718199+0800 LockDemo[42637:1199499Copy the code
The last and third-to-last lines show that the atomic non-thread-safe verification is complete.
- That is to say,
atomic
Read-write security alone does not make thread-safe, and a deeper locking mechanism is needed to make thread-safe. - It’s used in iOS development
nonatomic
Property, because of the overhead of using synchronization locks in iOS, which can cause performance problems, but in Mac OS X applications, useatomic
Properties generally do not have performance bottlenecks.
The concept of the lock
In computer science, a lock is a synchronization mechanism used to restrict access to resources in an environment where multiple threads exist.
The role of the lock
- In plain English: to prevent dirty reads or dirty writes to shared resources in the case of multiple threads.
- Also known as: the synchronization mechanism used to enforce restrictions on access to resources when executing multiple threads, i.e. the requirement of mutual exclusion in concurrency control.
Locks commonly used in iOS development
@synchronized
-
NSLock
Object lock -
NSRecursiveLock
Recursive locking -
NSConditionLock
Conditions for the lock -
pthread_mutex
Mutex (C) -
dispatch_semaphore
Semaphore implementation locking (GCD
) -
OSSpinLock
spinlocks
Source of performance diagram: Ibireme
@synchronized
@synchronized is an OC level lock that sacrifices performance for syntactic simplicity and readability. @synchronized is the one we use the most but perform the least. OC writing:
@synchronized(self) {// synchronized(self)Copy the code
Swift writing:
Objc_sync_enter (self) // Code block to execute objc_sync_exit(self)Copy the code
Code examples:
// thread 1 dispatch_async(dispatch_get_global_queue(0, 0), ^{@synchronized(self) {NSLog(@" first thread synchronization started "); sleep(3); NSLog(@" first thread synchronization ends "); }}); // thread 2 dispatch_async(dispatch_get_global_queue(0, 0), ^{sleep(1); @synchronized(self) {NSLog(@synchronized(self)); }});Copy the code
Results:
2017-11-29 14:36:52.056457+0800 LockDemo[46145:1306472] First thread synchronization operation started 2017-11-29 14:36:55.056868+0800 LockDemo[46145:1306472] End 2017-11-29 14:36:55.057261+0800 LockDemo[46145:1306473] End 2017-11-29 14:36:55.057261+0800Copy the code
-
@synchronized(self)
Instruction usedself
Is the unique identifier of the lock. It is mutually exclusive only if the identifiers are the sameself
Switch to something else and thread 2 won’t block.
NSString *s = [NSString string]; // thread 1 dispatch_async(dispatch_get_global_queue(0, 0), ^{@synchronized(self) {NSLog(@" first thread synchronization started "); sleep(3); NSLog(@" first thread synchronization ends "); }}); // thread 2 dispatch_async(dispatch_get_global_queue(0, 0), ^{sleep(1); @synchronized(s) {NSLog(@" second thread synchronization "); }});Copy the code
2017-11-29 14:43:55.930761+0800 LockDemo[46287:1312173] first thread synchronization operation started 2017-11-29 14:43:55.930761+0800 LockDemo[46287:1312158] Synchronize the second thread 2017-11-29 14:43:57.932287+0800 LockDemo[46287:1312173] Synchronize the first thread endCopy the code
-
@synchronized
The advantage of instruction locking is that we don’t need to explicitly create lock objects in our code to implement the locking mechanism, but as a precaution,@synchronized
Block protects code by implicitly adding an exception handler that 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.
NSLock
-
NSLock
A simple mutex is implemented in. throughNSLocking
The protocol defineslock
和unlock
Methods.
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
Copy the code
Take a chestnut selling popsicles
- (void)nslockTest {// Set the number of popsicles to 5 _count = 5; // create lock _lock = [[NSLock alloc] init]; // thread 1 dispatch_async(dispatch_get_global_queue(0, 0), ^{[self saleIceCream]; }); // thread 2 dispatch_async(dispatch_get_global_queue(0, 0), ^{[self saleIceCream]; }); } - (void)saleIceCream { while (1) { sleep(1); // lock [_lock]; if (_count > 0) { _count--; NSLog(@" number of remaining popsicles = %ld, Thread - %@, _count, [NSThread currentThread]); } else {NSThread - %@,[NSThread currentThread]); break; } // unlock [_lock unlock]; }}Copy the code
Locking result:
2017-11-29 16:21:29.728198+0800 LockDemo[55262:1411318] <NSThread: 0x604000475DC0 >{number = 3, name = (null)} 2017-11-29 16:21:29.728428+0800 LockDemo[55262:1411319] Thread - <NSThread: 0x604000475E00 >{number = 4, name = (null)} 2017-11-29 16:21:30.729009+0800 LockDemo[55262:1411318] Thread - <NSThread: 0x604000475DC0 >{number = 3, name = (null)} 2017-11-29 16:21:30.729378+0800 LockDemo[55262:1411319] Thread - <NSThread: 0x604000475E00 >{number = 4, name = (null)} 2017-11-29 16:21:31.733061+0800 LockDemo[55262:1411318] Thread - <NSThread: 0x604000475dc0>{number = 3, Name = (null)} 2017-11-29 16:21:31.733454+0800 LockDemo[55262:1411319] NSThread - <NSThread: 0x604000475e00>{number = 4, name = (null)}Copy the code
Result without locking:
702352+0800 LockDemo[55316:1412917] 702352+0800 LockDemo[55316:1412917] 0x604000270B80 >{number = 3, name = (null)} 2017-11-29 16:23:38.702352+0800 LockDemo[55316:1412919] Thread - <NSThread: 0x604000271040>{number = 4, name = (null)} 2017-11-29 16:23:39.705096+0800 LockDemo[55316:1412919] Thread - <NSThread: 0x604000271040>{number = 4, name = (null)} 2017-11-29 16:23:39.705099+0800 LockDemo[55316:1412917] Thread - <NSThread: 0x604000270B80 >{number = 3, name = (null)} 2017-11-29 16:23:40.709617+0800 LockDemo[55316:1412919] Thread - <NSThread: 0x604000271040>{number = 4, 709617+0800 LockDemo[55316:1412917] NSThread - <NSThread: 0x604000270b80>{number = 3, Name = (null)} 2017-11-29 16:23:41.714002+0800 LockDemo[55316:1412919] NSThread - <NSThread: 0x604000271040>{number = 4, name = (null)}Copy the code
-
NSLock
Classes have also been addedtryLock
和lockBeforeDate:
methods
- (BOOL)tryLock;
- (BOOL)lockBeforeDate:(NSDate *)limit;
Copy the code
TryLock attempts to acquire a lock, but it does not block the thread if the lock is not available. Instead, it simply returns NO. LockBeforeDate: The method attempts to acquire a lock, but if the lock is not acquired within a specified time, it changes the thread from blocked to non-blocked (or returns NO).
NSRecursiveLock recursive locking
Sometimes there is a recursive call in the “locking code” that locks before the recursion begins, and then the recursion begins and the method is repeated so that repeated execution of the locking code results in a deadlock.
- (void)recursiveLockTest {// create a lock _lock = [[NSLock alloc] init]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ static void(^TestMethod)(int); TestMethod = ^(int value) { [_lock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; value--; TestMethod(value); } [_lock unlock]; }; TestMethod(5); NSLog (@ "end"); }); }Copy the code
We found that the “end” will never be printed, which can be resolved by using a recursive lock. A recursive lock can be acquired repeatedly in a thread without causing a deadlock. The number of times the lock is acquired and released is recorded, and the lock is finally released only when the two balance.
- (void)recursiveLockTest {// create a lock _recursiveLock = [[NSRecursiveLock alloc] init]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ static void(^TestMethod)(int); TestMethod = ^(int value) { [_recursiveLock lock]; if (value > 0) { [NSThread sleepForTimeInterval:1]; value--; TestMethod(value); } [_recursiveLock unlock]; }; TestMethod(5); NSLog (@ "end"); }); }Copy the code
At this point “end” will be printed 5 seconds later.
Lock NSConditionLock conditions
NSCoditionLock does interthreaded task waiting calls and is thread-safe.
- (void)conditionLockTest { NSInteger HAS_DATA = 1; NSInteger NO_DATA = 0; _conditionLock = [[NSConditionLock alloc] initWithCondition:NO_DATA]; NSMutableArray *products = [NSMutableArray array]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ while (1) { [_conditionLock lockWhenCondition:NO_DATA]; [products addObject:[[NSObject alloc] init]]; NSLog (@ "production"); [_conditionLock unlockWithCondition:HAS_DATA]; sleep(5); }}); Dispatch_async (dispatch_get_global_queue(0, 0), ^{while (1) {NSLog(@" wait "); [_conditionLock lockWhenCondition:HAS_DATA]; [products removeObjectAtIndex:0]; NSLog (@ "selling"); [_conditionLock unlockWithCondition:NO_DATA]; }}); }Copy the code
NSConditionLock lock is the same as the other, also is the need to lock and unlock the corresponding, just lock, lockWhenCondition: and unlock unlockWithCondition: It can be combined arbitrarily, of course, depending on the requirements.
POSIX(pthread_mutex)
- C language under the definition of multi-thread lock. The pthread_mutex and
dispatch_semaphore_t
Similar, but totally different. Pthread_mutex is a set of conditional mutex apis provided on Unix/Linux platforms. - Create a simple pthread_mutex mutex and introduce a header file
#import <pthread.h>
Declare and initialize onepthread_mutex_t
The structure of the. usepthread_mutex_lock
和pthread_mutex_unlock
Function. callpthread_mutex_destroy
To release the lock’s data structure.
Use: #import
- (void)pthreadTest { __block pthread_mutex_t theLock; pthread_mutex_init(&theLock, NULL); dispatch_async(dispatch_get_global_queue(0, 0), ^{ pthread_mutex_lock(&theLock); NSLog(@" first thread synchronization operation started "); sleep(3); NSLog(@" first thread synchronization ends "); pthread_mutex_unlock(&theLock); }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(1); pthread_mutex_lock(&theLock); NSLog(@" second thread synchronization operation "); pthread_mutex_unlock(&theLock); }); }Copy the code
Execution Result:
2017-11-29 17:51:14.904834+0800 LockDemo[56729:1466788] first thread synchronization start 2017-11-29 17:51:14.904834+0800 LockDemo[56729-1466788] End 2017-11-29 17:51:14.905195+0800 LockDemo[56729-1466789] End 2017-11-29 17:51:14.905195+0800 LockDemo[56729-1466789] End 2017-11-29 17:51:14.905195+0800 LockDemo[56729-1466789] End 2017-11-29 17:51:14.905195+0800Copy the code
- Pthread_mutex can also create conditional locks that provide and
NSCondition
The same conditional control that initializes the mutex is used simultaneouslypthread_cond_init
To initialize the conditional data structure
Int pthread_cond_init (pthread_cond_t *cond, pthread_condattr_t *attr); Int pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mut); Int pthread_cond_timedWait (pthread_cond_t *cond, pthread_mutex_t *mut, const struct timespec *abstime); // Wake up int pthread_cond_signal (pthread_cond_t *cond); Int pthread_cond_broadcast (pthread_cond_t *cond); // destroy pthread_cond_destroy (pthread_cond_t *cond);Copy the code
Pthread_mutex also provides a number of functions, including a complete SET of APIS, including Pthreads thread creation control, very low-level, you can manually handle the thread state transition i.e. management of the life cycle, and even implement a set of multithreading, interested in further understanding.
dispatch_semaphore_t
Dispatch_semaphore_t GCD semaphores can also solve resource preemption problems and support signal notification and signal waiting. Each time a signal notification is sent, the semaphore +1; Semaphore -1, whenever a wait signal is sent; If the semaphore is 0, the signal will wait until the semaphore is greater than 0.
API:
/ *! * @param value * The starting value of the semaphore, which returns NULL if the value passed in is less than zero * @result * Successfully returns a new semaphore, */ dispatch_semaphoRE_t dispatch_semaphoRE_create (long value) /*! * @discussion * The semaphore decrement by 1, if the result is less than 0, then wait for the signal increment in the queue to arrive until timeout * @param dsema * @param timeout * wait time * type dispatch_time_t, There are two macros DISPATCH_TIME_NOW, DISPATCH_TIME_FOREVER * @result * */ long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); / *! * @discussion * Semaphore increment 1, if the previous semaphore is less than 0, will wake up a waiting thread * @param dsema * semaphore * @result * Wake up a thread return non-zero, 0 */ long dispatch_semaphoRE_signal (dispatch_semaphore_t dsema)Copy the code
Use:
- (void)semaphoreTest {dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); // thread 1 dispatch_async(dispatch_get_global_queue(0, 0), ^{dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog (@ "task 1"); sleep(10); dispatch_semaphore_signal(semaphore); }); // thread 2 dispatch_async(dispatch_get_global_queue(0, 0), ^{sleep(1); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog (@ "task 2"); dispatch_semaphore_signal(semaphore); }); }Copy the code
Execution Result:
2017-11-30 14:38:11.943521+0800 LockDemo[91493:2075379] Task 1 2017-11-30 14:38:21.946222+0800 LockDemo[91493:2075380] Task 2 2017-11-30 14:38:21.943521 +0800 LockDemo[91493:2075380] Task 2Copy the code
OSSpinLock spin lock
Use: #import
__block OSSpinLock theLock = OS_SPINLOCK_INIT; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); NSLog(@" first thread synchronization operation started "); sleep(3); NSLog(@" first thread synchronization ends "); OSSpinLockUnlock(&theLock); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ OSSpinLockLock(&theLock); sleep(1); NSLog(@" second thread synchronization operation "); OSSpinLockUnlock(&theLock); });Copy the code
Execution Result:
2017-11-30 15:12:39.705473+0800 LockDemo[92422:2104479] first thread synchronization operation started 2017-11-30 15:12:39.705473+0800 705820+0800 LockDemo[92422:2104478] The synchronization operation of the second thread has startedCopy the code
OSSpinLock Spin lock, the highest performance lock. The disadvantage is that it consumes a lot of CPU resources when waiting and is not suitable for longer tasks. OSSpinLock is no longer safe, so it is not recommended to use it.
After iOS 10, Apple came up with a solution to replace OSSpinLock with OS_UNfair_lock.
'OSSpinLockLock' is deprecated: first deprecated in iOS 10.0 - Use OS_UNfair_lock_lock () from < OS /lock.h> insteadCopy the code
#import <os/lock.h>
__block os_unfair_lock lock = OS_UNFAIR_LOCK_INIT; dispatch_async(dispatch_get_global_queue(0, 0), ^{ os_unfair_lock_lock(&lock); NSLog(@" first thread synchronization operation started "); sleep(8); NSLog(@" first thread synchronization ends "); os_unfair_lock_unlock(&lock); }); dispatch_async(dispatch_get_global_queue(0, 0), ^{ sleep(1); os_unfair_lock_lock(&lock); NSLog(@" second thread synchronization started "); os_unfair_lock_unlock(&lock); });Copy the code
Execution Result:
2017-11-30 15:12:39.705473+0800 LockDemo[92422:2104479] first thread synchronization operation started 2017-11-30 15:12:39.705473+0800 705820+0800 LockDemo[92422:2104478] The synchronization operation of the second thread has startedCopy the code
conclusion
- Synchronized: applicable to a few threads, the task is not large multithreading lock
- NSLock: Performance is not bad, but I don’t think many people use it.
- Dispatch_semaphore_t: uses signals for locking, high performance similar to OSSpinLock.
- NSConditionLock: used when multiple threads are working on communication proposals for different tasks.
- NSRecursiveLock: Good performance, use scenarios are limited to recursion.
- POSIX(Pthread_MUtex) : the low-level API of THE C language. It is recommended to use complex multithreading and can also encapsulate its own multithreading.
- Os_unfair_lock is used instead of OSSpinLock.