multithreading

Common multithreading schemes in iOS:

GCD functions:

  • Perform tasks synchronously

    dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
    Copy the code
  • Perform tasks asynchronously

    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    Copy the code

The GCD source

GCD queue:

  • Concurrent Dispatch Queue

    • Enables concurrent execution of multiple tasks (automatically enabling multiple threads to execute tasks simultaneously)
    • Concurrency is only available with asynchronous (dispatch_async) functions
  • Serial Dispatch Queue

    • Allow tasks to be executed one after another (after one task is completed, proceed to the next)

Confusing terms

There are four confusing terms: synchronous, asynchronous, concurrent, and serial

  • synchronousandasynchronousMain impacts:Can you open a new thread
    • Synchronization: Executes tasks in the current thread without the ability to start a new thread
    • Asynchrony: Performs tasks in a new thread, with the ability to start a new thread
  • concurrentandserialMain impacts:How tasks are performed
    • Concurrency: Multiple tasks are executed concurrently (simultaneously)
    • Serial: After one task is executed, the next task is executed

The execution effect of various queues

The deadlock problem

Root causes of deadlocks:

Adding tasks to the current serial queue using sync will lock the current serial queue (deadlock)

- (void)interview01
{
    // Question: The following code is executed on the main thread, will it cause a deadlock? Will!
    NSLog(@" Perform Task 1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@" Execute Task 2");
    });
    
    NSLog(@" Perform Task 3");
    // dispatch_sync immediately synchronizes tasks in the current thread
  	/* * Perform task 1 */
}

- (void)interview02
{
    // Question: The following code is executed on the main thread, will it cause a deadlock? Don't!
    NSLog(@" Perform Task 1");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(@" Execute Task 2");
    });
    
    NSLog(@" Perform Task 3");
    // dispatch_async does not require immediate synchronous execution of tasks on the current thread
  	/* * Perform task 1 * Perform task 3 * Perform task 2 */
}

- (void)interview03
{
    // Question: The following code is executed on the main thread, will it cause a deadlock? Will!
    NSLog(@" Perform Task 1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ / / 0
        NSLog(@" Execute Task 2");
        
        dispatch_sync(queue, ^{ / / 1
            NSLog(@" Perform Task 3");
        });
    
        NSLog(@" Execute Task 4");
    });
    
    NSLog(@" Execute Task 5");
  	/* * Perform task 1 * Perform task 5 * Perform task 2 */
}

- (void)interview04
{
    // Question: The following code is executed on the main thread, will it cause a deadlock? Don't!
    NSLog(@" Perform Task 1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_SERIAL);
// dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t queue2 = dispatch_queue_create("myqueu2", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{ / / 0
        NSLog(@" Execute Task 2");
        
        dispatch_sync(queue2, ^{ / / 1
            NSLog(@" Perform Task 3");
        });
        
        NSLog(@" Execute Task 4");
    });
    
    NSLog(@" Execute Task 5");
  	/* * Execute task 1 * Execute task 5 * Execute task 2 * Execute task 3 * Execute task 4 */
}

- (void)interview05
{
    // Question: The following code is executed on the main thread, will it cause a deadlock? Don't!
    NSLog(@" Perform Task 1");
    
    dispatch_queue_t queue = dispatch_queue_create("myqueu", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{ / / 0
        NSLog(@" Execute Task 2");
        
        dispatch_sync(queue, ^{ / / 1
            NSLog(@" Perform Task 3");
        });
        
        NSLog(@" Execute Task 4");
    });
    
    NSLog(@" Execute Task 5");
  	/* * Execute task 1 * Execute task 5 * Execute task 2 * Execute task 3 * Execute task 4 */
}
Copy the code

GCD Queue Group (Dispatch Group)

DispatchGroup can combine multiple concurrent tasks into a group and then listen for events when all tasks are complete. For example, multiple network requests can be made at the same time, and the UI can be reload after all the network requests are completed.

You can create a Dispatch Group as follows:

dispatch_group_t dispatch_group_create(void);
Copy the code

A Dispatch group is a simple data structure that is indistinguishable from one another, unlike a dispatch queue, which has an identifier to distinguish identity.

useDispatch GroupThere are two ways:

  • Use the dispatch_group_async function

    It is a variation of the normal dispatch_async function. The group parameter indicates the group to which the block to be executed belongs. The queue parameter indicates the queue to be executed. The block contains the task to be executed. Therefore dispatch_group_async can easily add different tasks in different queues to the Dispatch group. Queues in this function can be either serial or concurrent queues.

  • Use the dispatch_group_enter and dispatch_group_leave functions

    The former increases the number of tasks being executed in the Dispatch Group, while the latter decreases it. Therefore, after dispatch_group_enter is called, there must be a corresponding dispatch_group_leave. If there is no corresponding leave operation after the call to Enter, the set of tasks will never complete.

When all tasks have been added to the Group, there are two ways to wait for the task to complete:

  • Use the dispatch_group_wait function

    // Wait 2 seconds, then proceed directly to the code regardless of whether all tasks have been completed
    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)));
    // Wait until all tasks are complete before proceeding to the code
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    Copy the code

    Dispatch_group_wait is synchronous and blocks the current thread, so it cannot be executed on the main thread.

  • Use the dispatch_group_notify function

    // Create a queue group
    dispatch_group_t group = dispatch_group_create();
    // Create a concurrent queue
    dispatch_queue_t queue = dispatch_queue_create("my_queue", DISPATCH_QUEUE_CONCURRENT);
        
    // Add an asynchronous task
    dispatch_group_async(group, queue, ^{
    		for (int i = 0; i < 5; i++) {
            sleep(1);
            NSLog(1 - % @ @ "task"[NSThreadcurrentThread]); }}); dispatch_group_async(group, queue, ^{for (int i = 0; i < 5; i++) {
            sleep(1);
            NSLog(Task 2 - % @ "@"[NSThreadcurrentThread]); }});// This task will be automatically executed after the previous tasks are completed
    dispatch_group_notify(group, queue, ^{
    		dispatch_async(dispatch_get_main_queue(), ^{
    				for (int i = 0; i < 5; i++) {
    						NSLog(3 - % @ "@" task[NSThreadcurrentThread]); }}); });Copy the code

Multi-thread security hazard

Example 1: Deposit and withdraw money

Example 2: Selling tickets

/** save money, withdraw money demo */
- (void)moneyTest
{
    self.money = 100;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0.0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [selfsaveMoney]; }});dispatch_async(queue, ^{
        for (int i = 0; i < 10; i++) {
            [selfdrawMoney]; }}); }/ save * * * /
- (void)saveMoney
{
    int oldMoney = self.money;
    sleep(2.);
    oldMoney += 50;
    self.money = oldMoney;
    
    NSLog(At sign "50, I have %d dollars left - % at sign", oldMoney, [NSThread currentThread]);
}

Draw money / * * * /
- (void)drawMoney
{
    int oldMoney = self.money;
    sleep(2.);
    oldMoney -= 20;
    self.money = oldMoney;
    
    NSLog("Take 20, you have %d dollars - %@", oldMoney, [NSThread currentThread]);
}

/** Sell 1 ticket */
- (void)saleTicket
{
    int oldTicketsCount = self.ticketsCount;
    sleep(2.);
    oldTicketsCount--;
    self.ticketsCount = oldTicketsCount;
    
    NSLog("% D tickets left - %@", oldTicketsCount, [NSThread currentThread]);
}

/** ticket demo */
- (void)ticketTest
{
    self.ticketsCount = 15;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0.0);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
             [selfsaleTicket]; }});dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [selfsaleTicket]; }});dispatch_async(queue, ^{
        for (int i = 0; i < 5; i++) {
            [selfsaleTicket]; }}); }Copy the code

Multi-thread security risk analysis:

Multi-thread security hidden solution:

  • Use thread synchronization (synchronization, which means coordinated pacing, in a predetermined order)
  • A common thread synchronization technique is locking

IOS thread synchronization scheme

  • OSSpinLock
  • os_unfair_lock
  • pthread_mutex
  • dispatch_semaphore
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSRecursiveLock
  • NSCondition
  • NSConditionLock
  • @synchronized

OSSpinLock

  • OSSpinLock is called a “spin lock”, and the thread waiting for the lock will be in busy-wait state, occupying CPU resources.

  • It is no longer secure and priority inversion may occur

    • If the thread waiting for the lock has a higher priority, it will continue to occupy CPU resources, and the lower priority thread will not be able to release the lock
  • #import

When operating systems use multiple threads, they actually use a time-slice scheduling algorithm, which assigns threads a very small amount of time to run. When one thread runs out of time, the next thread is executed, so that we look as if multiple threads are executing at the same time. This algorithm has a priority, and a thread with a higher priority might be allocated a little more running time.

/ / initialization
OSSpinLock lock = OS_SPINLOCK_INIT;
// Try to lock (if waiting is required, do not lock, return false, if not waiting, return true)
bool result = OSSpinLockTry(&lock);
/ / lock
OSSpinLockLock(&lock);
/ / unlock
OSSpinUnLockLock(&lock);
Copy the code

os_unfair_lock

  • Os_unfair_lock os_unfair_lock is used to replace insecure OSSpinLock. It is only supported in iOS10
  • From underlying calls, threads waiting for os_UNFAIR_LOCK are dormant, not busy, etc
  • The header file needs to be imported#import <os/lock.h>
/ / initialization
os_unfair_lock lock = OS_UNFAIR_LOCK_INIT;
// Try to lock (if waiting is required, do not lock, return false, if not waiting, return true)
bool result = os_unfair_lock_trylock(&lock);
/ / lock
os_unfair_lock_lock(&lock);
/ / unlock
os_unfair_lock_unlock(&lock);
Copy the code

pthread_mutex

  • Mutex is called a mutex, and the thread waiting for the lock will sleep
  • The header file needs to be imported#import <pthread.h>

The difference between a spin lock and a mutex at the assembly level is that a spin lock causes assembly code to execute repeatedly in a particular piece of code, while a mutex causes assembly code to exit the thread because the current thread is asleep and there is nothing else to do.

The breakpoint executes assembly code and exits on syscall, indicating that the thread is asleep.

Ordinary lock
// Initialize the property
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_DEFAULT);
// Initialize the lock
pthread_mutex_t *mutex;
pthread_mutex_init(mutex, &attr);
// Try locking
pthread_mutex_trylock(&mutex);
/ / lock
pthread_mutex_lock(&mutex);
/ / unlock
pthread_mutex_unlock(&mutex);
// Destroy attributes
pthread_mutexattr_destroy(&attr);
/ / destruction of the lock
pthread_mutex_destroy(&mutex);

// Initialize the lock. If the attribute is passed NULL, PTHREAD_MUTEX_DEFAULT is used by default
// pthread_mutex_init(mutex, NULL);

// Static initialization
// pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

/* * Mutex type attributes */
#define PTHREAD_MUTEX_NORMAL		0			/ / ordinary locks
#define PTHREAD_MUTEX_ERRORCHECK	1		
#define PTHREAD_MUTEX_RECURSIVE		2		/ / recursive locking
#define PTHREAD_MUTEX_DEFAULT		PTHREAD_MUTEX_NORMAL  // Default is normal lock
Copy the code
Recursive locking

Recursive locking: Allows the same thread to lock a lock repeatedly

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

@interface MutexDemo2(a)
@property (assign.nonatomic) pthread_mutex_t mutex;
@end

@implementation MutexDemo2

- (void)__initMutex:(pthread_mutex_t *)mutex
{
    // Recursive locking: allows the same thread to lock the same lock repeatedly
    
    // Initialize the property
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    // Initialize the lock
    pthread_mutex_init(mutex, &attr);
    // Destroy attributes
    pthread_mutexattr_destroy(&attr);
}

- (instancetype)init
{
    if (self = [super init]) {
        [self __initMutex:&_mutex];
    }
    return self;
}

- (void)otherTest
{
    pthread_mutex_lock(&_mutex);
    
    NSLog(@"%s", __func__);
    
    static int count = 0;
    if (count < 10) {
        count++;
        [self otherTest];
    }
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
}

@end
Copy the code
conditions

Sometimes, when we are dealing with a matter, we need to verify whether certain conditions are established, and then execute them, and wait until the conditions are established. There’s also something about multithreading:

Commonly used API:

// Initialize the lock
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);
// Initialization conditions
pthread_cond_t cond;
pthread_cond_init(&cond, NULL);
// Wait condition (the current thread is asleep, release mutex lock; After being woken up, the thread is locked again.
pthread_cond_wait(&cond, &mutex);
// Activate a thread waiting for the condition
pthread_cond_signal(&cond);
// Activate all threads waiting for the condition
pthread_cond_broadcast(&cond);
// Destroy resources
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(&cond);
Copy the code

Example:

#import <pthread.h>

@interface MutexDemo3(a)
@property (assign.nonatomic) pthread_mutex_t mutex;
@property (assign.nonatomic) pthread_cond_t cond;
@property (strong.nonatomic) NSMutableArray *data;
@end

@implementation MutexDemo3

- (instancetype)init
{
    if (self = [super init]) {
        // Initialize the property
        pthread_mutexattr_t attr;
        pthread_mutexattr_init(&attr);
        pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
        // Initialize the lock
        pthread_mutex_init(&_mutex, &attr);
        // Destroy attributes
        pthread_mutexattr_destroy(&attr);
        
        // Initialization conditions
        pthread_cond_init(&_cond, NULL);
        
        self.data = [NSMutableArray array];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__remove) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__add) object:nil] start];
}

/ / thread 1
// Delete an element from the array
- (void)__remove
{
    pthread_mutex_lock(&_mutex);
    NSLog(@"__remove - begin");
    
    if (self.data.count == 0) {
        // Wait condition (the current thread is asleep, release mutex lock; After being woken up, the thread is locked again.
        pthread_cond_wait(&_cond, &_mutex);
    }
    
    [self.data removeLastObject];
    NSLog(@" Removed element");
    
    pthread_mutex_unlock(&_mutex);
}

/ / thread 2
// Add elements to the array
- (void)__add
{
    pthread_mutex_lock(&_mutex);
    
    sleep(1);
    
    [self.data addObject:@"Test"];
    NSLog(@" Added element");
    
    / / signal
    pthread_cond_signal(&_cond);
    / / radio
// pthread_cond_broadcast(&_cond);
    
    pthread_mutex_unlock(&_mutex);
}

- (void)dealloc
{
    pthread_mutex_destroy(&_mutex);
    pthread_cond_destroy(&_cond);
}

@end
Copy the code

NSLock

NSLock is a wrapper around a mutex normal lock

@protocol NSLocking
- (void)lock;
- (void)unlock;
@end

@interface NSLock : NSObject <NSLocking> {- (BOOL)tryLock;
/* * Pass in a time when the thread is in sleep * - If I can wait until this time for the lock to be released, the lock is successfully locked, returns true, and the code continues */ - If the lock is not released, the lock fails, returns false, and the code continues */
- (BOOL)lockBeforeDate:(NSDate *)limit;
@end
Copy the code

NSRecursiveLock

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

NSCondition

NSCondition is the encapsulation of mutex and COND

@interface NSCondition : NSObject <NSLocking> {- (void)wait;
/* * Pass in a time when the thread is in hibernation waiting state * - If it has received an activation signal before this time, it will wake up and lock, return true, and the code will continue to execute * - If it has not received an activation signal after this time, it will wake up and lock automatically, return false, The code also continues to execute */
- (BOOL)waitUntilDate:(NSDate *)limit;
// Activate a thread waiting for the condition
- (void)signal;
// Activate all threads waiting for the condition
- (void)broadcast;
@end
Copy the code

Source:

- (void) broadcast
{
  pthread_cond_broadcast(&_condition);
}

- (void) finalize
{
  pthread_cond_destroy(&_condition);
  pthread_mutex_destroy(&_mutex);
}

- (id) init
{
  if (nil! = (self = [super init])) {
    if (0! = pthread_cond_init(&_condition,NULL)) {
	  	DESTROY(self);
		} else if (0! = pthread_mutex_init(&_mutex, &attr_reporting)) { pthread_cond_destroy(&_condition); DESTROY(self); }}return self;
}

- (void) signal
{
  pthread_cond_signal(&_condition);
}

- (void) wait
{
  pthread_cond_wait(&_condition, &_mutex);
}

- (BOOL) waitUntilDate: (NSDate*)limit
{
  NSTimeInterval ti = [limit timeIntervalSince1970];
  double secs, subsecs;
  struct timespec timeout;
  int retVal = 0;

  // Split the float into seconds and fractions of a second
  subsecs = modf(ti, &secs);
  timeout.tv_sec = secs;
  // Convert fractions of a second to nanoseconds
  timeout.tv_nsec = subsecs * 1e9;

  retVal = pthread_cond_timedwait(&_condition, &_mutex, &timeout);
  if (retVal == 0)
    {
      return YES;
    }
  if (retVal == ETIMEDOUT)
    {
      return NO;
    }
  if (retVal == EINVAL)
    {
      NSLog(@"Invalid arguments to pthread_cond_timedwait");
    }
  return NO;
}
Copy the code

It can be seen from the source code that NSCondition follows the NSLocking protocol, and the internal LOCK of NSCondition has been realized, and the locking operation can be directly used to unlock.

NSConditionLock

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

@interface NSConditionLock : NSObject <NSLocking> {- (instancetype)initWithCondition:(NSInteger)condition;
@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;
@end
Copy the code

The sample

@interface NSConditionLockDemo(a)
@property (strong.nonatomic) NSConditionLock *conditionLock;
@end

@implementation NSConditionLockDemo

- (instancetype)init
{
    if (self = [super init]) {
        self.conditionLock = [[NSConditionLock alloc] initWithCondition:1];
    }
    return self;
}

- (void)otherTest
{
    [[[NSThread alloc] initWithTarget:self selector:@selector(__one) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__two) object:nil] start];
    
    [[[NSThread alloc] initWithTarget:self selector:@selector(__three) object:nil] start];
}

- (void)__one
{
    [self.conditionLock lock];
    
    NSLog(@"__one");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:2];
}

- (void)__two
{
    [self.conditionLock lockWhenCondition:2];
    
    NSLog(@"__two");
    sleep(1);
    
    [self.conditionLock unlockWithCondition:3];
}

- (void)__three
{
    [self.conditionLock lockWhenCondition:3];
    
    NSLog(@"__three");
    
    [self.conditionLock unlock];
}

@end
Copy the code

SerialQueue

It is also possible to achieve thread synchronization using serial queues directly from GCD

dispatch_queue_t ticketQueue = dispatch_queue_create("ticketQueue", DISPATCH_QUEUE_SERIAL)
dispatch_sync(ticketQueue, ^{
		/ / task
});
Copy the code

Dispatch_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
// The initial semaphore value
int value = 1;
// Initialize the semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(value);
// If the semaphore value is greater than 0, decrease the semaphore value by 1 and continue to execute the code
// If the semaphore value <= 0, it will sleep until the semaphore value becomes >0, then it will decrease the semaphore value by 1, and continue to execute the code
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// Set the semaphore to +1
dispatch_semaphore_signal(self.semaphore);
Copy the code

@synchronized

  • @synchronized is the encapsulation of a mutex recursive lock
  • Source view: objC4objc-sync.mmfile
  • @synchronized(obj) internally generates a recursive lock corresponding to OBj, and then locks and unlocks the lock
@synchronized(obj) {
		/ / task
}
Copy the code

Comparison of thread synchronization schemes

Ranking performance from highest to lowest:

  • Os_unfair_lock (supported after iOS10)
  • OSSpinLock (no longer recommended)
  • dispatch_semaphore
  • pthread_mutex
  • dispatch_queue(DISPATCH_QUEUE_SERIAL)
  • NSLock
  • NSCondition
  • pthread_mutex(recursive)
  • NSRecursiveLock
  • NSConditionLock
  • @synchronized

Spin lock and mutex comparison

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

There is no need to worry about this in iOS because OSSpinLock is no longer recommended, and the rest are mostly mutex locks

atomic

  • Atomic is used to ensure atomicity of property setters and getters. It is equivalent to placing a thread synchronization lock inside the getter and setter

  • See objC4’s objc-accessors.mm

  • It does not guarantee that the process of using attributes is thread-safe

Atomic is rarely used on iOS because it is too performance intensive and is generally used on MacOS.

Read and write security schemes in iOS

Consider how to implement the following scenarios:

  • Only one thread can write data at a time
  • Multiple threads are allowed to read at the same time
  • Both write and read operations are not allowed at the same time

The above scenario is typical “multiple read and single write”, which is often used for reading and writing data such as files. The implementation schemes in iOS are as follows:

  • pthread_rwlock: read-write lock
  • dispatch_barrier_async: Asynchronous fence call

pthread_rwlock

The thread waiting for the lock will go to sleep.

// Initialize the lockPthread_rwlock_t lock; pthread_rwlock_init(&lock,NULL);
// read - lock
pthread_rwlock_rdlock(&lock);
// read - try locking
pthread_rwlock_tryrdlock(&lock);
// write - lock
pthread_rwlock_wrlock(&lock);
// write - try locking
pthread_rwlock_trywrlock(&lock);
// Destroy resources
pthread_rwlock_unlock(&lock);
pthread_rwlock_destroy(&lock);
Copy the code

dispatch_barrier_async

Suppose we have a concurrent queue that reads and writes a data object. If the operations in the queue are read, multiple operations can be performed simultaneously. If there is a write operation, you must ensure that no read operation is being performed during the write operation. You must wait until the write operation is complete before reading the data. Otherwise, incorrect data may be read. This is where we use dispatch_barrier_async.

  • Tasks submitted with dispatch_barrier_async are guaranteed to be unique when executed in a concurrent queue.

  • This is valid only for self-created queues, not gloablQueue

  • If a serial or global concurrent queue is passed in, this function is equivalent to the effect of dispatch_async

dispatch_queue_t queue = dispatch_queue_create("rw_queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(self.queue, ^{
		[self read];
});

dispatch_barrier_async(self.queue, ^{
		[self write];
});        
Copy the code

Implementation principle of dispatch_once

if (dispatch_atomic_cmpxchg(vval, NULL, &dow)) {
    _dispatch_client_callout(ctxt, func);
    tmp = dispatch_atomic_xchg(vval, DISPATCH_ONCE_DONE);
    tail = &dow;
    while(tail ! = tmp) {while(! tmp->dow_next) { _dispatch_hardware_pause(); } sema = tmp->dow_sema; tmp = (struct _dispatch_once_waiter_s*)tmp->dow_next; _dispatch_thread_semaphore_signal(sema); }}else {
    dow.dow_sema = _dispatch_get_thread_semaphore();
    for (;;) {
        tmp = *vval;
        if (tmp == DISPATCH_ONCE_DONE) {
            break;
        }
        dispatch_atomic_store_barrier();
        if (dispatch_atomic_cmpxchg(vval, tmp, &dow)) {
            dow.dow_next = tmp;
            _dispatch_thread_semaphore_wait(dow.dow_sema);
        }
    }
    _dispatch_put_thread_semaphore(dow.dow_sema);
}
Copy the code
  1. First call: if vval is NULL, if vval is NULL. Execute the block first and let the value of vval be DISPATCH_ONCE_DONE to indicate that the task is complete and save the previous vval with TMP. At this point, dow is also empty, so the while judgment is not valid and the code execution ends.

  2. The second call from the same thread: vval has become DISPATCH_ONCE_DONE, so the if judgment is not true and the for loop for the else branch is entered. Since TMP is DISPATCH_ONCE_DONE, the loop exits without doing anything.

  3. Multiple threads calling at the same time: Since the if judgment is an atomic operation, only one thread must enter the if branch and the others into the else branch. Since vval is not DISPATCH_ONCE_DONE when other threads call the function, it goes to the latter part of the for loop. A linked list is constructed where the wait method of the semaphore is called and blocked on each node, whereas in the if branch, all nodes are traversed in turn and the signal method is called to wake up all the waiting semaphore.

CADisplayLink, NSTimer use note

CADisplayLink and NSTimer make strong references to target, and if target makes strong references to them, circular references are raised.

** Solution 1: **NSTimer can use blocks

__weak typeof(self) weakSelf = self;
[NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
		[weakSelf timerTest];
}];
Copy the code

Solution 2: Use proxy objects (inherited from NSObject)

@implementation MJProxy

+ (instancetype)proxyWithTarget:(id)target
{
    MJProxy *proxy = [[MJProxy alloc] init];
    proxy.target = target;
    return proxy;
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    return self.target;
}

@end
  
/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /

@interface ViewController(a)
@property (strong.nonatomic) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}

- (void)timerTest
{
    NSLog(@"%s", __func__);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
  	// Remember to destroy it
    [self.timer invalidate];
}

@end
Copy the code

** Solution 3: ** Use proxy objects (inherited from NSProxy)

#import "MJProxy.h"

@interface MJProxy(a)
@property (weak.nonatomic) id target;
@end

@implementation MJProxy

+ (instancetype)proxyWithTarget:(id)target
{
    // The NSProxy object does not need to call init because it has no init method
    MJProxy *proxy = [self alloc];
    proxy.target = target;
    return proxy;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel
{
    return [self.target methodSignatureForSelector:sel];
}

- (void)forwardInvocation:(NSInvocation *)invocation
{
    [invocation invokeWithTarget:self.target];
}
@end
  
/ * -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - * /

@interface ViewController(a)
@property (strong.nonatomic) NSTimer *timer;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:[MJProxy proxyWithTarget:self] selector:@selector(timerTest) userInfo:nil repeats:YES];
}

- (void)timerTest
{
    NSLog(@"%s", __func__);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
  	// Remember to destroy it
    [self.timer invalidate];
}

@end
Copy the code

As you can see, both plan 2 and Plan 3 use message forwarding. Although plan 2 seems to have simpler code, we recommend Plan 3 because of its higher execution efficiency. Solution 2, when executed, still goes through the msgSend process, which is to find the class object, find the method exists, find the method does not exist, find the parent of the class object, and so on, while Solution 3 goes directly to the message forwarding process.

NSProxy is a class specifically designed to be a proxy, so it is more suitable.

Let’s take a look at the following output:

ViewController *vc = [[ViewController alloc] init];
// Inherits from NSProxy
MJProxy *proxy1 = [MJProxy proxyWithTarget:vc];
// Inherits from NSObject
MJProxy1 *proxy2 = [MJProxy1 proxyWithTarget:vc];
// Output: 1 0
NSLog(@"%d %d",
	[proxy1 isKindOfClass:[ViewController class]],
	[proxy2 isKindOfClass:[ViewController class]]);
Copy the code

When proxy1 calls isKindOfClass, it will directly go to the message forwarding process, which is similar to asking VC to call isKindOfClass method, so the result must be true; When proxy2 calls isKindOfClass, it looks for the corresponding method call in its own class object, and the result is false.

The GCD timer

NSTimer relies on RunLoop, and if RunLoop is too heavy, NSTimer may not be on time.

Using the GCD timer will be more accurate.

// Create a queue
dispatch_queue_t queue = dispatch_queue_create("timer", DISPATCH_QUEUE_SERIAL);
// Create a timer
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, queue);
// Set the time
int64_t start = 2.0; // The command will be executed in 2 seconds
uint64_t interval = 1.0; // The interval is 1 second
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
interval * NSEC_PER_SEC.0);
// Set the callback
dispatch_source_set_event_handler(timer, ^{
		NSLog(@ "1111");
});
// Start timer
dispatch_resume(timer);
Copy the code

Encapsulate a timer utility class:

@interface MJTimer : NSObject

+ (NSString *)execTask:(void(^) (void))task
           start:(NSTimeInterval)start
        interval:(NSTimeInterval)interval
         repeats:(BOOL)repeats
           async:(BOOL)async;

+ (NSString *)execTask:(id)target
              selector:(SEL)selector
                 start:(NSTimeInterval)start
              interval:(NSTimeInterval)interval
               repeats:(BOOL)repeats
                 async:(BOOL)async;

+ (void)cancelTask:(NSString *)name;

@end

@implementation MJTimer

static NSMutableDictionary *timers_;
dispatch_semaphore_t semaphore_;
+ (void)initialize
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        timers_ = [NSMutableDictionary dictionary];
        semaphore_ = dispatch_semaphore_create(1);
    });
}

+ (NSString *)execTask:(void(^) (void))task start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
    if(! task || start <0 || (interval <= 0 && repeats)) return nil;
    
    / / the queue
    dispatch_queue_t queue = async ? dispatch_get_global_queue(0.0) : dispatch_get_main_queue();
    
    // Create a timer
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, queue);
    
    // Set the time
    dispatch_source_set_timer(timer,
                              dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC),
                              interval * NSEC_PER_SEC.0);
    
    
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    // Unique identifier of the timer
    NSString *name = [NSString stringWithFormat:@"%zd", timers_.count];
    // Put it in the dictionary
    timers_[name] = timer;
    dispatch_semaphore_signal(semaphore_);
    
    // Set the callback
    dispatch_source_set_event_handler(timer, ^{
        task();
        
        if(! repeats) {// Non-repetitive tasks
            [selfcancelTask:name]; }});// Start timer
    dispatch_resume(timer);
    
    return name;
}

+ (NSString *)execTask:(id)target selector:(SEL)selector start:(NSTimeInterval)start interval:(NSTimeInterval)interval repeats:(BOOL)repeats async:(BOOL)async
{
    if(! target || ! selector)return nil;
    
    return [self execTask:^{
        if ([target respondsToSelector:selector]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
            [target performSelector:selector];
#pragma clang diagnostic pop
        }
    } start:start interval:interval repeats:repeats async:async];
}

+ (void)cancelTask:(NSString *)name
{
    if (name.length == 0) return;
    
    dispatch_semaphore_wait(semaphore_, DISPATCH_TIME_FOREVER);
    
    dispatch_source_t timer = timers_[name];
    if (timer) {
        dispatch_source_cancel(timer);
        [timers_ removeObjectForKey:name];
    }

    dispatch_semaphore_signal(semaphore_);
}

@end
Copy the code

The interview questions

  1. Do you understand multithreading?

  2. What are the multithreading schemes for iOS? Which one do you prefer?

  3. Have you ever used GCD in a project?

  4. GCD queue type

  5. Talk about the differences between OperationQueue and GCD, and the advantages of each

  6. What are the thread-safe options?

  7. OC What do you know about locks? Ask a second question based on your answer;

    1. Question 1: Spin and mutex contrast?
    2. Follow-up 2: What should we pay attention to when using the above locks?
    3. Question 3: Use C/OC/C++, one or the other, to implement spin or mutex? Dictate!
  8. What is the print result of the following code?

    dispatch_queue_t queue = dispatch_get_global_queue(0.0);
    dispatch_async(queue, ^{
    		NSLog(@ "1");
    		[self performSelector:@selector(test) withObject:nil afterDelay:. 0];
    		NSLog(@ "3");
    });
    // Result output: 1 3
    Copy the code

    PerformSelector: withObject: afterDelay: is the nature of the Runloop add a timer, and the child thread is not started by default Runloop.