multithreading

In the course of the interview, the content of the article is basically carried from the big guy blog and self-understanding, may be a little messy, do not like spray!!

Ali, Byte: A Set of effective iOS Interview questions

Mainly by the COMMUNIST Party of China

How many types of threads are there in iOS development? Respectively compared

  1. Pthreads: Cross-system C multithreading framework, not recommended.
  2. NSThread: ## Object-oriented, need to manually manage the life cycle.
  3. GCD: Grand Central Dispatch, featuring quests and queues, just tell him what to do.
  4. NSOperation & NSOperationQueue: Encapsulation of GCD, object-oriented

2. What queues does GCD have and what queues are provided by default

  1. The home side column

    dispatch_get_main_queue()

  2. Global concurrent queue

    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)

    The queue priority in descending order is:

    DISPATCH_QUEUE_PRIORITY_HIGH

    DISPATCH_QUEUE_PRIORITY_DEFAULT

    DISPATCH_QUEUE_PRIORITY_LOW

    DISPATCH_QUEUE_PRIORITY_BACKGROUND

  3. Custom queues (Serial Serial and Concurrent)

    Dispatch_queue_create (" here is the queue name ", DISPATCH_QUEUE_SERIAL)

    Serial DISPATCH_QUEUE_SERIAL

    Parallel DISPATCH_QUEUE_CONCURRENT

3. What method apis does GCD have

  • The queue

dispatch_get_main_queue()

dispatch_get_global_queue()

dispatch_queue_create()

  • perform

dispatch_async()

dispatch_sync()

dispatch_after()

dispatch_once()

dispatch_apply()

dispatch_barrier_async()

dispatch_barrier_sync()

  • Scheduling group

dispatch_group_create()

dispatch_group_async()

dispatch_group_enter()

dispatch_group_leave()

dispatch_group_notify()

dispatch_group_wait()

  • Semaphore =

dispatch_semaphore_create()

dispatch_semaphore_wait()

dispatch_semaphore_signal()

  • Scheduling resources

dispatch_source_create()

dispatch_source_set_timer()

dispatch_source_set_event_handler()

dispatch_active()

dispatch_resume()

dispatch_suspend()

dispatch_source_cancel()

dispatch_source_testcancel()

dispatch_source_set_cancel_handler()

GCD main thread and main queue relationship

Tasks submitted to the main queue are executed on the main thread.

5, how to achieve synchronization, how many ways to say how many

Dispatch_async (in the same serial queue…)

dispatch_sync()

dispatch_barrier_sync()

dispatch_group_create() + dispatch_group_wait()

dispatch_apply(1, …)

[NSOpertaion start]

NSOperationQueue.maxConcurrentOperationCount = 1

Lock OSSpinLock (not recommended)

os_unfair_lock

Pthread_mutex (mutex, recursive, conditional, read/write)

@synchronied(obj)

NSLock

NSRecursiveLock

NSConditionLock & NSCondition

Dispatch_semaphore_create () + dispatch_semaphore_wait()

6. Implementation principle of dispatch_once

How is dispatch_once implemented?

  1. Read the token valuedispatch_once_t.dgo_once;
  2. If the Block is complete, return;
  3. If the Block is not complete, try atomic modificationdispatch_once_t.dgo_onceA value ofDLOCK_ONCE_UNLOCKED;

    3.1 If the dispatch_once_t.dgo_once is changed atomically to DLOCK_ONCE_DONE, run the Block command. Then wake up the waiting thread

    3.2 If the operation fails, the system enters a waiting loop

7. When will deadlocks occur

  • A single thread

Adds a synchronization task to the serial queue that is executing the task

/// dispatch_sync(dispatch_get_main_queue(), ^{NSLog(@)"This place is deadlocked."); }); Dispatch_queue_t theSerialQueue = dispatch_queue_create("I'm a serial queue", DISPATCH_QUEUE_SERIAL); Dispatch_async (theSerialQueue, ^{/// dispatch_async(theSerialQueue, ^"First floor"); /// the same serial queue dispatch_sync(theSerialQueue, ^{NSLog(@)"The second layer");
        
    });
});
Copy the code
  • Multiple threads

Simply put, A is equal to B, and B is equal to A.

A task requires multiple resources, such as resources 1 and 2. At this point, thread A and thread B both perform the task. However, thread A preempts resource 1 while thread B preempts resource 2. At this point, thread A also needs resource 2 to execute the task; Similarly, thread B also needs resource 1 to execute the task. So, A waits for B, B waits for A, the deadlock comes.

- (void)deadLock {
    
    
    NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(deadLock1) object:nil];
    [thread1 setName:@"[Thread Tom]];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(deadLock2) object:nil];
    [thread2 setName:@"[Thread Jerry]];
    
    [thread1 start];
    [thread2 start];
}

- (void)deadLock1 {
    
    [self.lock1 lock];
    NSLog(@"% @ lock lock1." ", [NSThread currentThread]); // Thread sleep for one second [NSThread sleepForTimeInterval:1]; [self.lock2 lock]; NSLog(@"% @ lock lock2." ", [NSThread currentThread]);

    
    [self doSomething];
    
    
    [self.lock2 unlock];
    NSLog(@"% @ unlock lock2." ", [NSThread currentThread]);
    
    [self.lock1 unlock];
    NSLog(@"% @ unlock lock1." ", [NSThread currentThread]);
}


- (void)deadLock2 {
    
    [self.lock2 lock];
    NSLog(@"% @ lock lock2." ", [NSThread currentThread]); // Thread sleep for one second [NSThread sleepForTimeInterval:1]; [self.lock1 lock]; NSLog(@"% @ lock lock1." ", [NSThread currentThread]);
    
    
    [self doSomething];
    
    
    [self.lock1 unlock];
    NSLog(@"% @ unlock lock1." ", [NSThread currentThread]);
    
    [self.lock2 unlock];
    NSLog(@"% @ unlock lock2." ", [NSThread currentThread]);
}

Copy the code

8. What types of thread locks are available? Introduce the functions and application scenarios respectively

The lock species note
OSSpinLock spinlocks It is not secure. IOS 10 is enabled
os_unfair_lock The mutex alternativeOSSpinLock
pthread_mutex The mutex PTHREAD_MUTEX_NORMAL,#import <pthread.h>
pthread_mutex (recursive) Recursive locking PTHREAD_MUTEX_RECURSIVE,#import <pthread.h>
pthread_mutex (cond) Conditions for the lock pthread_cond_t,#import <pthread.h>
pthread_rwlock Read-write lock Read operations are reentrant, and write operations are mutually exclusive
@synchronized The mutex Poor performance and inability to lock objects with memory address changes
NSLock The mutex encapsulationpthread_mutex
NSRecursiveLock Recursive locking encapsulationpthread_mutex (recursive)
NSCondition Conditions for the lock encapsulationpthread_mutex (cond)
NSConditionLock Conditions for the lock Specific conditional values can be specified

In 9, NSOperationQueue maxConcurrentOperationCount default values

1. This value enables the system to set a maximum value based on system conditions

10, NSTimer, CADisplayLink, dispatch_source_t pros and cons

advantages disadvantages
NSTimer Using a simple Depending on Runloop, it can not be used without Runloop,NSRunLoopCommonModesDefective, To join theThe main thread
CADisplaylink Depending on the screen refresh frequency to start the event, the most accurate. Perfect for UI refreshes. If the screen refresh is affected, the event is also affected, and the event trigger interval can only be a multiple of the duration of the screen refresh. If the event takes longer than the trigger duration, it is skipped several times and cannot be inherited
dispatch_source_t Don’t rely on the Runloop It is not precise and is relatively cumbersome to use
  • NSTimer

    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerFired:) userInfo:nil repeats:YES];  [[NSRunLoop currentRunLoop] addTimer:timerforMode:NSRunLoopCommonModes];
    [timer invalidate];
    Copy the code
  • CADisplaylink

    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(takeTimer:)];
    [link addToRunLoop:[NSRunLodop currentRunLoop] forMode:NSRunLoopCommonModes]; link.paused = ! link.paused; [link invalidate];Copy the code
  • Dispatch_source_t: for details, see 2.10 dispatch_source

    __block int countDown = 6; Dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); // configure the timer dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0); // set timer event handling dispatch_source_set_event_handler(timer, ^{// execute when timer is triggeredif (countDown <= 0) {
            dispatch_source_cancel(timer);
            
            NSLog(@"Countdown is over ~~~");
        }
        else {
            NSLog(@"Countdown with %d seconds left...", countDown); } countDown--; }); // start timer dispatch_active(timer);Copy the code

Do things ~ ~ ~

A, NSThread

NSThread is apple wrapped, object-oriented. You can use it to manipulate threads directly, but you need the developer to manually manage their life cycle.

But it is lighter than GCD and NSOperation/NSOperationQueue.

1 create NSThread

Before iOS 10:

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething) object:nil]; // start thread [thread start]; // Create and start thread -; Class method [NSThread detachNewThreadSelector:@selector(doSomething) toTarget:self withObject:nil];
Copy the code

After iOS 10, Apple kindly provided us with a Block callback:

- (instancetype)initWithBlock:(void (^)(void))block;

+ (void)detachNewThreadWithBlock:(void (^)(void))block;
Copy the code

In addition to displaying thread creation instances, Apple also provides a variety of NSObject classification methods to use. See NSObject-Objective-C Runtime classification Sending Messages for details

2 Common methods of NSThread

There aren’t a lot of methods for NSthreads, but there are a lot of them

  • Common class methods and class attributes:
// Get the current thread, read-only @property (class,readonly, strong) NSThread *currentThread; // <NSThread: 0x281DD6100 >{number = 1, name = main}readonly, strong) NSThread *mainThread; @property (class, read-only)readonly) BOOL isMainThread; + (void)sleepUntilDate:(NSDate *)date; + (void)sleepForTimeInterval:(NSTimeInterval)ti; // Exit current thread + (void)exit;
Copy the code
  • Common instance methods and instance properties:
@property (nullable, copy) NSString *name; // Whether a task is being executed @property (readonly, getter=isExecuting) BOOL executing; @property (readonly, getter=isFinished) BOOL finished; // Whether the thread has been cancelled (once cancelled, the thread shouldexit) @ property (readonly, getter=isCancelled) BOOL cancelled; // Whether the main thread @property (readonly) BOOL isMainThread; // Cancel the thread (once canceled, the thread shouldexit) - (void) cancel; // Start thread - (void)start;Copy the code

Second, the communist party

Typically, applications will be allowed to submit asynchronous requests, and then continue to do their own business while the system processes the request.

The COMMUNIST Party of China was designed with this principle in mind.

Dispatch-apple introduces GCDS like this:

Execute code concurrently on multicore hardware by submitting work to dispatch queues managed by the system.

Execute code concurrently on multi-core hardware by submitting work to system-managed Dispatch queues.

GCD is the most used and convenient solution for multi-threaded programming in iOS.

Using GCD has the following benefits:

  • GCDS automatically use more CPU cores;
  • GCD automatically manages the thread lifecycle;
  • GCDS can improve the performance of applications by delaying expensive computing tasks and running them in the background;
  • GCD provides an easy-to-use concurrency model (not just threads and locks);
  • Developers just need to tell the CCP what to do without extra thread-management code;

1. Tasks and queues

There are two important concepts in GCD: tasks and queues.

1.1 the task

That is, the code that needs to be executed in the Block when using GCD.

I personally understand that any piece of code is a task. So int a = 1 or NSLog of @”log”;

There are two methods for executing tasks: dispatch_sync and dispatch_async. The difference is whether it blocks the current thread and whether it has the ability to start a new thread.

  • Synchronous execution: Blocks the current thread and waits for the tasks in the Block to complete before the current thread continues to execute. Does not have the ability to start new threads.

  • Asynchronous execution: do not block the current thread, the current thread directly after the execution. The ability to start new threads, but not necessarily new threads.

1.2 the queue

A queue to store tasks. A queue is a special linear table with a first-in, first-out (FIFO) rule.

That is, new tasks are always inserted at the end of the queue, but execution starts at the head of the queue. It’s like queuing in everyday life.

Queues are divided into Serial Dispatch queues and Concurrent Dispatch queues.

  • Tasks in the serial queue are fetched and executed in FIFO order, and the next task is fetched only after the previous task is executed.

  • Tasks in parallel queues are also fetched in FIFO order, but GCD opens new threads to execute the fetched tasks.

    The action of fetching the task and starting a new thread is very fast, so it looks like the tasks are executed together.

    However, if the number of tasks in the queue is too large, the GCD cannot start 10,000 threads to execute tasks simultaneously.

    At the same time, the concurrency function of parallel to queues is only effective when executed asynchronously.

The difference between a serial queue and a parallel queue can be illustrated using the two images from this blog post:

The GCD exposes five different queues: main queue for the main thread, three background queues with different priorities, and one background queue with lower priorities (for I/O).

Users can also create custom queues, either serial or parallel. All blocks scheduled in custom queues are eventually put into the system’s global queue and thread pool.

Copy a picture of the big guy

Synchronous execution Asynchronous execution
Serial queues Current thread, executed one by one The other threads execute one by one
Parallel lines Current thread, executed one by one Open many threads, execute simultaneously

2. Use GCD

They say GCD is easy to use, so let’s use it:

  1. Create a queue (or get the system’s global queue);
  2. Append the task to the queue.

Then the system will execute the task according to the task type and queue (whether it is synchronous or asynchronous execution, which queue is executed).

2.1 Creating a Queue

Main Dispatch Queue

Main queue, a special serial queue. All tasks placed in the main queue are executed in the main thread. The main purpose is to refresh the UI, but you can also put everything in the main queue.

In principle, any UI refresh should be performed in the main queue, while time-consuming operations should be performed in other threads.

The main queue cannot be created, only obtained.

Dispatch_queue_t mainQueue = dispatch_get_main_queue();Copy the code

Global Dispatch Queue

Global queue, a global concurrent queue that Apple provides for developers to use directly.

Some uI-unrelated operations should be placed on a global queue, not the main queue. Such as network requests.

Obtain the global queue using the dispatch_get_global_queue method provided by GCD:

/ * * @function: dispatch_get_global_queue * @abstract: Obtains the global queue * @para identifier queue priority. Generally, DISPATCH_QUEUE_PRIORITY_DEFAULT is used. * @para flags Reserve the parameter and pass 0. Passing any value other than zero may result in NULL being returned. * @result: Return the specified queue, or NULL */ dispatch_queue_global_t dispatch_get_global_queue(long identifier, unsigned long flags).Copy the code

The dispatch_get_global_queue first parameter identifier has the following options:

/* * - DISPATCH_QUEUE_PRIORITY_HIGH * - DISPATCH_QUEUE_PRIORITY_DEFAULT * - DISPATCH_QUEUE_PRIORITY_LOW * - DISPATCH_QUEUE_PRIORITY_BACKGROUND */ /// The tasks sent to this queue are executed with the highest priority. // The tasks sent to this queue are executed before the tasks with the default and lower priorities#define DISPATCH_QUEUE_PRIORITY_HIGH 2// The tasks sent to this queue will be executed with the default priority. // The tasks sent to this queue will be executed after all high-priority tasks are scheduled and before low-priority tasks are scheduled#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0/// The tasks assigned to this queue will be executed with low priority /// The tasks assigned to this queue will be scheduled to be executed after all tasks with high priority and default priority have been fully scheduled#define DISPATCH_QUEUE_PRIORITY_LOW (-2)/// Tasks assigned to this queue will be executed at the background priority. /// Tasks assigned to this queue will be scheduled to be executed after all high-priority tasks. A thread with background state (setThreadPriority), /// (disk I/O is limited, thread scheduling priority is set to minimum)#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
Copy the code

Gets the global queue with the default priority

Dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);Copy the code

Starting with iOS 8, Apple also introduced quality of service for threads:

-qOS_class_user_interactive * -qOS_class_user_initiated * -qOS_class_default * - Apple recommends that we use the value of the quality of service category to mark the global concurrent queue QOS_CLASS_UTILITY * -qOS_class_BACKGROUND * * Global concurrent queues can still be identified by priority, which is mapped to the following QOS classes: * -dispatch_queue_priority_high: QOS_CLASS_USER_INITIATED * - DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT * - DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY * - DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND */ /* * @constant QOS_CLASS_USER_INTERACTIVE * @abstract This QOS class indicates that the thread performs the work of interacting with the user. * @discussion These efforts are required to run with the highest priority compared to the rest of the system. * Specify that this QOS class will demand almost all available system CPU resources and I/O bandwidth to run, even when competing for resources. * This is not a power-saving QOS class suitable for large tasks. This class should be limited to critical user interactions. * For example, handle events for the main loop, drawing, animation, etc. * * @constant QOS_CLASS_USER_INITIATED * @abstract This QOS class indicates that the thread is performing work initiated by the user and may be waiting for results. * work such as @Discussion takes precedence over user critical interactions but over other operations on the system. * This is not a power-saving QOS class suitable for large tasks. Its use should be limited to very short periods of time so that users do not switch tasks while waiting. * Typical user-initiated work that indicates progress by displaying placeholders or modal displays of the user interface. * * * @constant QOS_CLASS_DEFAULT * @abstrct Specifies the default QOS class to be used by the system when no specific QOS class information is available. * @Discussion this type of work has a lower priority than user-critical actions and user-initiated work, but a higher priority than utilities and background tasks. * Threads created by pthread_create without QOS class attributes will default to QOS_CLASS_DEFAULT. * This QOS class is not intended as a work class, it should only be the value of the QOS class that the system provides for propagation or recovery. * * * @constant QOS_CLASS_UTILITY * @abstract This QOS class indicates that the thread is performing work that may not be initiated by the user and that the user does not expect to wait for results immediately. * @discussion This type of work takes a lower priority than user-critical operations and user-initiated work, but a higher priority than low-level system maintenance work. * This QOS class indicates that such work should be run in an energy-efficient manner. * The work of this utility may not be shown to the user, but the impact of such work is visible to the user. * * * @constant QOS_CLASS_BACKGROUND * @abstract This QOS class indicates that the thread is performing work that was not initiated by the user and that the user may not know the result. * @discussion This type of work has a lower priority than other work. * This QOS class indicates that such work should be run in the most energy-efficient manner. * * * @constant QOS_CLASS_UNSPECIFIED * @abstract This flag indicates that QOS class information is missing or has been removed. * @discussion, as an API return value, may indicate that threads or pthreads are configured by incompatible legacy apis or conflict with qos-like systems. */ __QOS_ENUM(qos_class, unsigned int, QOS_CLASS_USER_INTERACTIVE = 0x21, QOS_CLASS_USER_INITIATED = 0x19, QOS_CLASS_DEFAULT = 0x15, QOS_CLASS_UTILITY = 0x11, QOS_CLASS_BACKGROUND = 0x09, QOS_CLASS_UNSPECIFIED = 0x00, );Copy the code

Custom queue

Apple allows developers to create custom queues, both serial and parallel.

/* * @funCIon: dispatch_queue_create * @abstract: Creates a custom queue * @para label Specifies the queue label. The value can be NULL. * @para attr Queue type, serial queue or parallel queue. DISPATCH_QUEUE_SERIAL and NULL indicate serial queue. DISPATCH_QUEUE_CONCURRENT indicates parallel queue. * @result: Returns the queue created. */ dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);Copy the code

As the notes say

  • The first parameter is the queue label, which can be NULL, and the developer can use this value for easy debugging. The application ID is recommended for queue labels in reverse order.
  • The second parameter is more important and indicates the type of queue the developer needs to create, serial or parallel.

    Serial queue: DISPATCH_QUEUE_SERIAL or NULL. Parallel queue: DISPATCH_QUEUE_CONCURRENT.

2.2 Creating a Task

All this work is really preparation, just to create a container to hold the task. But the container is indispensable.

/ * * @function: dispatch_sync * @abstract: Synchronous execution of a task on the current thread blocks the current thread until the task is complete. * @para queue Queue. The developer can specify which queue to execute this task * @para block task. The developer performs specific tasks within this Block. * @result: no return value */ //void //dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block); dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"Synchronous execution"); }); / * * @function: dispatch_async * @abstract: Another thread executes tasks asynchronously without blocking the current thread. * @para queue Queue. The developer can specify which queue to execute this task * @para block task. The developer performs specific tasks within this Block. * @result: no return value */ // void // dispatch_async(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"Asynchronous execution");
});
Copy the code

As noted in the comment, the first parameter indicates which queue to put on and the second indicates what the task is.

The main difference between synchronous and asynchronous is whether the current thread is blocked:

  • dispatch_syncBlocks the current thread.
  • dispatch_asyncDoes not block the current thread.

2.3 Combining Tasks and Queues

In the case of the current thread as the main thread, the task execution mode and queue type are paired together:

  1. Synchronous execution + serial queue
  2. Synchronous execution + parallel queue
  3. Asynchronous execution + serial queue
  4. Asynchronous execution + serial queue

Oh, I forgot two more. Main line

  1. Synchronous execution + primary queue
  2. Asynchronous execution + primary queue

In the case of the current thread being the master thread:

Serial queues Parallel lines The home side column
Synchronous execution Do not start a new thread, the task is executed sequentially Do not start a new thread, the task is executed sequentially A deadlock
Asynchronous execution Start a new thread and execute tasks sequentially Start new threads (possibly multiple) to execute tasks concurrently Do not start a new thread, the task is executed sequentially

Said one words:

  • Synchronous execution, which executes the specified task in the current thread and blocks subsequent tasks in the current thread;

    Under synchronous execution conditions, parallel queues cannot be parallel, because they are blocked.

    Note: Synchronous execution + primary queue = deadlock.

  • Asynchronous execution does not require blocking. A new thread is started to execute the task, and the subsequent tasks of the current thread are not blocked.

    In asynchronous execution, both serial and parallel queues start new threads

    The serial queue value opens a new thread, and the parallel queue opens as many threads as possible to execute the task separately (there is a limit, after all, it is impossible to open 1,000,000 threads at once).

  • The primary queue is a serial queue and can only be executed asynchronously. After all, synchronous execution + primary queue = deadlock.

Verify that synchronous execution + serial queue = deadlock

/** * sync + main queue */ - (void)syncMain {NSLog(@"Current thread: %@", [NSThread currentThread]);
    NSLog(@"SyncMain -- Start");
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"Synchronous execution + main queue");
    });
    
    NSLog(@"syncMain --- 结束");
}
Copy the code

The program crashed. Just print “current thread information” and “syncMain — start” and nothing more.

Collect the current execution environment first:

  1. Main thread execution;
  2. Synchronous execution;
  3. Main team tasks,

As I said above, any piece of code is a task.

It is clear that a dispatch_sync task (called task 1) was being performed at the time of the crash, and task 1 said to stop and execute ^{NSLog(@” synchronous execution + main queue “); } (call it task 2).

Dispatch_sync does not return until a Block has been executed. This means that dispatch_sync will not return until task 2 is complete.

In human terms, the main thread continues to perform task 1 until task 2 completes.

But we use the main queue to perform this synchronization, and the current task must complete if the main queue is to perform the next task.

At this point, task 1 can be completed only after the task is completed. Task 2 waits for task 1 to complete before it can start executing itself.

I’m waiting for you, and you’re waiting for me.

Yes!! This is a deadlock, but the new GCD has added a deadlock detection mechanism, if a deadlock occurs, will cause crash.

You can go to Apple source code – libdispatch or GCD source code blood spit analysis (2)

In fact, it is not synchronous execution + primary queue = deadlock, but in the main thread environment + synchronous execution + primary queue = deadlock.

At the next level, tasks in a serial queue are being executed. If a task is submitted to the serial queue synchronously, a deadlock will occur, which means a crash.

Verify:

As for GCD source blood ejecta analysis (2) said that can bypass crash to cause the deadlock, I think I may do… (If you are interested, try the following code.)

- (void)theDeadLock {
    
    NSLog(@"Current thread: %@", [NSThread currentThread]);
    
    dispatch_queue_t theQueue = dispatch_queue_create("com.junes.serialQueue", DISPATCH_QUEUE_SERIAL); // dispatch_async(theQueue, ^{// NSLog(@)); // dispatch_async(theQueue, ^{// NSLog(@))"From the first layer - %@", [NSThread currentThread]); /// if this is ahead of timereturnDispatch_sync (dispatch_get_main_queue(), ^{/// NSLog(@)"From the second layer - %@", [NSThread currentThread]); Dispatch_sync (theQueue, ^{dispatch_sync(theQueue, ^{"Level three. - Ollie to %@.", [NSThread currentThread]);
            });
            
            NSLog(@"Second layer completed - %@", [NSThread currentThread]);
        });
        
        NSLog(@"Layer 1 completed - %@", [NSThread currentThread]);
    });
    
    
    NSLog(@"Outermost %@", [NSThread currentThread]);
}
Copy the code

  • The deadlock is triggered here: theQueue must not be empty when layer 3 is executed.

    Layer 2 cannot return before layer 3 completes, if there are no tasks in theQueue, then layer 3 tasks can be executed immediately.

  • I don’t know…

    If you change the main queue of layer 2 into a global parallel queue or a custom serial queue, crash will occur.

    If anyone knows the principle, please let us know… Thank humbly

As for nesting queues in queues, there is also a table here ([] represents the outer operations, and all the outer operations work properly) :

[Synchronous execution + Serial queue] nested the same serial queue [Synchronous execution + Parallel queue] nested the same parallel queue [Asynchronous execution + serial queue] nested the same serial queue [Asynchronous execution + parallel queue] nested the same parallel queue
synchronous A deadlock The current thread executes serially A deadlock The current thread executes serially
asynchronous Another open thread (1) serial execution Another thread runs in parallel Another open thread (1) serial execution Another thread runs in parallel

2.4 Delayed Executiondispatch_after

Requirement: Postpone the task for some time.

  • dispatch_afterMethod can realize delayed execution of tasks.

Its parameters are:

  1. When: How long before the task is submitted to the queue;
  2. Queue: The queue to which the submission is made.
  3. Block: Submits a task.

Dispatch_after is superior to NSTimer because it does not need to specify a Runloop mode. Dispatch_after than NSObject. PerformSelector: withObject: afterDelay: good, because it does not need Runloop support.

NSLog(@"Dispatch_after");
    
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"In three seconds.");
});
Copy the code

Note, however, that dispatch_after does not execute tasks after a specified time, but submits them to the queue after a specified time.

Therefore, the time of the delay is not exact. That’s one of the downsides.

The second disadvantage is that a delayed Block of dispatch_after cannot be cancelled directly. But dispatch-Cancel offers a solution.

The implementation can be found in the Apple open Source code -libdispatchd > Dispatch Source > source.c, but I won’t go into details here:

Determine when, if it is now, and execute it asynchronously; Otherwise, a dispatch source is created to trigger dispatch_async at the specified time.

2.5 Single Executiondispatch_once

Requirement: singleton pattern.

  • dispatch_onceAllows developers to execute specified tasks safely and only once in a thread. This fits well with the singleton pattern.

Its parameters are:

  1. Predicate: marks that are executed once;
  2. Block: tasks that need to be executed once.
static TheClass *instance = nil;
+ (instance)sharedInstance 
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[TheClass alloc] init];
    });
    
    return instance;
}
Copy the code

GCD is thread-safe. Any thread attempting to access a critical section (that is, a task passed to dispatch_once) will be blocked if there is already one thread in the critical section until the critical section thread completes its operation.

Unfortunately, Swift cancels dispatch_once because it is too easy to implement singletons in Swift (just make the initialization method private and provide a static instance variable).

Here is a Swift version of dispatch_once:

// MARK: - DispatchQueue once
extension DispatchQueue {
    
    private static var _onceTracker = [String] ()/** Executes a block code, associated with a unique token, only once. The clode is thread safe and will only execute the code once even in the prescence of multithread calls. - parameter token: A unique reverse DNS style name suce as com.vectorfrom.
      
        or a GUID - parameter block: Block to execute once **/
      
    public class func once(token: String.block: () - >Void) {
        objc_sync_enter(self)
        defer {
            objc_sync_exit(self)}guard! _onceTracker.contains(token) else { return }
        _onceTracker.append(token)
        
        block()
    }
}
Copy the code

2.6 Concurrent Iterationdispatch_apply

Requirements: Iterating through a very, very large collection, using a for loop will take many, many events.

  • dispatch_applySubmits the specified task to the specified queue for the specified number of times, executes the task synchronously, and returns after all tasks are complete.

Speaking of people, dispatch_apply is a more advanced for loop that supports concurrent iterations. And it executes synchronously and must wait until all work is done before returning, just like the for loop.

Its parameters are:

  1. Iterations: The number of iterations required.
  2. Queue: The queue to which the iterated task is submitted.
  3. Block: What is the specific iteration task.
dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
    NSLog(@"dispatch_apply --- %zu --- %@", index, [NSThread currentThread]);
});
Copy the code

The above example is not worth using dispatch_apply: creating and running threads costs money (time, memory).

For simple iterations, using the for loop is far less expensive than dispatch_apply. You should only consider using Dispatch_apply if you need to iterate over very large collections.

Dispatch_apply on each queue (current primary thread) :

  • Main queue: deadlock (this is synchronous execution, after all);
  • Serial queues: Serial queues cancel out completelydispatch_applyParallel iteration is not as good as a for loop;
  • Parallel queues: Execute iterative tasks in parallel, which is a good choice, alsodispatch_applyThe meaning of.

2.7 Fence Methoddispatch_barrier_async

Requirement: Two sets of tasks are executed asynchronously, but the second set of tasks cannot be executed until the first group is complete.

  • dispatch_barrier_asyncA “fence” can be provided to separate the two groups of asynchronously executed tasks. After all the tasks submitted to the queue before the fence method are executed, the fence task can be executed. After the fence task is executed, the queue will resume its original execution state.

Its parameters are:

  1. Queue: Queue of tasks to be separated.
  2. Block: indicates the specific content of the fence task.
- (void)barrier_display {
    
    NSLog(@"Current thread -- %@", [NSThread currentThread]);
    
    dispatch_queue_t theConcurrentQueue = dispatch_queue_create("com.junes.serial.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_queue_t theQueue = theConcurrentQueue;
    
    dispatch_async(theQueue, ^{
        NSLog(@"Task 1 is underway."); // Simulate time-consuming tasks [NSThread sleepForTimeInterval:2]; NSLog(@"Task 1 completed.");
    });
    
    dispatch_async(theQueue, ^{
        NSLog(@"Mission 2 is underway."); // Simulate time-consuming tasks [NSThread sleepForTimeInterval:1]; NSLog(@"Mission 2 completed.");
    });
    
    
    
    dispatch_barrier_async(theQueue, ^{
        NSLog(@"================== Fence mission ==================");
    });
    
    
    dispatch_async(theQueue, ^{
        NSLog(@"Mission 3 is underway."); // Simulate time-consuming tasks [NSThread sleepForTimeInterval:4]; NSLog(@"Mission 3 completed.");
    });
    dispatch_async(theQueue, ^{
        NSLog(@"Mission 4 is underway."); // Simulate time-consuming tasks [NSThread sleepForTimeInterval:3]; NSLog(@"Mission 4 completed.");
    });
}
Copy the code

Take a look atdispatch_barrier_asyncdispatch_async

View source code:

  • dispatch_barrier_async

    void dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work)
    {
    	dispatch_continuation_t dc = _dispatch_continuation_alloc();
    	uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER;
    	dispatch_qos_t qos;
    
    	qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    	_dispatch_continuation_async(dq, dc, qos, dc_flags);
    }
    Copy the code
  • dispatch_async

    void dispatch_async(dispatch_queue_t dq, dispatch_block_t work)
    {
    	dispatch_continuation_t dc = _dispatch_continuation_alloc();
    	uintptr_t dc_flags = DC_FLAG_CONSUME;
    	dispatch_qos_t qos;
    
    	qos = _dispatch_continuation_init(dc, dq, work, 0, dc_flags);
    	_dispatch_continuation_async(dq, dc, qos, dc->dc_flags);
    }
    Copy the code

You can see that dispatch_barrier_async and dispatch_async opportunities are exactly the same, with the only difference

/ / / this is dispatch_barrier_async uintptr_t dc_flags = DC_FLAG_CONSUME | DC_FLAG_BARRIER; /// this is dispatch_async uintptr_t dc_flags = DC_FLAG_CONSUME;Copy the code

The only difference is creating dc_flags passed by dispatch_qOS_T qos.

Dispatch_barrier_async has an additional flag DC_FLAG_BARRIER.

This flag does not apply to global concurrent queues…

Take a look atdispatch_barrier_syncdispatch_sync

For dispatch_barrier_sync and dispatch_sync. View source code:

  • Dispatch_barrier_sync:

    void dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work)
    {
    	uintptr_t dc_flags = DC_FLAG_BARRIER | DC_FLAG_BLOCK;
    	if (unlikely(_dispatch_block_has_private_data(work))) {
    		return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    	}
    	_dispatch_barrier_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
    }
    
    
    static void _dispatch_barrier_sync_f(dispatch_queue_t dq, void *ctxt,
    		dispatch_function_t func, uintptr_t dc_flags)
    {
    	_dispatch_barrier_sync_f_inline(dq, ctxt, func, dc_flags);
    }
    
    Copy the code
  • Dispatch_sync:

    void dispatch_sync(dispatch_queue_t dq, dispatch_block_t work)
    {
    	uintptr_t dc_flags = DC_FLAG_BLOCK;
    	if (unlikely(_dispatch_block_has_private_data(work))) {
    		return _dispatch_sync_block_with_privdata(dq, work, dc_flags);
    	}
    	_dispatch_sync_f(dq, work, _dispatch_Block_invoke(work), dc_flags);
    }
    
    
    static void _dispatch_sync_f(dispatch_queue_t dq, void *ctxt, dispatch_function_t func,
    		uintptr_t dc_flags)
    {
    	_dispatch_sync_f_inline(dq, ctxt, func, dc_flags);
    }
    
    
    static inline void _dispatch_sync_f_inline(dispatch_queue_t dq, void *ctxt,
    		dispatch_function_t func, uintptr_t dc_flags)
    {
    	if(likely(dq->dq_width == 1)) {/// Serial queue execution herereturn _dispatch_barrier_sync_f(dq, ctxt, func, dc_flags);
    	}
    
    	if(unlikely(dx_metatype(dq) ! = _DISPATCH_LANE_TYPE)) { DISPATCH_CLIENT_CRASH(0,"Queue type doesn't support dispatch_sync");
    	}
    
    	dispatch_lane_t dl = upcast(dq)._dl;
    	// Global concurrent queues and queues bound to non-dispatch threads
    	// always fall into the slow case, see DISPATCH_ROOT_QUEUE_STATE_INIT_VALUE // this method is executed when global parallel queues are created through the stack belowif(unlikely(! _dispatch_queue_try_reserve_sync_width(dl))) {return _dispatch_sync_f_slow(dl, ctxt, func, 0, dl, dc_flags);
    	}
    
    	if (unlikely(dq->do_targetq->do_targetq)) {
    		return_dispatch_sync_recurse(dl, ctxt, func, dc_flags); } _dispatch_introspection_sync_begin(dl); // execute Block _dispatch_sync_invoke_and_complete(dl, CTXT, func DISPATCH_TRACE_ARG( _dispatch_trace_item_sync_push_pop(dq, ctxt, func, dc_flags))); }Copy the code

Don’t get it??

Look at the stack:

  • Dispatch_barrier_sync:

    Dispatch_barrier_sync ↘ _dispatch_barrier_sync_f

  • Dispatch_sync:

    Dispatch_sync ↘ _dispatch_SYNC_F ↘ _dispatch_SYNC_F_INLINE ↘ _dispatch_barrier_SYNc_f

If it’s a serial queue, the same method _dispatch_barrier_sync_f ends up being called

How the barrier method behaves on each queue (currently the main thread) :

The home side column Custom serial queues Global parallel queue Custom parallel queues
dispatch_barrier_async Serial queues make no sense Serial queues make no sense The equivalent ofdispatch_asyncThe purpose of the fence cannot be achieved A fence is placed between the previous and subsequent tasks. The fence task is executed after all previous tasks are completed, and the queue is restored to its original working state after completion
dispatch_barrier_sync A deadlock Serial execution of tasks

In the current master thread environment, validate one by one (no need for serial queues) :

  • Dispatch_barrier_async + Custom parallel queue

    Perfect fit, right?

    Custom concurrent queues are the best partner of dispatch_barrier_async.

  • Dispatch_barrier_async + Global parallel queue

    Exactly the same as dispatch_async, it doesn’t work for fencing purposes.

    Global concurrent queues, which may also be used by systems, should not be monopolized for fences.

  • Dispatch_barrier_sync + Primary queue

    Deadlock, above the source code interpretation can find the cause.

  • Dispatch_barrier_sync + User-defined serial queue

    What’s the point of fencing a serial queue… Dispatch_barrier_sync also blocks the thread.

  • Dispatch_barrier_sync + Global parallel queue

    Similar to dispatch_barrier_async + global parallel queue, no fencing effect…

  • Dispatch_barrier_sync + User-defined parallel queue

    It fits the bill, doesn’t it?

Conclusion:Custom parallel queuesIs a good helper of the fence method

2.8 scheduling groupdispatch_group_t

Requirement: Asynchronously execute several time-consuming tasks, and then return to the main thread to execute the tasks when all the time-consuming tasks are completed.

When you first see this task, you can also do the fence task. Is it necessary to spend time learning something new? And look down…

  • The Dispatch Group notifies the developer when the tasks for the entire Group are complete. These tasks can be synchronous, asynchronous, or even queued differently.

Monitoring the execution of such scattered tasks can be a headache for developers. Fortunately, there is a dispatch group dispatch_gourp_t to help developers keep track of these different tasks.

  • Creating a Scheduling Group
Dispatch_group_t = dispatch_group_create();Copy the code
  • Put tasks into a scheduling group

After creating a scheduling group, add tasks to the scheduling group. There are two ways to do this, but with different priorities:

  1. dispatch_group_async;

    Asynchronous request. The task is automatically completed, and the execution of its internal code is regarded as the task is completed.

    Network requests are also generally asynchronous requests. So as long as the request completes, the task is considered complete, but the task is not complete

    Suitable for synchronous completion of internal tasks, such as dealing with a very large set, or a computationally heavy task.

  2. dispatch_group_enter.

    Notify the scheduling group that a task has started. Tasks do not complete automatically, we need to use dispatch_group_leave to tell the dispatch group that a task has completed.

    Suitable for asynchronous completion of internal tasks, such as asynchronous network requests, file downloads.

    However, dispatch_group_Enter must be paired with dispatch_group_leave, otherwise a crash may occur.

Let’s test the applicability of the above questions and demonstrate how to use them.

- (void)group_validate {
    
    /// 创建一个调度组
    dispatch_group_t group = dispatch_group_create();
    
    
    dispatch_queue_t theGlobalQueue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t theSerialQueue = dispatch_queue_create("com.junes.serial.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t theConcurrentQueue = dispatch_queue_create("com.junes.serial.queue", DISPATCH_QUEUE_CONCURRENT); Dispatch_group_async (group, theGlobalQueue, ^{NSLog(@)"Start task 1 +++++++"); Sleep (2); NSLog(@"Mission 1 completed -----------------");
    });
    
    dispatch_group_async(group, theSerialQueue, ^{
        NSLog(@"Start task 2 +++++++"); Sleep (4); NSLog(@"Mission 2 completed -----------------");
    });
    
    dispatch_group_async(group, theConcurrentQueue, ^{
        NSLog(@"Start task 3 +++++++"); Dispatch_async (dispatch_get_global_queue(0, 0), ^{sleep(5); NSLog(@"Mission 3 is now truly completed -----------------");
        });
        
        NSLog(@"Task 3 has now been completed by dispatch_group_notify");
    });
    
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"All missions completed...");
    });
    
}
Copy the code

The red box of the resulting graph proves the theory.

Next, place “asynchronous network operations” into the dispatch group with dispatch_group_Enter and look again.

- (void)group_display {
    
    /// 创建一个调度组
    dispatch_group_t group = dispatch_group_create();
    
    
    dispatch_queue_t theGlobalQueue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t theSerialQueue = dispatch_queue_create("com.junes.serial.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t theConcurrentQueue = dispatch_queue_create("com.junes.serial.queue", DISPATCH_QUEUE_CONCURRENT); Dispatch_group_async (group, theGlobalQueue, ^{NSLog(@)"Start task 1 +++++++"); Sleep (2); NSLog(@"Mission 1 completed -----------------");
    });
    
    dispatch_group_async(group, theSerialQueue, ^{
        NSLog(@"Start task 2 +++++++"); Sleep (4); NSLog(@"Mission 2 completed -----------------"); }); dispatch_group_enter(group); /// simulate the asynchronous network request dispatch_async(theConcurrentQueue, ^{NSLog(@)"Start task 3 +++++++");
        
        sleep(5);
        
        NSLog(@"Mission 3 Completed -----------------");
        dispatch_group_leave(group);
    });
    
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"All missions completed...");
    });
    
    NSLog(@"Dispatch_group_notify is executed asynchronously and does not block the thread. I am the proof.");
}
Copy the code

This is normal with scheduling groups!

  • Task to complete
  1. dispatch_group_notify;

    Execute asynchronously. After all tasks in the specified scheduling group are complete, a Block is added to a specific queue.

    For dispatch_group_async tasks, the task is considered complete as long as the Block code execution is complete. (Whether or not there are asynchronous requests in the Block, as verified above)

    For dispatch_group_Enter tasks, you must use dispatch_group_leave to notify the dispatch group of the task completion.

  2. dispatch_group_wait.

    Synchronous execution blocks the thread.

    This method blocks the thread until all tasks are complete (or time out).

The use of dispatch_group_notify was demonstrated earlier. Let’s look at the use of dispatch_group_wait.

- (void)group_display {
    
    /// 创建一个调度组
    dispatch_group_t group = dispatch_group_create();
    
    
    dispatch_queue_t theGlobalQueue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t theSerialQueue = dispatch_queue_create("com.junes.serial.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t theConcurrentQueue = dispatch_queue_create("com.junes.serial.queue", DISPATCH_QUEUE_CONCURRENT); Dispatch_group_async (group, theGlobalQueue, ^{NSLog(@)"Start task 1 +++++++"); Sleep (2); NSLog(@"Mission 1 completed -----------------");
    });
    
    dispatch_group_async(group, theSerialQueue, ^{
        NSLog(@"Start task 2 +++++++"); Sleep (4); NSLog(@"Mission 2 completed -----------------"); }); dispatch_group_enter(group); /// simulate the asynchronous network request dispatch_async(theConcurrentQueue, ^{NSLog(@)"Start task 3 +++++++");
        
        sleep(5);
        
        NSLog(@"Mission 3 Completed -----------------");
        dispatch_group_leave(group);
    });
    
    NSLog(@"Dispatch_group_wait is about to lock up threads"); /// DISPATCH_TIME_FOREVER (DISPATCH_TIME_NOW (DISPATCH_TIME_FOREVER)) dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"Dispatch_group_wait releases thread"); Dispatch_async (dispatch_get_main_queue(), ^{NSLog(@"All missions completed.");
    });
}
Copy the code

As a reminder, the second parameter of dispatch_group_wait specifies when a timeout occurs. For convenience, Apple provides DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.

  1. DISPATCH_TIME_FOREVER:

    Never time out. If the task never completes, the thread will always block.

    If dispatch_group_leave number less than dispatch_group_enter, the result is worth looking forward to.

  2. DISPATCH_TIME_NOW:

    Time out immediately, no asynchrony has a chance to complete…

2.9 a semaphoredispatch_semaphore_t

Let’s start with some code:

__block int theNumber = 0; Dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"Task 1 has begun %@", [NSThread currentThread]);
    
    for (int i = 0; i < 10000000; ++i) {
        theNumber++;
    }
    
    NSLog(@"Task 1 completed %@", [NSThread currentThread]);
});

dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
    NSLog(@"Task 2 has begun %@", [NSThread currentThread]);
    
    for (int i = 0; i < 10000000; ++i) {
        theNumber++;
    }
    
    NSLog(@"Task 2 completed %@", [NSThread currentThread]);
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"theNumber = %d", theNumber);
});
Copy the code

GCD to use multiple threads to use the same resource example. The final result is not a simple number of loops * 2 (of course, a little bit more loops…).

In multithreaded programming, it is inevitable that multiple threads will use the same resource. If there is no locking mechanism, then the correctness of the program is lost.

To ensure proper GCD programming, resources must be locked when they are used (mainly for modifying resources).

The semaphore (dispatch_semaphore_t) is GCD’s locking mechanism. Meant to hold counting signals, Apple provides three apis for developers to use.

  1. dispatch_semaphore_create;

    Creates a semaphore from the initial value passed in.

    Negative values cannot be passed in. At run time, if the internal value is negative, the absolute value of this value is the number of threads waiting for resources.

  2. dispatch_semaphore_wait;

    Semaphore -1.

    When the result value after -1 is less than 0, the thread blocks and waits for resources in FIFO mode.

  3. dispatch_semaphore_signal.

    Semaphore plus one.

    When the result value after +1 is greater than 0, the waiting thread is woken up in FIFO.

Add a semaphore to the above problem code:

__block int theNumber = 0; Dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); Dispatch_group_t group = dispatch_group_create(); Dispatch_group_async (group, dispatch_get_global_queue(0, 0), ^{dispatch_get_global_queue(0, 0), DISPATCH_TIME_FOREVER); NSLog(@"Task 1 has begun %@", [NSThread currentThread]);
    
    for (int i = 0; i < 10000000; ++i) {
        theNumber++;
    }
    
    NSLog(@"Task 1 completed %@", [NSThread currentThread]); // dispatch_semaphore_signal(semaphore); }); Dispatch_group_async (dispatch_get_global_queue(0, 0)), ^{ Dispatch_semaphore_wait (semaphore, DISPATCH_TIME_FOREVER); NSLog(@"Task 2 has begun %@", [NSThread currentThread]);
    
    for (int i = 0; i < 10000000; ++i) {
        theNumber++;
    }
    
    NSLog(@"Task 2 completed %@", [NSThread currentThread]); // dispatch_semaphore_signal(semaphore); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"theNumber = %d", theNumber);
});
Copy the code

The problem was solved

2.10 Scheduling Resourcesdispatch_source

A dispatch source is an underlying data type that coordinates the processing of specific underlying system events.

Dispatch source has the following characteristics.

  1. When configuring a Dispatch source, you specify the events to monitor, the Dispatch quue, and the blocks to process the events.
  2. When an event occurs, the Dispatch source submits the specified Block to the specified queue for execution.
  3. To prevent events from being backlogged to the Dispatch queue, the Dispatch source adopts the event merge mechanism. If the new event arrives before the previous event is processed, the old and new events are merged. Depending on the event type, the merge operation may replace old events or update information about old events.
  4. The Dispatch source provides a succession of events and remains associated with the Dispatch Queue until cancelled.
  5. Dispatch sources are very lightweight, have very little CPU load, and consume almost no resources. It is a wrapper around the BSD kernel’s usual feature, Kqueue, which is the technology used in application programming to handle events that occur in XUN. Kqueue is arguably the best way for applications to handle the rich variety of events in A XUN kernel.

dispatch_sourceThe type of

/* * When an event is fired with a high frequency at the same time, the Dispatch Source accumulates these responses as Adds and processes them when the system is idle. * If the firing frequency is scattered, the Dispatch Source responds to these events separately. */ DISPATCH_SOURCE_TYPE_DATA_ADD Custom events. Add DISPATCH_SOURCE_TYPE_DATA_OR custom events. Variable OR DISPATCH_SOURCE_TYPE_DATA_REPLACE custom events, Variable Replace DISPATCH_SOURCE_TYPE_MACH_SEND DISPATCH_SOURCE_TYPE_MACH_RECV the MACH port receives DISPATCH_SOURCE_TYPE_MACH_RECV DISPATCH_SOURCE_TYPE_MEMORYPRESSURE Memory alerts The DISPATCH_SOURCE_TYPE_PROC process listens for, for example, the exit of a process, the creation of one or more child threads, or the receipt of UNIX signals by a process DISPATCH_SOURCE_TYPE_READ IO operation, For example, file operations and socket operations. DISPATCH_SOURCE_TYPE_SIGNAL DISPATCH_SOURCE_TYPE_TIMER Timer DISPATCH_SOURCE_TYPE_VNODE DISPATCH_SOURCE_TYPE_WRITE IO operations such as DISPATCH_MACH_SEND_DEAD for file operations and socket operationsCopy the code

usedispatch source

Of all dispatch source types, the most common is DISPATCH_SOURCE_TYPE_TIMER.

/ *! * @abstract: Creates the specified dispatchsource
 * @param type* Diapatch to createsourceSpecies. Must be its type constant. * * @param Handle * The underlying system handle to monitor. This parameter bytypeDetermines the constant provided in the parameter. Pass a zero. * * @param mask * Flag mask to specify which events are required. This parameter bytypeDetermines the constant provided in the parameter. Pass a zero. * * @param queue * Which queue processes events. */ dispatch_source_t dispatch_source_create(dispatch_source_type_ttype,
	uintptr_t handle,
	unsigned long mask,
	dispatch_queue_t _Nullable queue);
Copy the code

Dispatch_source_create The created dispatch source is suspended and needs to be manually woken up.

configurationdispatch source

/ *! * @abstract * Configures the dispatch for this timer typesource* * @param start * When to start receiving events. See dispatch_time() and dispatch_walltime() for more information. * * @param interval * Timer interval in nanoseconds. DISPATCH_TIME_FOREVER is used once. * * @param leeway * Allowable error in nanosecond units. */ void dispatch_source_set_timer(dispatch_source_tsource, dispatch_time_t start, uint64_t interval, uint64_t leeway); / *! * @abstract * is the given dispatchsourceSet event handling. * * @paramsource* Dispatches to be configured. * * @param Handler * Handles the callback when an event is received. The former is a Block and the latter is a function. */ void dispatch_source_set_event_handler(dispatch_source_tsource,
	dispatch_block_t _Nullable handler);
void
dispatch_source_set_event_handler_f(dispatch_source_t source,
	dispatch_function_t _Nullable handler);
Copy the code

Dispatch_source_set_event_handler uses a Block as a callback. Dispatch_source_set_event_handler_f uses function Pointers as callbacks.

Start, suspend, canceldispatch source

// start the specified dispatchsourcevoid dispatch_active(dispatch_object_t object); /// Wake up the specified dispatchsourcevoid dispatch_resume(dispatch_object_t object); /// Suspend the specified dispatchsource. void dispatch_suspend(dispatch_object_t object); // cancel the specified dispatchsource
void
dispatch_source_cancel(dispatch_source_t source); /// View the specified dispatchsourceWhether it has been cancelled. Cancelled return zero, otherwise non-zero. long dispatch_source_testcancel(dispatch_source_tsource); / / / cancel dispatchsourceAfter the last event processing. void dispatch_source_set_cancel_handler(dispatch_source_tsource,
	dispatch_block_t _Nullable handler);
Copy the code

A few things need to be explained about the above method:

  1. The newly created dispatch source is suspended and dispatch_active or dispatch_resume must be manually called to work.

  2. All events that occur when the Dispatch source is in the suspend state are accumulated. The Dispatch source is restored, but instead of passing all events at once, it is first merged into a single event. Only dispatch_resume can be used to restore the dispatch_source service.

  3. Canceling the dispatch source is an asynchronous operation. After disaptch_source_cancel is called, no new events are delivered, but the event being processed is continued;

  4. After the last event is processed, the dispatch source executes its own cancellation handler (dispatch_source_set_cancel_handler). In the cancel processor, memory and resources can be freed.

  5. Be sure to cancel the Dispatch source while it is working properly. Never cancel the dispatch source by calling disaptch_source_cancel while in the suspend state;

Ok, last full example:

__block int countDown = 6; Dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue()); // configure the timer dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0); // set timer event handling dispatch_source_set_event_handler(timer, ^{// execute when timer is triggeredif (countDown <= 0) {
        dispatch_source_cancel(timer);
        
        NSLog(@"Countdown is over ~~~");
    }
    else {
        NSLog(@"Countdown with %d seconds left...", countDown); } countDown--; }); // start timer dispatch_active(timer);Copy the code

(Added since 20200625 –)

You might have friends who created and started the timer this way, but didn’t call back. The principle is that there is no dispatch_source_cancel operation. The following solutions are provided

  1. Add dispatch_source_cancel to handler;

    Principle: are written explicitly, of course exist!

    If (NO) dispatch_source_cancel(timer);

  2. Set the timer to a member variable or property (but be aware of memory leaks from circular references at this point).

    Principle: When released as a member variable, cancel is automatically performed.

(Added on 20200625 — end)

NSOperation and NSOperationQueue

NSOperation and NSOperationQueue are based on a higher level of ENCAPSULATION in GCD and are fully object-oriented. They correspond to GCD tasks and queues respectively. NSOperation and NSOperationQueue are easier to use and more readable than GCD, but slightly more overhead.

Borrow a mind map of big guy to illustrate the relevant knowledge points:

1.NSOperationoperation

NSOperation translates to “operation” and corresponds to tasks in the GCD.

NSOperation is an abstract class and cannot be used directly by itself, but Apple has two subclasses for us: NSInvocationOperation and NSBlockOperation. Of course, you can also customize subclasses (AFNetworking has a custom subclass, AFURLConnectionOperation).

NSOperation is a one-time task. The task can be executed only once and cannot be executed again after it is executed.

NSoperation has three important states:

  1. IsReady: returns YES to indicate that it isReady to be executed, otherwise indicates that some preparation work has not been completed;
  2. IsExecuting: If you return YES, you are executing.
  3. IsFinished: Return YES to complete (cancelled)isCancelledAlso considered finished).

1.1 startNSOperationThere are two ways

  1. NSOperationCan cooperate withNSOperationQueueUse;

    You just add NSOperation to the NSOperationQueue

    The NSOperation is fetched from the NSOperationQueue and added to a new thread for execution, which defaults to asynchronous execution.

  2. NSOperationIt can also be used independently.

    Use the start method to start the operation.

    This mode is executed synchronously by default.

    If the NSOperation is not ready (isReady returns NO), an exception is raised.

It is recommended that NSOperation be used together with NSOperationQueue.

1.2 Operation Dependencydependencies

Dependencies are a convenient choice when you need to perform NSOperations in a particular order.

AddDependency: and removeDepencency can be used to add or remove dependencies. By default, if a NSOperation dependency is not executed, it is never ready; Once its last execution is complete, the NSOperation is ready.

The dependency rules for NSOperation do not distinguish whether a dependency operation is actually complete (cancelled is also considered complete). However, the developer can decide whether to continue to complete the NSOperation if the dependency operation is cancelled or not actually completed.

1.3 Completing the callbackcompletionBlock

After the NSOperation completes, the completionBlock is called back by the thread executing the NSOperation.

But this is done, this is really done, cancel can’t be triggered.

The completionBlock is a property, set directly by the setter.

1.4 Conform to KVO attributes

Some properties of the NSOperation class are KVC and KVO compliant.

  • isCancelled

    Whether to cancel. Read-only.

  • isAsynchronous

    Whether it is executed asynchronously. Read-only.

  • isExecuting

    Whether the command is being executed. Read-only.

  • isFinished

    Whether it is completed. Read-only.

  • isReady

    Is it ready to be executed? Read-only.

  • dependencies

    All the dependencies. Read-only.

  • queuePriority

    Queue priority. It can be read and written.

  • completionBlock

    Complete the callback Block. It can be read and written.

When subclassing NSOperation, you must implement KVC and KVO if you provide custom implementations for the above attributes. Also, if you add some attributes, it is best to implement KVC and KVO as well.

1.5 a subclassNSBlockOperation

NSBlockOperation stores tasks as blocks and is very simple to use.

  • createNSBlockOperation
Class method blockOperationWithBlock: NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"I'm a task for NSBlockOperation."); }]; NSBlockOperation *blockOperation2 = [[NSBlockOperation alloc] init];Copy the code

The first method is usually used. The first way, after all, directly creates the instance and joins the task.

  • forNSBlockOperationAdd tasks

If you execute blockOperation2 below, you will find no response. Which makes sense, since there are no missions.

We can use the addExecutionBlock: method to add tasks to NSBlockOperation.

[blockOperation2 addExecutionBlock:^{
    NSLog(@"I'm a task for NSBlockOperation.");
}];
Copy the code

You can use addExecutionBlock: to add tasks to an NSBlockOperation instance. Both tasks added via addExecutionBlock and those passed in when blockOperationWithBlock is initialized are stored in the instance property executionBlocks. Yes, an NSBlockOperation instance can have multiple tasks.

Execute directly on the current thread and block the current thread.

1.6 a subclassNSInvocationOperation

There are also two ways to create an NSInvocationOperation:

// Create method 1: Target/selector/object NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(theInvocationSelector) object:nil]; // Select * from the following table. Method theMethod = class_getInstanceMethod([self class], @selector(theInvocationSelector)); // const char *theType = method_getTypeEncoding(theMethod); // NSMethodSignature *theMethodSignature = [NSMethodSignaturesignatureWithObjCTypes:theType]; NSMethodSignature *theMethodSignature = [self methodSignatureForSelector:@selector(theInvocationSelector)]; NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:theMethodSignature]; NSInvocationOperation *invicationOperation2 = [[NSInvocationOperation alloc] initWithInvocation:invocation];Copy the code

The first way is easier to understand. Second way to incoming NSInvocation instance, do not know what is friend NSInvocation can be understood as can pass multiple parameters performSelector: withObject: Just fine (or take a look at the ios-ns invocation and improved performSelector).

1.7 Custom Subclasses

Subclasses of custom NSOperation have quite a few caveats.

The default implementation of several important NSOperation methods is as follows:

In addition, NSOperation has a very important concept: state. When these states change, you need to send a KVO notification, also using the big guy’s diagram:

If you only need to customize non-asynchronous (that is, synchronous) NSOperation, you just need to override the main method. If you also want to override getters and setters that access NSOperation data, make sure those methods are thread-safe.

By default, the main method does nothing. When overriding this method, do not call [super main].

At the same time, the main method is automatically executed in an auto-release pool, with no additional auto-release pool created.

But for asynchronous NSOperation, at least the following methods or attributes should be overridden:

  1. start

    Start operations asynchronously.

    Once started, update the execution property of the operation.

    Before start, you must check whether it has been cancelled. If it has been cancelled

    [super start] must not be called.

  2. asynchronous

    Return YES, preferably for KVO notification.

  3. executing

    The thread safely returns the execution status of the operation.

    KVO notifications must be sent when values change. KVO keyPath isExecuting.

  4. finished

    The thread safely returns the completed status of the operation.

    KVO notifications must be sent when values change.

    Once the action is canceled, the task is considered complete. (The action queue does not remove the action until the task is complete)

Of course, overwriting the above attributes is a minimum requirement, and we will definitely need to overwrite more in real development.

Maintaining Operation Object States can be achieved by Maintaining Operation Object States.

attribute The KVO keyPath note
ready isReady In general, there is no need to override this property. However, if the value of ready is determined by external factors, the developer is better off providing a custom implementation. Cancels a dependency that is waiting for completionNSOperation, these dependencies will be ignored and the value of this property will be updated to YES to indicate normal operation. At this point, the action queue will remove it more quickly.
executing isExecuting If you rewritestartMethod, you must override the property. And issue a KVO notification when its value changes
finished isFinished If you rewritestartMethod, you must override the property in theNSOperationSet the value to YES and issue a KVO notification when execution completes or is cancelled
cancelled isCancelled Issuing KVO notifications for this property is not recommended, after allcancelWhen theNSOperationThe properties of thereadyfinishedThe value of

Pay attention to

  1. The customNSOperation, be sure to supportcancelOperation.

    The main process for executing a task should periodically check the Cancelled property. If YES is returned, NSOperation should clean up and exit as soon as possible.

    If you override the start method, you must include an early check to cancel the operation.

  2. Self-managed attributesexecutingfinishedBe sure to be thereexecutingProperty value set back to NOfinishedProperty value is set to YES.

    Even if it is cancelled before execution begins, be sure to take care of the changes.

Learn AFNetworking

Of course, this is not the latest version, but version 2.3.1 of AFNetworking.

Versions after AFNetworking 3.0 make full use of NSURLSession. NSURLSession itself is asynchronous and does not require a runloop. Therefore, versions after 3.0 do not use NSOperation.

AFURLConnectionOperation is an asynchronous NSOperation subclass. Take a look:

  • Start the operation-start
- (void)start { /// # 1 [self.lock lock]; / / / # 2 if [the self isCancelled]) {/ / / # 2.1 [self performSelector: @ the selector (cancelConnection) onThread: [[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } / / / # 3 else if ([self isReady]) {/ / / # 3.1 self. State = AFOperationExecutingState; // # 3.2 [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil  waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } /// # 4 [self.lock unlock]; }Copy the code
  1. useNSRecursiveLock(recursive lock) lock, ensure thread safety;
  2. checkNSOperationWhether it has been cancelled;

    2.1 Cancel operations using subthreads.

  3. checkNSOperationIs ready;

    3.1 Update the status state and send a KVO notification. Recursive locking is also used internally;

    3.2 Starting a network request through a child thread.

  4. When the operation is complete, theNSRecursiveLock(Recursive lock) unlock.

As you can see from start, AFNetworking uses child threads to perform cancellation operations and real tasks. Take a look:

  • Dedicated child thread+networkRequestThread
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    /// # 2.1
    @autoreleasepool {
        /// # 2.2
        [[NSThread currentThread] setName:@"AFNetworking"];
    
        /// # 2.3
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        /// # 2.4
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    /// # 1
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        /// # 2
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });
    
    return _networkRequestThread;
}
Copy the code
  1. usedispatch_onceTo create child threads;
  2. Specify the thread entry asnetworkRequestThreadEntryPoint.

    2.1 Perform operations in AutoReleasepool for easy management;

    2.2 Change the thread name for easy use;

    2.3 Focus on creating NSRunloop objects to keep threads active and execute network requests with NSURLConnection;

    2.4 Focus on creating an NSMechPort object to achieve communication between threads and ensure that the child threads are always created to process logic.

Having looked at the child thread, let’s look at canceling connection (this is not the -cancel method) :

  • Cancel the connectioncancelConnection
- (void)cancelConnection {//... ///# 1
    if(! [self isFinished]) { ///# 2
        if (self.connection) {
            /// # 2.1
            [self.connection cancel];
            /// # 2.2[self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:error]; } / / /# 3
        else{ self.error = error; [self finish]; }}}Copy the code
  1. Check whether the user has finished.
  2. checkconnectionWhether the attribute exists, if so;

    2.1 Cancel the current network request;

    2.2 call onnection: didFailWithError: save error information, within the cleanup and perform the end finish operation.

  3. ifconnectionDoes not exist.

    Save the error message, and then directly execute the finish operation.

  • Put an end to the operation-finish
- (void)finish { [self.lock lock]; self.state = AFOperationFinishedState; [self.lock unlock]; / / /... }Copy the code

It is straightforward to use -setState: to manage its own state and issue KVO notifications while keeping the thread safe.

  • Perform a task-operationDidStart
- (void)operationDidStart {
    /// # 1
    [self.lock lock];
    /// # 2
    if(! [self isCancelled]) { /// ... } [self.lock unlock]; }Copy the code
  1. It’s still used hereNSRecursiveLock(Recursive locking) keep thread safe;
  2. Double check to see if it has been cancelled (in full compliance with Apple’s explicit “periodic review”cancelledProperties.” );

Okay, finally, a look at Apple’s beloved cancel operation

  • Cancel the operation-cancel
- (void)cancel {
    /// # 1
    [self.lock lock];
    /// # 2
    if(! [self isFinished] && ! [self isCancelled]) { ///# 2.1
        [super cancel];

        /// # 2.2
        if ([self isExecuting]) {
            [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
        }
    }
    [self.lock unlock];
}
Copy the code
  1. Still useNSRecursiveLock(Recursive locking) keep thread safe;
  2. Check your status if it hasfinishedcancelled, that also in addition to understand the lock also have no number of execution.

    2.1 Call [super cancel]; (Main and start must not call superclass methods)

    2.2 Checking whether you are executing a flight. If executing, cancel the connection task as in -start.

As you can see, NSOperation itself has a -cancel method. But there is also a cancelConnection that needs to handle its own task.

After all, this is an NSOperation and its state depends not only on the business logic but also on communicating with its parent. AFNetworking overwrites the getters for ready, Executing, and FINISHED.

- (BOOL)isReady {
    return self.state == AFOperationReadyState && [super isReady];
}

- (BOOL)isExecuting {
    return self.state == AFOperationExecutingState;
}

- (BOOL)isFinished {
    return self.state == AFOperationFinishedState;
}
Copy the code

Finally, copy two high-end operations

  1. qualityOfService

    Quality of service. Said NSOperation priority when access to system resources, is NSQualityOfServiceDefault by default.

    The highest priority for NSQualityOfServiceUserInteractive.

  2. queuePriority

    Queue priority. Said NSOperation relative priority, in the operation queue is NSOperationQueuePriorityNormal by default.

    In the unified operation queue, NSOperation with higher priority will be executed first, provided ready is YES.

    The highest priority for NSOperationQueuePriorityVeryHigh. Funny, that’s kind of a fancy name…

2,NSOperationQueue

NSOperationQueue, an operation queue that performs NSOperation based on priority and readiness.

Once an NSOpertaion has been added to the NSOperationQueue, it cannot be removed directly, remaining in the operation queue until it reports that it completed the operation.

After an NSOperation instance is added to the NSOperationQueue, its asynchronous has no effect. At this point, NSOperationQueue simply calls the GCD to execute it asynchronously.

For the NSOperation whose ready is YES, the operation queue selects the execution with the largest queuePriority.

2.1 Creating an Operation Queue

There are two types of NSOperationQueue: primary queue and custom queue.

NSOperationQueue *theMainOperationQueue = [NSOperationQueue mainQueue]; NSOperationQueue *theOperationQueue = [[NSOperationQueue alloc] init];Copy the code

All NSOperations added to the main queue are executed on the main thread.

NSOperation added to a custom queue is executed concurrently by default on child threads.

2.2 toNSOperationQueueAdd operation

There are several ways to add operations to NSOperationQueue.

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"operation1");
}];

NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"operation2");
}];

NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"operation3");
}];

NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"operation4");
}];

NSBlockOperation *operation5 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"operation5"); }]; // add a single NSOperation [theOperationQueue addOperation:operation1]; // Add multiple nsOperations [theOperationQueue addOperations:@[operation2, operation3, operation4, operation5]waitUntilFinished:NO]; [theOperationQueue addOperationWithBlock:^{NSLog(@)"addOperationWithBlock");
}];
Copy the code

For – addOperation: with – addOperations: waitUntilFinished: in terms of a NSOperation a most only operate in a queue. If the NSOperation already in a queue, then this method will be thrown NSInvalidArgumentException; Similarly, if an NSOperation is being executed, this exception will be thrown. Also, this exception is thrown even if the second addition uses the same queue.

What need reminds is, – addOperations: waitUntilFinished: the second argument if YES, then will block the current thread, until the NSOperation all finished in the first parameter.

One last exception: -addBarrierBlock:. Add barrier method (dispatch_barrier_asyn).

2.3 NSOperationQueueControl the number of concurrent requests

NSOperationQueue has an attribute called maxConcurrentOperationCount. The value of this property controls the maximum number of CONCURRENT NSOperations that can be executed concurrently in an operation queue.

MaxConcurrentOperationCount defaults to 1, that is, not limit. At the same time, Apple also recommends us to set this value, which will enable the system to set the maximum value according to system conditions.

2.4 Pause/Resume operationssuspended

Suspended is really just a property. When we set it to YES, we pause the queue; When it is set to NO, the queue is restored.

By pause, we do not pause immediately after setting, but rather do not continue after executing the action that is currently being performed.

2.5 Canceling Operations-cancelAllOperations

CancelAllOperations cancels all operations in the queue directly. It’s all operations…

Unlike Suspended, suspended pauses can be resumed. And a cancellation here is really a cancellation.

2.6 Operation SynchronizationwaitUntilAllOperationsAreFinished

After calling this method, the current thread blocks until all tasks in the queue are complete.


3. Compare GCD withNSOperationQueue

Finally, compare GCD with NSOperationQueue:

Use GCD for simple tasks.

If you need to control concurrency, cancel tasks, add dependencies, etc., use NSOperation Queue. It’s just that a lot of times you need to subclass NSOperation…

Locks in iOS

There are many types of locks available in iOS, but here’s how ibireme’s OSSpinLock is no longer safe:

Of course, there are many locking schemes, such as the use of serial queue, fence method, scheduling group can also achieve the purpose of locking. But only real locks are discussed here.

To start with a few concepts (see sub-Wikipedia) :

  • Critical section: a block of code that accesses a common resource. A piece of code cannot be executed concurrently, that is, two threads cannot execute the code at the same time.

  • Spin lock: The thread repeatedly checks that the lock variable is available. During this process, the thread keeps running, so it’s a busy wait. Spin-locks effectively avoid the scheduling overhead of the process context, which is useful when threads are blocked for short periods of time. However, single-core single-thread cpus are not suitable for spinlocks.

  • Mutex: Mechanism that prevents two threads from reading or writing to the same common resource (such as a global variable, which is a mutex) at the same time. This is accomplished by slicing code into critical sections one by one. Threads waiting for the mutex go to sleep and need to perform a context switch when awakened.

  • Read/write locks: also known as shared-mutex locks and multi-reader singletons locks. Used to solve the problem of multithreading to read and write public resources. Read operations can be concurrently reentrant, and write operations are mutually exclusive. Read/write locks are usually implemented with mutex, condition variables, and semaphores.

  • Conditional lock: Conditional variable. The task goes to sleep when certain resource requirements are not met, and the condition lock is locked. When a resource is allocated, the condition lock is released and the task continues. Condition variables always come with mutex,

  • Recursive locking: A recursive lock can be locked multiple times by a thread without causing deadlock problems. A recursive lock keeps track of how many times it is locked, and the lock can only be released if the number of unlock times balances the number of locks. Recursive locking applies to recursive methods.

  • Semaphore: A synchronization object used to hold a count between 0 and a specified maximum value. The thread completes a wait for the semaphore, counting -1; The thread completes a signal (release) on the semaphore, counting to +1. When the count is 0 or less, the thread must wait for the semaphore to know that its value is greater than 0. A semaphore applies to a shared resource that can only be used by a limited number of users at the same time, and is a lock that does not require a busy wait.

Let’s start with the classic scene of snatching train tickets:


- (void)trainTicket {
    
    self.trainTicketRemainder = 10000;
    
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
      
        for (int i = 0; i < 1000; ++i) {

            [self buyTrainTicket];
            sleep(0.1);
        }
    });
    
    dispatch_async(queue, ^{
      
        for (int i = 0; i < 1000; ++i) {
            
            [self buyTrainTicket];
            sleep(0.2);
        }
    });
    
}


- (void)buyTrainTicket {
    if (self.trainTicketRemainder < 1) {
        NSLog(@"Not enough tickets...");
        return;
    }
    
    self.trainTicketRemainder--;
    
    NSLog(@"Ticket sold successfully, current margin: %d", self.trainTicketRemainder);
    
}
Copy the code

As a general rule, the number of logs in the last printed log should be 10,000-1000 * 2 = 8000. But the number here is 8028. Obviously, this violates the correctness of the program.

Problems of the main reason lies in the two different threads at the same time on a Shared resource (self. TrainTicketRemainder) modified. To avoid this problem, we need to lock the thread.

The principle of locking is also not difficult. Here is a pseudocode:

do(Acquire Lock /// Acquire Critical section /// Release lock /// reflection section /// non-critical section)Copy the code

For the example above, can be self. TrainTicketRemainder — — the code as a critical section.

1.OSSpinLockspinlocks

As you can see from the graph above, this lock performs best, but it is no longer secure.

A brief description of the unsafe situation here: while a low-priority thread is holding a lock, the high-priority thread will be in the busy wait state of OSSpinLock and consume a lot of CPU time. This prevents the low-priority thread from taking CPU time, which causes the low-priority thread to be unable to complete the task and release the lock. Move on to OSSpinLock, which is no longer safe

This problem is known as priority inversion. (It is recommended to look at the thread quality of service qOS_Class or the thread priority of the global queue in 2.1 Creating a queue.)

Using OSSpinLock requires #import

OS_SPINLOCK_INIT The default value is 0, which is greater than 0 in locked states and 0 in unlocked states. OSSpinLock theOSSpinLock = OS_SPINLOCK_INIT; OSSpinLock (&theosspinlock); // @param __lock: OSSpinLock (&theosspinlock); OSSpinLockUnlock(&theosspinlock); OSSpinLock (&theosspinlock); // @discussion attempts to lock, can lock, and returns YES, // @param __lock: OSSpinLockTry(&theosspinlock);Copy the code

OSSpinLock has been deprecated since iOS 10. Use OS_UNfair_lock instead.

Make a list:

The lock species note
OSSpinLock spinlocks It is not secure. IOS 10 is enabled
os_unfair_lock The mutex alternativeOSSpinLock
pthread_mutex The mutex PTHREAD_MUTEX_NORMAL,#import <pthread.h>
pthread_mutex (recursive) Recursive locking PTHREAD_MUTEX_RECURSIVE,#import <pthread.h>
pthread_mutex (cond) Conditions for the lock pthread_cond_t,#import <pthread.h>
pthread_rwlock Read-write lock Read operations are reentrant, and write operations are mutually exclusive
@synchronized The mutex Poor performance and inability to lock objects with memory address changes
NSLock The mutex encapsulationpthread_mutex
NSRecursiveLock Recursive locking encapsulationpthread_mutex (recursive)
NSCondition Conditions for the lock encapsulationpthread_mutex (cond)
NSConditionLock Conditions for the lock Specific conditional values can be specified

2,os_unfair_lockThe mutex

Os_unfair_lock Is recommended by Apple to replace insecure OSSpinLock, but only for iOS 10 or later.

Os_unfair_lock is a mutex in which waiting threads do not wait as busy as spin locks, but sleep.

To use OS_UNfair_LOCK, #import < OS /lock.h> is required.

Os_unfair_lock OS_UNfair_lock theOs_unfair_lock = OS_UNFAIR_LOCK_INIT; // @param lock: os_UNfair_lock (&theOS_unfair_lock); Os_unfair_lock_unlock (&theOS_unfair_lock); // @discussion attempts to lock, can be locked and return YES, otherwise return NO // @param lock: Os_unfair_lock_trylock (&theOs_unfair_lock);Copy the code

3,pthread_mutexThe mutex

Pthreads stand for POSIX threads, which define a set of thread-related apis across platforms.

Pthread_mutex can be a mutex.

Using pthread_mutex requires #import

// define an attribute variable pthread_mutexattr_t attr; Pthread_mutexattr_init (&attr); pthread_mutexattr_init(&attr); // @param __lock: address of attribute pthread_mutexattr_t // @paramtype: Lock type pthread_mutexattr_setType (&attr, PTHREAD_MUTEX_NORMAL); // define a lock variable pthread_mutex_t mutex; // @param mutex: the address of the lock // @param attr: Address of attribute pthread_mutexattr_t pthread_mutex_(&mutex, &attr); Pthread_mutexattr_destroy (&attr); pthread_mutexattr_destroy(&attr); Pthread_mutex_lock (&mutex); pthread_mutex_lock(&mutex); Pthread_mutex_unlock (&mutex); pthread_mutex_unlock(&mutex); Pthread_mutex_destroy (&mutex); pthread_mutex_destroy(&mutex);Copy the code

The second argument to the pthread_mutexattr_setType method represents the type of the lock. There are four types:

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

When the type is PTHREAD_MUTEX_DEFAULT, it is equivalent to null. So it can be rewritten as:

pthread_mutexattr_settype(&attr, null);
Copy the code

4,pthread_mutex ( recursive )Recursive locking

In the previous section, you saw that the second parameter to the pthread_mutexattr_setType method has multiple values. If this value is passed to PTHREAD_MUTEX_RECURSIVE, the pthread_mutex initialized by the attribute setting this value is a recursive lock.

If it’s a mutex, or a mutex, and a thread locks the same lock more than once, it’s bound to cause thinking. But recursive locking allows a thread to lock the same lock more than once without causing deadlock problems. However, the recursive lock is not actually released until the number of unlock times is balanced by the number of lock times.

Using pthread_mutex requires #import

Pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_RECURSIVE) PTHREAD_MUTEX_RECURSIVE

But here’s an example:

- (void)display_PTHREAD_MUTEX_RECURSIVE {/// Define an attribute pthread_mutexattr_t attr; // initialize the attribute pthread_mutexattr_init(&attr); // set the lock type pthread_mutexattr_setType (&attr, PTHREAD_MUTEX_RECURSIVE); // initialize lock pthread_mutex_init(&mutex, &attr); Pthread_mutexattr_destroy (&attr); [self test_PTHREAD_MUTEX_RECURSIVE]; Pthread_mutex_destroy (&mutex); } - (void)test_PTHREAD_MUTEX_RECURSIVE { static int count = 5; // The first time I entered the database, I entered the database. Pthread_mutex_lock (&mutex); NSLog(@"Lock % @", [NSThread currentThread]);
    
    if (count > 0) {
        count--;
        [self test_PTHREAD_MUTEX_RECURSIVE];
    }
    
    NSLog(@"Unlock % @", [NSThread currentThread]);
    pthread_mutex_unlock(&mutex);
    
}
Copy the code

5,pthread_mutexConditions for the lock

Pthread_mutex can also act as a conditional lock in addition to mutex and recursive locks.

However, pthread_mutex needs the conditional variable pthread_cond_t to act as a conditional lock.

Using pthread_mutex requires #import

// initialize the lock with the default pthread_mutex_init(&mutex, NULL); // define a condition variable pthread_cond_t cond; // initialize the condition variable pthread_cond_init(&cond, NULL); /// wait condition (when entering sleep, release mutex lock; Pthread_cond_wait (&cond, &mutex); Pthread_cond_signal (&cond); pthread_cond_signal(&cond); /// Wake up all threads waiting for the condition pthread_cond_broadcast(&cond); Pthread_cond_destroy (&cond); Pthread_mutex_destroy (&mutex);Copy the code

Conditional locks are not used in many different ways. Use producer-consumer to demonstrate this.

Let’s define a few variables:

Pthread_mutex_t mutex; Pthread_cond_t cond; NSMutableArray *shop;Copy the code

The code:

- (void)setup_pthread_cond {/// initialize the lock using the default pthread_mutex_init(&mutex, NULL); // initialize the condition variable pthread_cond_init(&cond, NULL); /// Wake up all threads waiting for the condition pthread_cond_broadcast(&cond); shop = [NSMutableArray array]; NSLog(@"Please begin your show..."); dispatch_queue_t theQueue = dispatch_get_global_queue(0, 0); dispatch_async(theQueue, ^{ [self produce]; }); dispatch_async(theQueue, ^{ [self buy]; }); } // pretend to be the producer - (void)produce {while (true) { pthread_mutex_lock(&mutex); Sleep (0.1);if (shop.count > 5) {
            NSLog(@"The store is full. We can't make any more."); pthread_cond_wait(&cond, &mutex); } /// throw the product into the shop [shop addObject:@"fan"];
        NSLog(@"Produced a fan"); /// Wake up a waiting thread pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); }} // pretend to be a consumer - (void)buy {while (true) { pthread_mutex_lock(&mutex); // enter the wait (enter sleep, release _mutex; Re-lock _mutex when awakened)if (shop.count < 1) {
            NSLog(@"I can't get it now. I'll wait.");
            pthread_cond_wait(&cond, &mutex);
        }
        
        
        [shop removeObjectAtIndex:0];
        NSLog(@"Finally got it. It wasn't easy."); pthread_cond_signal(&cond); pthread_mutex_unlock(&mutex); }}Copy the code

Producer-consumer scenarios using conditional locks are proposed.

I have to say that the producer-consumer model works well for the killer call cycle.

6,pthread_rwlockRead-write lock

Pthread_rwlock, on the shoe. Also known as “share-mutex” and “multi-reader singleton” locks. Used to solve the problem of multithreading to read and write public resources. Read operations can be concurrently reentrant, and write operations are mutually exclusive.

Using pthread_rwlock requires #import

pthread_rwlock_t rwlock; // initialize lock pthread_rwlock_init(&rwlock, NULL); Pthread_rwlock_rdlock (&rwlock); /// read - try to lock pthread_rwlock_tryrdlock(&rwlock); // write - lock pthread_rwlock_wrlock(&rwlock); // write - try to lock pthread_rwlock_trywrlock(&rwlock); / / / unlock pthread_rwlock_unlock (& rwlock); Pthread_rwlock_destroy (&rwlock);Copy the code

Code demo:

- (void)setup_pthread_rwlock { // pthread_rwlock_t rwlock; // initialize lock pthread_rwlock_init(&rwlock, NULL); dispatch_queue_t theQueue = dispatch_get_global_queue(0, 0);for (int i = 0; i < 3; ++i) {
        dispatch_async(theQueue, ^{
            [self write];
        });
    }
    
    for (int i = 0; i < 3; ++i) {
        dispatch_async(theQueue, ^{
            [self read];
        });
    }
}


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

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

7,@synchronizedMutex (recursive lock)

@synchronized is the easiest lock to use on iOS, but also the least efficient (see figure at the beginning of Chapter 4).

@synchronized is a mutex, and of course it is a recursive lock, otherwise how could it be nested?

It takes an argument, and that argument is the object we want to lock. If you don’t know what to lock, select self.

Simple usage demonstration:

- (void)setup_synchronized {
    
    dispatch_queue_t theQueue = dispatch_get_global_queue(0, 0);
    
    for (int i = 0; i < 3; ++i) {
        dispatch_async(theQueue, ^{
            [self display_synchronized];
        });
    }
    
}


- (void)display_synchronized {
    
    @synchronized (self) {
        sleep(3);
        NSLog(@"% @", [NSThread currentThread]); }}Copy the code

Note that @synchronized cannot lock the address of the “locked object”.

The principle is simple. Convert the passed object ID to SyncData using id2Data in objc_sync_enter, and then syncData.mutex ->lock(). Clang rewrites @synchronized source code clang-rewriteobjc. CPP, truly implements objC_sync_Enter source code in runtime, download address Apple official website, Github.

Also, check out this article about @synchronized, there’s more to it than you want to know.

Eight,NSLockThe mutex

NSLock, mutex. Encapsulated by pthread_mutex with attribute PTHREAD_MUTEX_NORMAL.

There is an NSLocking protocol in iOS:

@protocol NSLocking

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

@end
Copy the code

NSLock complies with the NSLocking protocol. You can use -lock to add a lock and -unlock to unlock a lock.

In addition, NSLock provides the convenience of -lockbeforeDate: and -trylock.

NSLock:

NSLock *lock = [[NSLock alloc] init]; /// lock [lock]; // unlock [lock unlock]; If the lock is locked within 10 seconds, return YES on success, or NO on failure. [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:10]]; /// Try to lock, return YES on success, return NO otherwise. [lock tryLock];Copy the code

NSLock also provides an attribute named Name for convenience. We can use this property to facilitate development.

9,NSRecursiveLockRecursive locking

NSRecursiveLock, recursive lock. Encapsulated by pthread_mutex with attribute PTHREAD_MUTEX_RECURSIVE.

Similar to NSLock, NSRecursiveLock follows the NSLocking protocol. You can use -lock to add locks and -unlock to unlock locks.

The API is the same as NSLock, and the usage is exactly the same.

10,NSConditionConditions for the lock

NSCondition, conditional lock. It is encapsulated by pthread_mutex and pthread_cond.

NSCondition complies with the NSLocking protocol. You can use -lock to add a lock and -unlock to unlock it.

Before more information read NSCondition – Foundation | Apple official documents:

The semantics for using an NSCondition object are as follows:

The semantics of using NSCondition objects are as follows:

  1. Lock the condition object.
  1. Lock the conditional object.
  1. Test a boolean predicate. (This predicate is a boolean flag or other variable in your code that indicates whether it is safe to perform the task protected by the condition.)
  1. Tests Boolean predicates. (This predicate is a Boolean flag or other variable in the code that is simply safe to perform the task of selling condition protection.)
  1. If the boolean predicate is false, call the condition object’s wait or waitUntilDate: method to block the thread. Upon returning from these methods, go to step 2 to retest your boolean predicate. (Continue waiting and retesting the predicate until it is true.)
  1. If the Boolean predicate is false, the condition object’swaitwaitUntilDate:Method to block the thread. Based on the return values of these two methods, jump to step 2 to retest the Boolean predicate. (Keep waiting and testing until the Boolean predicate is true.)
  1. If the boolean predicate is true, perform the task.
  1. If Boolean is true for this, the task is executed.
  1. Optionally update any predicates (or signal any conditions) affected by your task.
  1. Change any predicates that affect the task (or signal condition variables) as needed.
  1. When your task is done, unlock the condition object.
  1. When the task is complete, the condition variable is unlocked.

A Boolean predicate is a predicate, which is a True and False identifier, just like we use if, but in NSCondition, we use while. Aple also provides a pseudocode:

lock the condition
while(! (boolean_predicate)) {wait on condition
}
do protected work
(optionally, signal or broadcast the condition again or change a predicate value)
unlock the condition
Copy the code

That’s a pretty good idea of what NSCondition means and how to use it. But that’s not all…

NSCondition also provides the following methods and attributes:

@property (nullable, copy) NSString *name; /// wait for the condition variable to block the current thread until the condition is signaled - (void)wait; // wait for the condition variable to block until the condition is signaled before the specified time - (BOOL)waitUntilDate:(NSDate *)limit; Void signal; void signal; void signal; /// signal the condition variable to wake up all waiting threads - (void)broadcast;Copy the code

Let’s rewrite the example code in 4.5 pthread_mutex conditional locks to replace the use of pthread_mutex with NSCondition:

/// pretend to be a producer - (void)produce_NSCondition {while (true) { [condition lock]; Sleep (0.1); // [shot.count > 5] = = = = = = = = = = = =if (shop.count > 5) {
            NSLog(@"The store is full. We can't make any more.");
            [condition wait]; } /// throw the product into the shop [shop addObject:@"fan"];
        NSLog(@"Produced a fan"); [condition signal]; [condition unlock]; } // pretend to be a consumer - (void)buy_NSCondition {while (true) { [condition lock]; // [shot.count < 1] = = = = = = = = = = = = = = = = = = = = =false/// enter the waitif (shop.count < 1) {
            NSLog(@"I can't get it now. I'll wait.");
            [condition wait];
        }
        
        
        [shop removeObjectAtIndex:0];
        NSLog(@"Finally got it. It wasn't easy."); [condition signal]; [condition unlock]; }}Copy the code

The result is exactly the same as pthread_mutex_t with pthread_cond_t…

11,NSConditionLockConditions for the lock

NSConditionLock is more advanced than NSCondition and can specify specific condition values.

NSConditionLock complies with the NSLocking protocol. You can use -lock to add the lock and -unlock to unlock it.

NSConditionLock also provides the following properties and methods:

// initialize with an NSInteger condition - (instancetype)initWithCondition:(NSInteger)condition; /// attribute: @property (readonly) NSInteger condition; - (void)lockWhenCondition (NSInteger)condition; /// try locking, return result immediately. Return YES if it can be locked, NO otherwise // condition variables are not considered /// whatever the result is, the subsequent code is executed - (BOOL)tryLock; // If the condition is true, try locking and return immediately. - (BOOL)tryLockWhenCondition:(NSInteger)condition; /// block the thread and lock it for a specified time. Return YES if successful, otherwise return NO /// regardless of condition variables, i.e. NSLocking -lock method - (BOOL)lockBeforeDate:(NSDate *)limit; /// block the thread and lock it if the condition variable is true for the specified time. Return YES on success, NO otherwise /// execute subsequent code regardless of result - (BOOL)lockWhenCondition:(NSInteger)condition beforeDate:(NSDate *)limit; - (void)unlockWithCondition:(NSInteger)condition;Copy the code

Note that unlockWithCondition is not unlocked when the condition variable meets, but is unlocked first and then updated.

Refer to the link

Multithreaded Programming Guide

IOS multithreading: an exhaustive summary of “GCD”

IOS multithreading complete set: thread life cycle, four solutions for multithreading, thread safety issues, GCD use, NSOperation use

For iOS multithreading, look at me

IOS – Multithreaded programming knowledge collation

IOS Multithreaded Programming — a mountain of iOS development

Apple open-source code – libDispatch

GCD source blood ejection analysis (2)

GCD dispatch_queue

GCD All solutions -09-dispatch_block -gcd Cancel the operation

GCD full solution -10-dispatch_source -Dispatch_resources

NSObject – Objective-C Runtime

How is dispatch_once implemented?

OSSpinLock is no longer secure

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

Performance aside, talk about why you shouldn’t use @synchronized

clang – RewriteObjC.cpp

Apple open-source code – Runtime

Lot – mkAppleOpenSourceDownload/objc4.