This post was originally posted on my blog

preface

There are a lot of locks in iOS, so how do you use them? This article shares 13 locking schemes. This is a long article with a total of ten thousand words. The code is available on Github.

  • OSSpinLockspinlocks
  • os_unfair_lockThe mutex
  • pthread_mutexRecursive locking
  • pthread_mutexConditions for the lock
  • dispatch_semaphoreA semaphore
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized
  • dispatch_barrier_asyncThe fence
  • dispatch_groupScheduling group

Performance comparison: Borrow an image from ibireme

You can see that dispatch_semaphore and pthread_mutex have the highest performance after OSSpinLock. Now that Apple has optimized the performance of pthread_mutex in the new OS, it doesn’t look that far behind OSSpinLock

GNUstep, a project of the GNU Project, is an open-source re-implementation of the OC library in Cocoa

GNUstep source code address

Although GNUstep is not an official source code for Apple, it is a good reference

spinlocks

Wikipedia’s description of spin locks

A spin lock is a type of lock used in computer science for multi-threaded synchronization in which threads repeatedly check whether the lock variable is available. Because the thread keeps executing during this process, it is a busy wait. Once a spin lock is acquired, the thread holds it until it explicitly releases the spin lock.

Spin-locks avoid the scheduling overhead of the process context and are therefore effective in situations where threads block only for short periods of time. So operating system implementations tend to use spin locks in many places. The Lightweight SRW Lock provided by the Windows operating system uses spin locks internally. Obviously, the CPU is not suitable for using A spin lock, in this case, the CPU is the CPU mononuclear single-threaded, because, at the same time only one thread is in running state, assume that A running thread found unable to get the lock, can only wait for to unlock, but because A themselves don’t hang up, so the thread holding the lock B there is no way to enter the running state, A can be scheduled only after the time slice allocated to A by the operating system is used up. Using spin locks in this case can be expensive.

The mutex

Wikipedia’s description of mutex

Mutex (English: Mutual Exclusion, abbreviated Mutex) is a mechanism used in multithreaded programming to prevent two threads from simultaneously reading and writing on the same common resource, such as a global variable. This is done by slicing code into critical sections. A critical region is a piece of code that accesses a common resource, not a mechanism or algorithm. A program, process, or thread can have multiple critical regions, but mutex is not always applied.

Examples of resources that need this mechanism are flags, queues, counters, interrupt handlers, and so on for passing data, synchronizing state, and so on between multiple lines of code running in parallel. Maintaining the synchronization, consistency, and integrity of these resources is difficult because a thread can be suspended (sleep) or resumed (wake up) at any time.

For example, a piece of code (a) is modifying a piece of data step by step. At this point, another thread (b) wakes up for some reason. If B tries to read the data that A is modifying at this time, and A happens to have not finished the whole modification process, the state of this data will be in a great uncertain state at this time, and the data read will certainly be problematic. The worse case is that B also writes data to this area, which will lead to uncontrollable consequences. Therefore, data shared between multiple threads must be protected. The way to do this is to ensure that only one critical region is running at a time, while the rest of the critical region, whether read or write, must be suspended and denied a chance to run.

Read-write lock

Wikipedia’s description of mutex

Read/write lock is a synchronization mechanism for concurrent control of computer programs. It is also known as “shared-mutex lock” or multi-reader-singleton lock. Multi-reader locks, “push locks”) are used to solve read-write problems. Read operations can be concurrently reentrant, and write operations are mutually exclusive.

Read/write locks are usually implemented with mutex, condition variables, and semaphores.

Read/write locks can have different operation-mode priorities:

  • Read operation first: maximum concurrency is allowed, but write operations may starve.
  • Write priority: Waiting writes acquire locks as soon as all read operations that have started are complete. The internal implementation requires two mutex locks.
  • No priority is specified

A semaphore

Wikipedia’s description of semaphores

A semaphore (English: semaphore), also known as a semaphore, is a synchronization object used to hold a count between 0 and a specified maximum value. When a thread completes a wait for the semaphore, the count is reduced by one; When the thread completes a release of the Semaphore object, the count is incremented by one. Signaled from the object’s waiting state when the count reaches 0, the thread waits until the object becomes signaled. Signaled objects are signaled to be greater than 0. Signaled state = 0.

Semaphore objects are a way to control a shared resource that supports only a limited number of users and do not require busy waiting.

The concept of semaphores was invented by Dutch computer scientist Edsger W. Dijkstra and is widely used in different operating systems. In the system, each process is given a semaphore, which represents the current state of each process. The process without control is forced to stop at a specific place and wait for a signal to continue. If the semaphore is an arbitrary integer, it is usually called a Counting semaphore or a general semaphore. If the semaphore has only binary zeros or ones, it is called a binary semaphore. In Linux, the binary semaphore is also called a Mutex.

scenario

Use the classic save and withdraw case. So let’s say we have $100 in our account, and we put $10 in every time we deposit, and we take $20 out every time we withdraw. Five deposits, five withdrawals. So you should end up with 50 dollars. If we access existence and withdrawal in different threads without locking, this can cause problems.

*/ - (void)moneyTest {self. Money = 100; dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_async(queue, ^{for(int i = 0; i < 5; i++) { [self __saveMoney]; }}); dispatch_async(queue, ^{for(int i = 0; i < 5; i++) { [self __drawMoney]; }}); } /** save money */ - (void)__saveMoney {int oldMoney = self. Money; sleep(.2); oldMoney += 10; self.money = oldMoney; NSLog(@"存10元,还剩%d元 - %@", oldMoney, [NSThread currentThread]); } /** get money */ - (void)__drawMoney {int oldMoney = self.money; sleep(.2); oldMoney -= 20; self.money = oldMoney; NSLog(@"Take 20 dollars, you have %d dollars - %@.", oldMoney, [NSThread currentThread]);
}

Copy the code

The output is

Ios-lockdemo [21005:249636] - <NSThread: 0x600001b4F540 >{number = 4, name = (null)} ios-lockDemo [21005:249637] 0x600001B79840 >{number = 3, name = (null)} ios-lockDemo [21005:249636] 0x600001B4F540 >{number = 4, name = (null)} ios-lockDemo [21005:249636] 0x600001b4F540 >{number = 4, name = (null)} ios-lockDemo [21005:249637] 0x600001B79840 >{number = 3, name = (null)} ios-lockDemo [21005:249637] 0x600001B79840 >{number = 3, name = (null)} ios-lockDemo [21005:249636] 0x600001b4F540 >{number = 4, name = (null)} ios-lockDemo [21005:249637] 0x600001B79840 >{number = 3, name = (null)} ios-lockDemo [21005:249636] 0x600001b4F540 >{number = 4, name = (null)} ios-lockDemo [21005:249637] 0x600001b79840>{number = 3, name = (null)}Copy the code

The results were clearly not what was expected

And that’s because, normally, when you come in and cancel your deposit, after you put in 10 dollars, you have 110 dollars left, and then you withdraw 20 dollars, and you have 90 dollars left. But when we’re operating on different threads at the same time, it might lead to a situation where the person who is saving money is coming to withdraw money. That’s 10 dollars before you put it in. I’m going to take the money and I’m going to get the current amount of money, because I’m depositing 10 dollars, I’m not done depositing 10 dollars, so when I take the money out, it’s 100 dollars, and when I take the 20 dollars out, I put the 10 dollars in, and then I take the 20 dollars out. They give us 100-20 = 80 yuan, and then actually 100+10-20 = 90 yuan. In this way, the data is distorted.

How to solve:

To solve this problem, thread locks are required. When you deposit money, lock it first, then unlock it when you’re done. The same is true for withdrawals, so that the data is consistent.

OSSpinLockspinlocks

  • OSSpinLockCalled a “spin lock,” the thread waiting for the lock stays in busy-wait state, occupying CPU resources
  • It is no longer secure and priority inversion may occur
  • The header file needs to be imported#import <libkern/OSAtomic.h>

use

OSSpinLock lock = OS_SPINLOCK_INIT; // Try locking (if there is no need to wait, just lock, returntrue. If you need to wait, leave the lock unlocked and returnfalse) BOOL res = OSSpinLockTry(lock); / / lock OSSpinLockLock (lock); / / unlock OSSpinLockUnlock (lock);Copy the code

YZOSSpinLock inherits YZBaseLock and locks it before each deposit and withdrawal, and unlocks it after each deposit and withdrawal.

#import "YZOSSpinLock.h"
#import <libkern/OSAtomic.h>

@interface YZOSSpinLock()
@property (assign, nonatomic) OSSpinLock moneyLock;
@end

@implementation YZOSSpinLock

- (instancetype)init
{
    if (self = [super init]) {
        self.moneyLock = OS_SPINLOCK_INIT;
    }
    return self;
}

- (void)__drawMoney
{
    OSSpinLockLock(&_moneyLock);
    
    [super __drawMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}

- (void)__saveMoney
{
    OSSpinLockLock(&_moneyLock);
    
    [super __saveMoney];
    
    OSSpinLockUnlock(&_moneyLock);
}


@end
Copy the code

The output is

Ios-lockdemo [22496:265962] select $20, $80 - <NSThread: 0x600003Add800 >{number = 3, name = (null)} ios-lockDemo [22496:265962] 0x600003Add800 >{number = 3, name = (null)} ios-lockDemo [22496:265962] 0x600003Add800 >{number = 3, name = (null)} ios-lockDemo [22496:265962] 0x600003Add800 >{number = 3, name = (null)} ios-lockDemo [22496:265962] 0x600003Add800 >{number = 3, name = (null)} ios-lockDemo [22496:265961] 0x600003AecD00 >{number = 4, name = (null)} ios-lockDemo [22496:265961] 0x600003Aecd00 >{number = 4, name = (null)} ios-lockDemo [22496:265961] 0x600003Aecd00 >{number = 4, name = (null)} ios-lockDemo [22496:265961] 0x600003AecD00 >{number = 4, name = (null)} ios-lockDemo [22496:265961] 0x600003aecd00>{number = 4, name = (null)}Copy the code

From the output, it can ensure thread safety and data is not corrupted. But OSSpinLock is no longer safe.

Assembly tracking

The second time you interrupt the lock, the lock is already locked, at this time look at the lock assembly code

Debug->Debug Worlflow->Always Show Disassembly

whyOSSpinLockNo longer safe

For information on why OSSpinLock is no longer secure, see the article OSSpinLock is No longer secure

Here is a summary of the main content

If a low-priority thread acquires a lock and accesses a shared resource, and a high-priority thread attempts to acquire the lock, it will be in a busy state of spin lock and consume a lot of CPU. Priority inversion occurs when a low-priority thread cannot compete with a high-priority thread for CPU time, causing the task to be delayed and unable to release the lock. This isn’t just a theoretical problem, developers have encountered this problem so many times that Apple engineers disabled OSSpinLock.

conclusion

  • All types of spin locks in iOS will no longer be available unless the developer can ensure that all threads accessing the lock are of the same priority.

os_unfair_lockThe mutex

Os_unfair_lock is used to replace unsafe osspinlocks, and is only supported in iOS10. Threads waiting for the os_unfair_lock lock are dormant

#import < OS /lock.h>

use

Os_unfair_lock LOCK = OS_UNFAIR_LOCK_INIT; // Try locking (if there is no need to wait, just lock, returntrue. If you need to wait, leave the lock unlocked and returnfalse) BOOL res = os_unfair_lock_trylock(&lock); / / lock os_unfair_lock_lock (& lock); / / unlock os_unfair_lock_unlock (& lock);Copy the code

YZUnfairLock inherits YZBaseLock, which locks the device before each deposit and withdrawal, and unlocks the device after each deposit and withdrawal.


#import "YZUnfairLock.h"
#import <os/lock.h>

@interface YZUnfairLock()
@property (nonatomic ,assign) os_unfair_lock moneyLock;

@end

@implementation YZUnfairLock
- (instancetype)init
{
    
    if (self = [super init]) {
        self.moneyLock = OS_UNFAIR_LOCK_INIT;
    }
    return self;
}

- (void)__saveMoney
{
    os_unfair_lock_lock(&_moneyLock);
    
    [super __saveMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

- (void)__drawMoney
{
    os_unfair_lock_lock(&_moneyLock);
    
    [super __drawMoney];
    
    os_unfair_lock_unlock(&_moneyLock);
}

@end
Copy the code

Assembly tracking

The second time you interrupt the lock, the lock is already locked, at this time look at the lock assembly code

Debug->Debug Worlflow->Always Show Disassembly

The breakpoint is tracked down and you see that eventually when you get to Syscall, the breakpoint fails. This is because Syscall calls a kernel function that puts the threads into hibernation and no longer consumes CPU resources. Os_unfair_lock is a mutex lock.

pthread_mutexThe mutex

  • Mutex is called a mutex, and the thread waiting for the lock will sleep
  • #import

use

// Initialize the attribute pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); // Initialize lock pthread_mutex_init(mutex, &attr); Pthread_mutexattr_destroy (&attr);Copy the code

There are four types of locks

#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2 //
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
Copy the code

When the type is PTHREAD_MUTEX_DEFAULT, the value is null

For example, the use above can be directly equivalent to

pthread_mutex_init(mutex, NULL); // Null, equivalent to PTHREAD_MUTEX_DEFAULTCopy the code

YZMutexLock inherits YZBaseLock, which locks the device before each deposit and withdrawal, and unlocks the device after each deposit and withdrawal.

#import "YZMutexLock.h"
#import <pthread.h>@interface YZMutexLock() @property (assign, nonatomic) pthread_mutex_t moneyMutexLock; @end@implementation YZMutexLock @param mutex lock */ - (void)__initMutexLock:(pthread_mutex_t *)mutex{// static initialization // pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // Initialize the attribute pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT); // Initialize lock pthread_mutex_init(mutex, &attr); Pthread_mutexattr_destroy (&attr); //pthread_mutex_init(mutex, NULL); - (instanceType)init {self = [super init];if (self) {
        [self __initMutexLock:&_moneyMutexLock];
    }
    returnself; } - (void)__saveMoney { pthread_mutex_lock(&_moneyMutexLock); [super __saveMoney]; pthread_mutex_unlock(&_moneyMutexLock); } - (void)__drawMoney { pthread_mutex_lock(&_moneyMutexLock); [super __drawMoney]; pthread_mutex_unlock(&_moneyMutexLock); } - (void)dealloc {//delloc requires the lock pthread_mutex_destroy(&_moneymutexlock); } @endCopy the code

It’s okay to see the output. Threads are safe.

Ios-lockdemo [2573:45093] - <NSThread: 0x600003EbbB80 >{number = 3, name = (null)} ios-lockDemo [2573:45093] 0x600003EbbB80 >{number = 3, name = (null)} ios-lockDemo [2573:45093] 0x600003EbbB80 >{number = 3, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45093] 0x600003EbbB80 >{number = 3, name = (null)} ios-lockDemo [2573:45093] 0x600003ebbb80>{number = 3, name = (null)}Copy the code

pthread_mutexRecursive locking

  • In addition to “mutex”, Mutex also has recursive locks
  • #import

use

// Initialize the attribute pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // Initialize lock pthread_mutex_init(mutex, &attr); Pthread_mutexattr_destroy (&attr);Copy the code

There are four types of locks

#define PTHREAD_MUTEX_NORMAL 0
#define PTHREAD_MUTEX_ERRORCHECK 1
#define PTHREAD_MUTEX_RECURSIVE 2 //
#define PTHREAD_MUTEX_DEFAULT PTHREAD_MUTEX_NORMAL
Copy the code

eg:

YZMutexRecursiveLock inherits YZBaseLock, otherTest recursively locks

#import "YZMutexRecursiveLock.h"
#import <pthread.h>@interface YZMutexRecursiveLock() @property (assign, nonatomic) pthread_mutex_t MutexLock; @end@implementation recursivelock @param mutex lock */ - (void)__initMutexLock:(pthread_mutex_t *)mutex{ Pthread_mutexattr_t attr; pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // Initialize lock pthread_mutex_init(mutex, &attr); Pthread_mutexattr_destroy (&attr); } - (void)otherTest{// If (void)otherTest; Pthread_mutex_lock (&_mutexlock); NSLog(@Lock "% s",__func__);
    static int count = 0;
    if (count < 5) {
        count++;
        [self otherTest];
    }
     NSLog(@"Unlock % s",__func__);
    pthread_mutex_unlock(&_MutexLock);
    
}
- (instancetype)init
{
    self = [super init];
    if (self) {
        [self __initMutexLock:&_MutexLock];
    }
    returnself; } - (void)dealloc {//delloc: pthread_mutex_destroy(&_mutexlock); } @endCopy the code

When you call

YZBaseLock *lock = [[YZMutexRecursiveLock alloc] init];
[lock otherTest];
Copy the code

The output is:

Ios-lockdemo [7358:129676] lock -[YZMutexRecursiveLock otherTest] ios-lockDemo [7358:129676] lock -[YZMutexRecursiveLock OtherTest] ios-lockDemo [7358:129676] Lock -[YZMutexRecursiveLock otherTest] ios-lockDemo [7358:129676] lock -[YZMutexRecursiveLock otherTest] iOS-LockDemo[7358:129676] Ios-lockdemo [7358:129676] Lock -[YZMutexRecursiveLock otherTest] ios-lockDemo [7358:129676] unlock -[YZMutexRecursiveLock OtherTest] ios-lockDemo [7358:129676] Unlock -[YZMutexRecursiveLock otherTest] ios-lockDemo [7358:129676] unlock [YZMutexRecursiveLock otherTest] iOS-LockDemo[7358:129676] unlock -[YZMutexRecursiveLock otherTest] Ios-lockdemo [7358:129676] Unlock -[YZMutexRecursiveLock otherTest] ios-lockDemo [7358:129676] unlock -[YZMutexRecursiveLock otherTest]Copy the code

It can be seen from the result that five consecutive locks are recursively locked each time. And then when you unlock it, layer by layer.

pthread_mutexConditions for the lock

  • Mutex has “mutex”, “recursive locks”, and “recursive locks”
  • #import

use

Producer consumer

To demonstrate the effect of conditional locking, we use producer consumers to demonstrate the effect. For the design pattern of producer consumers, see my previous article on iOS Design Patterns (2) Producer-consumer, which used semaphores. This article uses the pthread_mutex conditional lock.

code

There are three properties

@property (assign, nonatomic) pthread_mutex_t mutex; // lock @property (assign, nonatomic) pthread_cond_t cond; @property (strong, nonatomic) NSMutableArray *data; / / the data sourceCopy the code

Initialize the

// Initialize the attribute pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // Initialize lock pthread_mutex_init(&_mutex, &attr); Pthread_mutexattr_destroy (&attr); Pthread_cond_init (&_cond, NULL); self.data = [NSMutableArray array];Copy the code

eg:

YZMutexCondLock inherits YZBaseLock and tests in otherTest

// // yzmutexCondlock. m // ios-lockdemo // // Created by Eagle on 2018/8/13. // Copyright © 2018 Yongzhen. All rights reserved. //#import "YZMutexCondLock.h"
#import <pthread.h>@interface YZMutexCondLock() @property (assign, nonatomic) pthread_mutex_t mutex; // lock @property (assign, nonatomic) pthread_cond_t cond; @property (strong, nonatomic) NSMutableArray *data; @end@implementation YZMutexCondLock - (instanceType)init {if(self = [super init]) {// Initialize the attribute pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); // Initialize lock pthread_mutex_init(&_mutex, &attr); Pthread_mutexattr_destroy (&attr); Pthread_cond_init (&_cond, NULL); self.data = [NSMutableArray array]; }returnself; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start]; } // producer-consumer mode // thread 1 // Remove elements from array - (void)__remove {pthread_mutex_lock(&_mutex);if(self.data.count == 0) {// If the data is empty, it will wait (go to sleep, release mutex lock, wake up, lock mutex again)"__remove - 等待");
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@"Deleted element"); pthread_mutex_unlock(&_mutex); } // thread 2 // add elements to array - (void)__add {pthread_mutex_lock(&_mutex); sleep(1); [self.data addObject:@"Test"];
    NSLog(@"Added elements"); // Activate a thread waiting for the condition pthread_cond_signal(&_cond); // Activate all threads waiting for the condition // pthread_cond_broadcast(&_cond); pthread_mutex_unlock(&_mutex); } - (void)dealloc { pthread_mutex_destroy(&_mutex); pthread_cond_destroy(&_cond); } @endCopy the code

When you call

YZBaseLock *lock = [[YZMutexCondLock alloc] init];
[lock otherTest];
Copy the code

The output is:

2018-08-13 17:09:31.643902+0800 ios-lockdemo [26733:229374] __remove-wait 2018-08-13 17:09:32.648587+0800 Ios-lockdemo [26733:229375] added element 2018-08-13 17:09:32.648894+0800 ios-lockdemo [26733:229374] deleted elementCopy the code

After printing __remove-wait, wait a second, add the element, release the lock, and then delete the element.

NSLock lock

  • NSLockIs themutexCommon lock encapsulation

api

NSLocking Lock unlock,

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end
Copy the code

NSLock complies with this protocol, the lock can be used directly, in addition, there are tryLock and lockBeforeDate

- (void)lock; // unlock - (void)unlock; // Unlock - (BOOL)tryLock; LockBeforeDate :(NSDate *) - (BOOL)lockBeforeDate:(NSDate *)limit; // Try to lock within a given amount of time, return YES if the lock is successfully locked, and return NO if the lock has not been locked after that time.Copy the code

use

YZNSLock inherits YZBaseLock and locks it every time you deposit or withdraw money, and unlocks it every time you deposit or withdraw money.

#import "YZNSLock.h"

@interface YZNSLock()
@property (nonatomic,strong) NSLock *lock;
@end


@implementation YZNSLock

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.lock =[[NSLock alloc] init];
    }
    return self;
}
- (void)__saveMoney
{
    [self.lock lock];
    [super __saveMoney];
    
    [self.lock unlock];
}

- (void)__drawMoney
{
    [self.lock lock];
    
    [super __drawMoney];
    
    [self.lock unlock];
}
- (void)dealloc
{
   
}


@end
Copy the code

The output is

Ios-lockdemo [39175:397286] - <NSThread: 0x600000AF2740 >{number = 3, name = (null)} ios-lockDemo [39175:397286] 0x600000AF2740 >{number = 3, name = (null)} ios-lockDemo [39175:397286] 0x600000AF2740 >{number = 3, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397286] 0x600000AF2740 >{number = 3, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397286] 0x600000af2740>{number = 3, name = (null)}Copy the code

From the output, it can ensure thread safety and data is not corrupted.

NSLockIs themutexCommon lock encapsulation

If you want to prove that NSLock encapsulates a mutex normal lock, there are two ways to do it

  • Assembly analysis
    • For assembly analysis, you can interrupt the point to follow in and eventually find the callmutex, because lock is called msgSend, assembly code is more complex, readers interested in their own verification.
  • GNUstep
    • GNUstep source nslock. m in the following code
+ (void) initialize
{
  static BOOL	beenHere = NO;

  if(beenHere == NO) { beenHere = YES; pthread_mutexattr_init(&attr_normal); pthread_mutexattr_settype(&attr_normal, PTHREAD_MUTEX_NORMAL); pthread_mutexattr_init(&attr_reporting); pthread_mutexattr_settype(&attr_reporting, PTHREAD_MUTEX_ERRORCHECK); pthread_mutexattr_init(&attr_recursive); pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE); . Other code}Copy the code

NSRecursiveLock lock

  • NSRecursiveLock encapsulates a mutex recursive lock, and has the same API as NSLock

api

NSLocking Lock unlock,

@protocol NSLocking

- (void)lock;
- (void)unlock;

@end
Copy the code

NSRecursiveLock complies with this protocol and can be used directly, as well as tryLock and lockBeforeDate

- (void)lock; // unlock - (void)unlock; // Unlock - (BOOL)tryLock; LockBeforeDate :(NSDate *) - (BOOL)lockBeforeDate:(NSDate *)limit; // Try to lock within a given amount of time, return YES if the lock is successfully locked, and return NO if the lock has not been locked after that time.Copy the code

use

YZNSRecursiveLock inherits YZBaseLock and locks it before each deposit and withdrawal.

#import "YZNSRecursiveLock.h"

@interface YZNSRecursiveLock()
@property (nonatomic,strong) NSRecursiveLock *lock;
@end


@implementation YZNSRecursiveLock

- (instancetype)init
{
    self = [super init];
    if (self) {
        self.lock =[[NSRecursiveLock alloc] init];
    }
    return self;
}
- (void)__saveMoney
{
    [self.lock lock];
    [super __saveMoney];
    
    [self.lock unlock];
}

- (void)__drawMoney
{
    [self.lock lock];
    
    [super __drawMoney];
    
    [self.lock unlock];
}
- (void)dealloc
{
    
}
@end
Copy the code

The output is

Ios-lockdemo [39175:397286] - <NSThread: 0x600000AF2740 >{number = 3, name = (null)} ios-lockDemo [39175:397286] 0x600000AF2740 >{number = 3, name = (null)} ios-lockDemo [39175:397286] 0x600000AF2740 >{number = 3, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397286] 0x600000AF2740 >{number = 3, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397287] 0x600000AE66C0 >{number = 4, name = (null)} ios-lockDemo [39175:397286] 0x600000af2740>{number = 3, name = (null)}Copy the code

From the output, it can ensure thread safety and data is not corrupted.

YZNSRecursiveLockIs themutexRecursive lock encapsulation

There are two ways to prove that NSRecursiveLock encapsulates a mutex plain lock

  • Assembly analysis
    • For assembly analysis, you can interrupt the point to follow in and eventually find the callmutex, because lock is called msgSend, assembly code is more complex, readers interested in their own verification.
  • GNUstep
    • GNUstep source NSLock. M NSRecursiveLock has the following code
//NSRecursiveLock - (id) init {if(nil ! = (self = [super init])) {if(0! = pthread_mutex_init(&_mutex, &attr_recursive)) { DESTROY(self); }}returnself; } // attr_recursive initializes pthread_mutexattr_init(&attr_recursive); pthread_mutexattr_settype(&attr_recursive, PTHREAD_MUTEX_RECURSIVE);Copy the code

NSConditionConditions for the lock

  • NSCondition is the encapsulation of mutex and COND

use

Producer consumer

Use the producer-consumer pattern as with YZMutexCondLock above

api

- (void)wait; // Wait - (BOOL)waitUntilDate:(NSDate *)limit; // Wait until the given time - (void)signal; // Activate a thread waiting for the condition - (void)broadcast; // Activate all threads waiting for the conditionCopy the code

code

Initialize the

Self. Condition = [[NSCondition alloc] init];Copy the code

eg:

YZNSCondition inherits YZBaseLock and conducts tests in otherTest

#import "YZNSCondition.h"

@interface YZNSCondition()
@property (strong, nonatomic) NSCondition *condition;
@property (strong, nonatomic) NSMutableArray *data;
@end

@implementation YZNSCondition

- (instancetype)init
{
    if (self = [super init]) {
        self.condition = [[NSCondition alloc] init];
        self.data = [NSMutableArray array];
    }
    returnself; } - (void)otherTest { [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start]; [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start]; } // producer-consumer mode // thread 1 // Delete elements from array - (void)__remove {[self.condition lock];if(self.data.count == 0) {// If the data is empty, wait (enter sleep, release lock, wake up, lock mutex again) NSLog(@)"__remove - 等待");
        [self.condition wait];
    }
    
    [self.data removeLastObject];
    NSLog(@"Deleted element"); [self.condition unlock]; } // thread 2 // Add elements to array - (void)__add {[self.condition lock]; sleep(1); [self.data addObject:@"Test"];
    NSLog(@"Added elements"); // Activate a thread waiting for the condition [self.condition signal]; // activate all threads waiting for the condition // [self.condition broadcast]; [self.condition unlock]; } @endCopy the code

When you call

YZBaseLock *lock = [[YZNSCondition alloc] init];
[lock otherTest];
Copy the code

The output is:

2018-08-13 18:09:31.643902+0800 ios-lockdemo [26733:229374] __remove-wait 2018-08-13 18:09:32.648587+0800 Ios-lockdemo [26733:229375] added element 2018-08-13 18:09:32.648894+0800 ios-lockdemo [26733:229374] deleted elementCopy the code

After printing __remove-wait, wait a second, add the element, release the lock, and then delete the element.

NSConditionLock

NSConditionLock is a further encapsulation of NSCondition, and you can set specific conditional values

API

There are mainly the following apis, as the name implies, a look at the name.

- (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;

Copy the code

use

Initialize the

NSConditionLock *lock = [[NSConditionLock alloc] initWithCondition:0]; // When condition is 2, lock [lock whencondition :2]; // Unlock [lock unlockWithCondition:3];Copy the code

eg:

YZNSConditionLock inherits YZBaseLock and otherTest

#import "YZNSConditionLock.h"@interface conditionlock () @end@implementation YZNSConditionLock - (void)otherTest {// NSConditionLock *lock =  [[NSConditionLock alloc] initWithCondition:0]; // thread 1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[lock lockWhenCondition:2]; NSLog(@Thread 1 "");
        sleep(2);
        NSLog(@"Thread 1 unlocked successfully"); [lock unlockWithCondition:3]; }); // thread 2 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[lock lockWhenCondition:0]; NSLog(@Thread 2 "");
        sleep(3);
        NSLog(@"Thread 2 unlocked successfully"); [lock unlockWithCondition:1]; }); // thread 3 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[lock lockWhenCondition:3]; NSLog(@"Thread 3");
        sleep(3);
        NSLog(@"Thread 3 unlocked successfully"); [lock unlockWithCondition:4]; }); // thread 4 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{[lock lockWhenCondition:1]; NSLog(@Threads "4");
        sleep(2);
        NSLog(@"Thread 4 unlocked successfully");
        [lock unlockWithCondition:2];
    });
    
}
@end

Copy the code

When you call

YZBaseLock *lock = [[YZNSConditionLock alloc] init];
[lock otherTest];
Copy the code

The output is:

2018-08-14 15:37:10.854390+0800 ios-lockDemo [11810:143479] Thread 2 2018-08-14 15:37:10.854390+0800 Ios-lockdemo [11810:143479] Thread 2 is unlocked successfully. 2018-08-14 15:37:10.854703+0800 ios-lockDemo [11810:143478] Thread 4 2018-08-14 15:37:12.856226+0800 ios-lockdemo [11810:143478] Thread 4 unlocked successfully 2018-08-14 15:37:12.856487+0800 ios-lockdemo [11810:143476] thread 1 2018-08-14 15:37:14.860596+0800 ios-lockDemo [11810:143476] Thread 1 unlocked successfully 2018-08-14 15:37:14.860791+0800 Ios-lockdemo [11810:143477] Thread 3 2018-08-14 15:37:17.864072+0800 ios-lockDemo [11810:143477] Thread 3 is unlocked successfullyCopy the code

According to the result, NSConditionLock can be fully unlocked by conditional value.

dispatch_semaphoreA semaphore

  • A semaphore is called a semaphore.
  • The initial value of a semaphore that can be used to control the maximum number of concurrent accesses by a thread
  • The initial value of the semaphore is 1, which means that only one thread is allowed to access the resource at the same time to ensure thread synchronization

For details about semaphores, please refer to GCD -dispatch_semaphore_t and the actual application of semaphores

Semaphore principle

dispatch_semaphore_create(long value); // Dispatch_semaphore_signal (dispatch_semaphore_t deem); // Dispatch_semaphore_wait (dispatch_semaphore_t dsema, dispatch_time_t timeout); // Wait for semaphoreCopy the code

Dispatch_semaphore_create (long value) creates a semaphore of type dispatch_semaphore_.

Dispatch_semaphore_wait (dispatch_semaphore_t dSEMA, dispatch_time_t timeout) Wait for signals. If the semaphore value is 0, the function will wait, that is, not return (equivalent to blocking the current thread), until the value of the semaphore it is waiting for is greater than or equal to 1, the function will subtract the semaphore value by one, and then return.

Dispatch_semaphore_signal (dispatch_semaphore_t deem). This increments the value of the semaphore by one.

Usually the waiting semaphore and the sending semaphore functions come in pairs. When a task is executed concurrently, the dispatch_semaphore_WAIT function is used to wait (block) before the current task is executed. After the last task is executed, the semaphore is sent via dispatch_semaphore_signal function (increment the semaphore value by 1). The dispatch_semaphore_WAIT function determines that the semaphore value is greater than or equal to 1 and then reduces the semaphore value by 1. Then the current task can be executed. The dispatch_semaphore_signal function sends the semaphore (incremented by 1) to notify the next task…… In this way, through semaphores, tasks in concurrent queues are executed synchronously.

use

I’m going to initialize it by setting “1”, and then I’m going to call “dispatch_semaphore_wait” every time I make a deposit, I’m going to call “dispatch_semaphore_signal” after I make a deposit

eg:

YZSemaphore inherits YZBaseLock and tests in otherTest

#import "YZSemaphore.h"

@interface YZSemaphore()
@property (strong, nonatomic) dispatch_semaphore_t moneySemaphore;
@end

@implementation YZSemaphore
- (instancetype)init
{
    if (self = [super init]) {
        self.moneySemaphore = dispatch_semaphore_create(1);
    }
    return self;
}

- (void)__drawMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    
    [super __drawMoney];
    
    dispatch_semaphore_signal(self.moneySemaphore);
}

- (void)__saveMoney
{
    dispatch_semaphore_wait(self.moneySemaphore, DISPATCH_TIME_FOREVER);
    
    [super __saveMoney];
    
    dispatch_semaphore_signal(self.moneySemaphore);
}
Copy the code

When you make an external call,

YZBaseLock *lock = [[YZSemaphore alloc] init];
[lock moneyTest];
Copy the code

The output

Ios-lockdemo [13500:171371] - <NSThread: 0x600001CA9840 >{number = 3, name = (null)} ios-lockDemo [135005:171369] 0x600001C960C0 >{number = 4, name = (null)} ios-lockDemo [135005:171371] 0x600001CA9840 >{number = 3, name = (null)} ios-lockDemo [135005:171369] 0x600001C960C0 >{number = 4, name = (null)} ios-lockDemo [13500:171371] 0x600001CA9840 >{number = 3, name = (null)} ios-lockDemo [135005:171369] 0x600001C960C0 >{number = 4, name = (null)} ios-lockDemo [13500:171371] 0x600001CA9840 >{number = 3, name = (null)} ios-lockDemo [135005:171369] 0x600001C960C0 >{number = 4, name = (null)} ios-lockDemo [135005:171371] 0x600001CA9840 >{number = 3, name = (null)} ios-lockDemo [135005:171369] 0x600001c960c0>{number = 4, name = (null)}Copy the code

The results show that it can guarantee the safe read and write of multithreaded data.

Use two

The semaphore can also control the number of threads, such as setting a maximum of three threads during initialization

#import "YZSemaphore.h"

@interface YZSemaphore()
@property (strong, nonatomic) dispatch_semaphore_t semaphore;
@end

@implementation YZSemaphore
- (instancetype)init
{
    if (self = [super init]) {
        self.semaphore = dispatch_semaphore_create(3);
    }
    return self;
}

- (void)otherTest
{
    for (int i = 0; i < 20; i++) {
        [[[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil] start]; }} // thread 10, 7, 6, 9, 8 - (void)test{// If the semaphore value is >0, then the semaphore value is decreased by 1, then continue to execute the code // If the semaphore value is <= 0, then the semaphore value is decreased by 1. Then proceed to dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); sleep(2); NSLog(@"test - %@", [NSThread currentThread]); // dispatch_semaphore_signal(self.semaphore); } @endCopy the code

The output of calling otherTest is

The 2018-08-14 16:38:56. 489121 + 0800 iOS - LockDemo (14002-180654)test- <NSThread: 0x600003A938C0 >{number = 3, name = (null)} 2018-08-14 16:38:56.492100+0800 ios-lockdemo [14002:180655]test- <NSThread: 0x600003A93900 >{number = 4, name = (null)} 2018-08-14 16:38:56.492281+0800 ios-lockdemo [14002:180656]test- <NSThread: 0x600003A93940 >{number = 5, name = (null)} 2018-08-14 16:38:58.497578+0800 ios-lockdemo [14002:180657]test- <NSThread: 0x600003A93980 >{number = 6, name = (null)} 2018-08-14 16:38:58.499225+0800 ios-lockdemo [14002:180658]test- <NSThread: 0x600003A8E640 >{number = 7, name = (null)} 2018-08-14 16:38:58.549633+0800 ios-lockDemo [14002:180659]test- <NSThread: 0x600003A93a00 >{number = 8, name = (null)} 2018-08-14 16:39:00.499672+0800 ios-lockdemo [14002:180660]test- <NSThread: 0x600003AA6CC0 >{number = 9, name = (null)} 2018-08-14 16:39:00.499799+0800 ios-lockdemo [14002:180661]test- <NSThread: 0x600003AA6EC0 >{number = 10, name = (null)} 2018-08-14 16:39:00.550353+0800 ios-lockdemo [14002:180662]test- <NSThread: 0x600003AA6D80 >{number = 11, name = (null)} 2018-08-14 16:39:02.501379+0800 ios-lockdemo [14002:180663]test - <NSThread: 0x600003aa6f00>{number = 12, name = (null)}
Copy the code

According to the result, a maximum of three threads can execute each time.

synchronized

  • @synchronized is the encapsulation of a mutex recursive lock
  • Objc-sync.mm file in objC4
  • @synchronized(obj) internally generates a recursive lock corresponding to OBj, and then locks and unlocks the lock

Read more about @synchronized than you might want to know

use

@synchronized is simple to use

Also using the save ticket example above, the YZSynchronized class inherits from YZBaseLock, with the following code

#import "YZSynchronized.h"

@interface YZSynchronized()

@end

@implementation YZSynchronized
- (void)__saveMoney
{
    @synchronized (self) {
        [super __saveMoney];
    }
}

- (void)__drawMoney
{
    @synchronized (self) {
        [super __drawMoney];
    }
}
@end
Copy the code

After the call

Ios-lockdemo [2573:45093] - <NSThread: 0x600003EbbB80 >{number = 3, name = (null)} ios-lockDemo [2573:45093] 0x600003EbbB80 >{number = 3, name = (null)} ios-lockDemo [2573:45093] 0x600003EbbB80 >{number = 3, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45095] 0x600003E84880 >{number = 4, name = (null)} ios-lockDemo [2573:45093] 0x600003EbbB80 >{number = 3, name = (null)} ios-lockDemo [2573:45093] 0x600003ebbb80>{number = 3, name = (null)}Copy the code

It can be seen that the multithreaded data did not occur disorder

Source code analysis

Objc-sync.mm runtime source objc-sync.mm runtime source objc-sync.mm

int objc_sync_enter(id obj)
{
    int result = OBJC_SYNC_SUCCESS;

    if (obj) {
        SyncData* data = id2data(obj, ACQUIRE);
        assert(data);
        data->mutex.lock();
    } else {
        // @synchronized(nil) does nothing
        if (DebugNilSync) {
            _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug");
        }
        objc_sync_nil();
    }

    return result;
}


// End synchronizing on 'obj'. 
// Returns OBJC_SYNC_SUCCESS or OBJC_SYNC_NOT_OWNING_THREAD_ERROR
int objc_sync_exit(id obj)
{
    int result = OBJC_SYNC_SUCCESS;
    
    if (obj) {
        SyncData* data = id2data(obj, RELEASE); 
        if(! data) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; }else {
            bool okay = data->mutex.tryUnlock();
            if(! okay) { result = OBJC_SYNC_NOT_OWNING_THREAD_ERROR; }}}else {
        // @synchronized(nil) does nothing
    }
	

    return result;
}
Copy the code
typedef struct alignas(CacheLineSize) SyncData {
    struct SyncData* nextData;
    DisguisedPtr<objc_object> object;
    int32_t threadCount;  // number of THREADS using this block
    recursive_mutex_t mutex;
} SyncData;
Copy the code

As well as

using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>;
Copy the code

@synchronized encapsulates a mutex recursive lock. Because it is a recursive lock, the lock can be added recursively, the reader is interested in verifying themselves.

pthread_rwlockRead-write lock

Read/write lock is a synchronization mechanism for concurrent control of computer programs. It is also known as “shared-mutex lock” or multi-reader-singleton lock. Multi-reader locks, “push locks”) are used to solve read-write problems. Read operations can be concurrently reentrant, and write operations are mutually exclusive.

  • #import

use

// Initialize lock pthread_rwlock_t lock; pthread_rwlock_init(&lock, NULL); // read-lock pthread_rwlock_rdlock(&lock); // read - try to lock pthread_rwlock_tryrdlock(&lock); // write - lock pthread_rwlock_wrlock(&lock); // write - try to lock pthread_rwlock_trywrlock(&lock); / / unlock pthread_rwlock_unlock (& lock); / / destroyed pthread_rwlock_destroy (& lock);Copy the code

eg:

YZRwlock inherits YZBaseLock, otherTest, lock before each read or write, sleep for 1 second, then unlock, as shown below

#import "YZRwlock.h"
#import <pthread.h>

@interface YZRwlock()
@property (assign, nonatomic) pthread_rwlock_t lock;

@end


@implementation YZRwlock

- (instancetype)init
{
    self = [super init];
    if(self) {// Initialize the lock pthread_rwlock_init(&_lock, NULL); }return self;
}

- (void)otherTest{
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i < 3; i++) {
        dispatch_async(queue, ^{
            [self write];
            [self read];
        });
        
    }
    for (int i = 0; i < 3; i++) {
        dispatch_async(queue, ^{
            [self write];
        });
    }
}

- (void)read {
    pthread_rwlock_rdlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

- (void)write
{
    pthread_rwlock_wrlock(&_lock);
    
    sleep(1);
    NSLog(@"%s", __func__);
    
    pthread_rwlock_unlock(&_lock);
}

- (void)dealloc
{
    pthread_rwlock_destroy(&_lock);
}

@end
Copy the code

When you call

YZBaseLock *lock = [[YZRwlock alloc] init];
[lock otherTest];
Copy the code

The output is:

2018-08-15 16:07:45.753659+0800 ios-lockDemo [25457:248359] -[YZRwlock Write] 2018-08-15 16:07:46.758460+0800 Ios-lockdemo [25457:248356] -[YZRwlock write] 2018-08-15 16:07:47.763705+0800 ios-lockDemo [25457:248358] -[YZRwlock write] 2018-08-15 16:07:47.763705+0800 ios-lockDemo [25457:248358] -[YZRwlock Write] 2018-08-15 16:07:48.767980+0800 ios-lockdemo [25457:248381] -[YZRwlock write] 2018-08-15 16:07:49.772241+0800 Ios-lockdemo [25457:248382] -[YZRwlock write] 2018-08-15 16:07:50.777547+0800 ios-lockDemo [25457:248383] -[YZRwlock write] 2018-08-15 16:07:50.777547+0800 ios-lockDemo [25457:248383] -[YZRwlock write Write 2018-08-15 16:07:51.779544+0800 ios-lockdemo [25457:248359] -[YZRwlockread[2018-08-15 16:07:51.779544+0800 ios-lockDemo [25457:248356] -[YZRwlockread[2018-08-15 16:07:51.779546+0800 ios-lockDemo [25457:248358] -[YZRwlockread]
Copy the code

Methods are executed one at a time after a write is printed, whereas read can be executed simultaneously. This is called a read/write lock.

dispatch_barrier_asyncAsynchronous fence

  • The concurrent queue passed by this function must pass by itselfdispatch_queue_cretateTo create the
  • If a serial or global concurrent queue is passed in, this function is equivalent todispatch_asyncEffect of a function

use

Self. queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT); Dispatch_async (self.queue, ^{}); // dispatch_barrier_async(self.queue, ^{});Copy the code

eg:

YZBarrier inherits YZBaseLock and otherTest

#import "YZBarrier.h"@interface YZBarrier () @property (strong, nonatomic) dispatch_queue_t queue; @end@implementation - (void)otherTest{// initialize queue self.queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);
    
    for(int i = 0; i < 3; I++) {// read dispatch_async(self.queue, ^{[selfread]; }); // dispatch_barrier_async(self.queue, ^{[self write]; }); Dispatch_async (self.queue, ^{[selfread]; }); Dispatch_async (self.queue, ^{[selfread];
        });
        
    }
}

- (void)read {
    sleep(1);
    NSLog(@"read");
}

- (void)write
{
    sleep(1);
    NSLog(@"write");
}
@end
Copy the code

When you call

YZBaseLock *lock = [[YZBarrier alloc] init];
[lock otherTest];
Copy the code

The output is:

The 2018-08-15 17:50:45. 867990 + 0800 iOS - LockDemo (30046-324146)read2018-08-15 17:50:46.871969+0800 ios-lockdemo [30046:324146] Write 2018-08-15 17:50:47.876419+0800 iOS-LockDemo[30046:324146]readThe 2018-08-15 17:50:47. 876419 + 0800 iOS - LockDemo (30046-324148)readThe 2018-08-15 17:50:47. 876450 + 0800 iOS - LockDemo (30046-324145)read2018-08-15 17:50:48.880739+0800 ios-lockdemo [30046:324145] Write 2018-08-15 17:50:49.885434+0800 iOS-LockDemo[30046:324145]readThe 2018-08-15 17:50:49. 885435 + 0800 iOS - LockDemo (30046-324146)readThe 2018-08-15 17:50:49. 885442 + 0800 iOS - LockDemo (30046-324148)read2018-08-15 17:50:50.889361+0800 ios-lockdemo [30046:324148] Write 2018-08-15 17:50:51.894104+0800 iOS-LockDemo[30046:324148]readThe 2018-08-15 17:50:51. 894104 + 0800 iOS - LockDemo (30046-324146)read
Copy the code

The method is executed one at a time after the write operation is printed. The method can be executed simultaneously, but when the write operation is executed, all other reads and writes are suspended.

dispatch_group_tScheduling group

Having said so much about the use of locks, scheduling groups can also achieve a similar effect to fences.

api

//1. Create dispatch_group_t group = dispatch_group_create(); Dispatch_queue_t queue = dispatch_get_global_queue(0, 0); The dispatch_group_enter(group) command is executed this time. Dispatch_group_leave (group); ^{dispatch_group_notify(group, dispatch_get_main_queue(), ^{// perform UI refresh});Copy the code

eg:

YZDispatchGroup inherits YZBaseLock and otherTest for testing. Assume the scenario is that two images need to be downloaded in the child thread, and sleep() simulated time-consuming operation. After downloading both images, return to the main thread to refresh the UI.

#import "YZDispatchGroup.h"

@implementation YZDispatchGroup
- (instancetype)init
{
    self = [super init];
    if (self) {
        
    }
    returnself; } - (void)otherTest{//1. Create dispatch_group_t group = dispatch_group_create(); Dispatch_queue_t queue = dispatch_get_global_queue(0, 0); The dispatch_group_enter(group) command is executed this time. dispatch_async(queue, ^{ [self downLoadImage1]; Dispatch_group_leave (group); }); The dispatch_group_enter(group) command is executed this time. dispatch_async(queue, ^{ [self downLoadImage2]; Dispatch_group_leave (group); }); ^{dispatch_group_notify(group, dispatch_get_main_queue(), ^test1 andtestAfter 2, the request is madetest3
         [self reloadUI];
    });

}
- (void)downLoadImage1 {
    sleep(1);
    NSLog(@"%s--%@",__func__,[NSThread currentThread]);
}
- (void)downLoadImage2 {
     sleep(2);
    NSLog(@"%s--%@",__func__,[NSThread currentThread]);
}
- (void)reloadUI
{
    NSLog(@"%s--%@",__func__,[NSThread currentThread]);
}
@end
Copy the code

When you call

YZBaseLock *lock = [[YZBarrier alloc] init];
[lock otherTest];
Copy the code

The output is:

2018-08-15 19:08:35.651955+0800 ios-lockDemo [3533:49583]- [YZDispatchGroup downLoadImage1]--<NSThread: 0x6000033ed380>{number = 3, Name = (null)} 2018-08-15 19:08:36.648922+0800 ios-lockdemo [3533:49584]- [YZDispatchGroup downLoadImage2]--<NSThread: 0x6000033e0000>{number = 4, Name = (null)} 2018-08-15 19:08:36.649179+0800 ios-lockdemo [3533:49521]- [YZDispatchGroup reloadUI]--<NSThread: 0x6000033865c0>{number = 1, name = main}Copy the code

From the result, it can be seen that the sub-thread takes time to operate. When the image is being downloaded, the main thread will not refresh the UI until the two images are downloaded.

dispatch_groupThere are two caveats

  • Dispatch_group_enter must appear before dispatch_group_leave
  • Dispatch_group_enter and dispatch_group_leave must be paired

Spin-lock, mutex of choice

In front of so many locks, so how to choose in the usual development? In fact, the main reference to the following criteria to choose.

When is it cost-effective to use a spin lock?

  • Threads are expected to wait a short time for locks
  • Locking code (critical sections) is often invoked, but contention rarely occurs
  • CPU resources are not tight
  • Multicore processor

When is it cost-effective to use a mutex?

  • The thread is expected to wait a long time for the lock
  • Single-core processor
  • There are IO operations in the critical area
  • Critical section code complex or large loop
  • Critical sections are very competitive

The resources

Download this article from Github

OSSpinLock Is Unsafe

OSSpinLock is no longer secure

GNUstep source code address

The Runtime source

Basic principles of iOS

There’s more to @synchronized than you ever wanted to know

More information, welcome to pay attention to the individual public number, not regularly share a variety of technical articles.