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

  • nonatomicIt’s not going to generategetter,setterMethod add synchronization lock (non-atomic)
  • atomicIt’s going to be generatedgettersetterAdd 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.

validationatomicNon-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,atomicRead-write security alone does not make thread-safe, and a deeper locking mechanism is needed to make thread-safe.
  • It’s used in iOS developmentnonatomicProperty, because of the overhead of using synchronization locks in iOS, which can cause performance problems, but in Mac OS X applications, useatomicProperties 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
  • NSLockObject lock
  • NSRecursiveLockRecursive locking
  • NSConditionLockConditions for the lock
  • pthread_mutexMutex (C)
  • dispatch_semaphoreSemaphore implementation locking (GCD)
  • OSSpinLockspinlocks


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 usedselfIs the unique identifier of the lock. It is mutually exclusive only if the identifiers are the sameselfSwitch 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
  • @synchronizedThe 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,@synchronizedBlock 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

  • NSLockA simple mutex is implemented in. throughNSLockingThe protocol defineslockunlockMethods.
@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
  • NSLockClasses have also been addedtryLocklockBeforeDate: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 anddispatch_semaphore_tSimilar, 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_tThe structure of the. usepthread_mutex_lockpthread_mutex_unlockFunction. callpthread_mutex_destroyTo 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 andNSConditionThe same conditional control that initializes the mutex is used simultaneouslypthread_cond_initTo 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.