In the previous chapter, we learned some of the basics of multithreading. This chapter will explain in peacetime development, the most commonly used GCD some knowledge

Portal series:

☞ iOS Basics – Principles of Multithreading

The GCD introduction

define

The whole process of GCD is Grand Central Dispatch, implemented by C language, which is a solution proposed by Apple for multi-core parallel computing. CGD will automatically utilize more CPU cores and automatically manage the life cycle of threads. Programmers only need to tell GCD the tasks that need to be performed, without writing any code for thread management. GCD is also the most used multi-threading technology on iOS.

advantage

  • GCDIs apple’s solution to multi-core parallel computing
  • GCD– More CPU cores are automatically utilized (e.g., dual-core, quad-core)
  • GCDAutomatically manages the thread lifecycle (thread creation, task scheduling, thread destruction)
  • The programmer just needs to tellGCDWhat do you want to do without writing any thread management code

The GCD use

The USAGE API of GCD is relatively simple, mainly divided into synchronous and asynchronous execution. We have already covered the concepts in the previous chapter, so we will not repeat them.

Synchronous execution API

//queue: queue block: task
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
Copy the code

Asynchronous execution API

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

We found that when using GCD, we need to pass in two parameters. “Dispatch_queue_t” indicates the queue type to be added, “dispatch_block_t” indicates the task to be executed.

dispatch_block_ttask

As you can see by looking at its definition, this argument is actually a block callback with no arguments, used to perform the task.

typedef void (^dispatch_block_t)(void);
Copy the code

dispatch_queue_tThe queue

The dispatch_queue_t parameter indicates the queue type. As we know from the previous chapter, queues (FIFO) are mainly divided into four types in iOS:

  1. The home side columns (main_queue) : serial queue created by the system.
    • Obtaining method:dispatch_get_main_queue()
  2. Global queue (global_queue) : a concurrent queue created by the system
    • Obtaining method:dispatch_get_global_queue(long identifier, unsigned long flags);
  3. Serial queue (Serial Dispatch Queue) : a custom serial queue
    • Obtaining method:Dispatch_queue_create (@" queue name ",DISPATCH_QUEUE_SERIAL)
  4. Concurrent queue (Concurrent Dispatch Queue) : custom concurrent queue
    • Obtaining method:Dispatch_queue_create (@" queue name ",DISPATCH_QUEUE_CONCURRENT);

The global queue will get different queues according to different input parameters. In daily development, we usually enter parameter 0 to get the main concurrent queue.

Custom queues are created by calling dispatch_queue_CREATE (const char *_Nullable label,dispatch_queue_attr_t _Nullable attr). The two parameters are the custom queue name and queue type. The serial queue DISPATCH_QUEUE_SERIAL is NULL defined by the macro, so NULL is also represented as a serial queue.

Now, what happens if we look at the two execution modes, the three queues are paired together

Synchronous + (main queue or custom serial queue)

The relevant codes are as follows:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(Task 1: % @ "@"[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(Task 2 "@"[NSThread currentThread]);
    });
    NSLog(Task 3: % @ "@"[NSThread currentThread]);
}
Copy the code

The results

After printing out task 1, the program deadlock crashed

Why do these results occur?

The main thread performs task 1, dispatch_get_main_queue() synchronizes task 2 with (dispatch_sync), then performs task 3.

The feature of queue is FIFO. Task viewDidLoad already exists in the main queue. If you add task 2 to the main queue, you need to complete task 2 in viewDidLoad before task 2 can be executed. But in order to finish viewDidLoad, you have to do task 2 and task 3 in viewDidLoad. This creates a deadlock.

Asynchronous + (main queue or custom serial queue)

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(Task 1: % @ "@"[NSThread currentThread]);
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        NSLog(Task 2: % @ "@"[NSThread currentThread]);
    });
    NSLog(Task 3: % @ "@"[NSThread currentThread]);
}
Copy the code

The results

We can see that when the main queue is obtained, it is executed asynchronously without causing the program to crash.

After task 1 is executed by the main thread, task 2 needs to be executed asynchronously (dispatch_async). Dispatch_async does not require simultaneous execution of tasks on the current thread immediately, i.e., does not block; So the main thread then performs task 3, and finally asynchronously performs task 2.

Synchronous + (global queue or custom concurrent queue)

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_get_global_queue(0.0);
    dispatch_sync(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(Task 1: %@[NSThreadcurrentThread]); }});dispatch_sync(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@" Task 2: %@"[NSThreadcurrentThread]); }}); }Copy the code

The results

According to the order
A task was executed in the main thread, no new thread was opened

Asynchronous + (global queue or custom concurrent queue)

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_get_global_queue(0.0);
    dispatch_async(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(Task 1: %@[NSThreadcurrentThread]); }});dispatch_async(queue, ^{
        for (int i = 0; i<3; i++) {
            NSLog(@" Task 2: %@"[NSThreadcurrentThread]); }}); }Copy the code

Running results:

It can be seen that task 1 and task 2 are interleaved. Task 2 is executed after task 1 is executed. And the thread number indicates that a new thread has indeed been started. Note Asynchronous execution has the ability to start new threads.

However, we can see from the above print result of asynchronous + main queue, in the main queue, the asynchronous execution does not start a new thread, but still the same main thread. Note If the thread is executed asynchronously in the main queue, no new thread is started

The reason is that the main queue is a serial queue, and one task must be executed before another. Asynchronous execution does not cause congestion, but the main queue still has to go step by step.

Nesting of the same serial queue

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@ "task 1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{ 
        NSLog(Task 2 "@");
        dispatch_sync(queue, ^{ 
            NSLog(Task 3 "@");
        });
        NSLog(Task 4 "@");
    });
    NSLog(5 "@" task);
}
Copy the code

The results

Task 1, task 5, task 2, and then it crashes

Deadlock crashes are caused by the same reason as synchronous + serial queues above. Because the main queue is serial, the code must be executed from top to bottom.

After task 1 is printed, dispatch_async is asynchronous, it does not block the thread, but it is added to a customized serial queue, so task 5 is executed, then the logic in the asynchronous code block is executed, task 2 is printed, then the dispatch_sync code block is executed, but because it is synchronous, The dispatch_async task has not been completed in the serial queue, and then dispatch_sync blocks the current thread waiting for task 3 to execute, causing mutual waiting, so the deadlock crashes

Nesting of different serial queues

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@ "task 1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(Task 2 "@");
        dispatch_sync(queue2, ^{
            NSLog(Task 3 "@");
        });
        NSLog(Task 4 "@");
    });
    NSLog(5 "@" task);
}
Copy the code

The results

Task 1, Task 5, Task 2, task 3, task 4

By running it we can see that when nested execution is performed asynchronously on different serial queues, it does not cause deadlock crashes. I’m going to execute the code in serial order. This does not cause a deadlock wait because dispatch_sync blocks another thread rather than the current one.

Nesting of the same concurrent queue

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@ "task 1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(Task 2 "@");
        dispatch_sync(queue, ^{
            NSLog(Task 3 "@");
        });
        NSLog(Task 4 "@");
    });
    NSLog(5 "@" task);
}
Copy the code

Running results:

Task 1, Task 5, Task 2, task 3, task 4

After the main thread dispatch_async performs task 1, it needs to perform task 2 asynchronously. So execute task 5 of the main thread first, then task 2; Dispatch_sync then needs to be synchronized in the concurrent queue for task 3, so it blocks, waits for it to complete, and then executes task 4 in the concurrent queue.

Different concurrent queues are nested

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@ "task 1");
    dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(Task 2 "@");
        dispatch_sync(queue2, ^{ 
            NSLog(Task 3 "@");
        });
        NSLog(Task 4 "@");
    });
    NSLog(5 "@" task);
}
Copy the code

Running results:

Task 1, Task 5, Task 2, task 3, task 4

This code is analyzed in a similar way to the nesting of different serial queues, creating a new queue so it does not block the current queue.

conclusion

  • Synchronous execution does not open up threads, and the code executes sequentially, causing congestion
  • Synchronous execution on the same serial queue causes a deadlock wait
  • Asynchronous execution has the ability to open up threads. In concurrent queue execution, the order of execution is uncertain. In serial queue execution, the order of execution is sequential, and no new threads are opened in the main queue

The resources

Apple multithreaded open source code

Exploring the underlying principles of iOS – the nature of multithreading