The main concepts involved

  • Task: Code placed in a block that can be executed either sync or async
  • Queue:Serial QueueConcurrent Queue
    • Main queue: Essentially a serial queue
    • Global queue: Essentially a concurrent queue

Knowledge:

  • Tasks in the main queue must be executed on the main thread. The main thread can execute tasks that are not in the main queue
  • Synchronous execution does not open up child threads and executes on the current thread
  • Asynchronous execution has the ability to create subthreads, but not necessarily, as we’ll see later
  • Synchronous execution A serial queue executes sequentially
  • Synchronous execution Concurrent queues are still executed sequentially
  • Executing a serial queue asynchronously creates a child thread that executes the queued tasks in order
  • Asynchronous execution of a concurrent queue has the ability to create multiple child threads

There are two execution modes of tasks, four types of queues, and a total of eight combinations, as follows:

Synchronous execution + serial queue

    dispatch_queue_t queueSerial = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
    
    void(^task)(void) = ^{
        NSLog(@"task--%@",[NSThread currentThread]);
    };

    void(^task1)(void) = ^{
        NSLog(@"task1--%@",[NSThread currentThread]);
    };

    dispatch_sync(queueSerial, task);
    dispatch_sync(queueSerial, task1);
    NSLog(@"end");
Copy the code

End = task1; end = task1; end = task1; end = task1;

dispatch_sync(queueSerial, task);
Copy the code
  • Synchronous executiondispatch_syncBlock current thread (main thread)
  • Serial queuequeueSerialInsert task intask
  • Executes on the current thread (main thread)tasktask
  • taskTask completion unblocks the current thread (main thread)
dispatch_sync(queueSerial, task1); / / in the same wayCopy the code
  • Synchronous executiondispatch_syncBlock current thread (main thread)
  • Serial queuequeueSerialInsert task intask1
  • Executes on the current thread (main thread)task1task
  • task1Task completion unblocks the current thread (main thread)

Task ->task1->end

Synchronous execution + concurrent queue

dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);

    void(^task)(void) = ^{
        NSLog(@"task--%@",[NSThread currentThread]);
    };

    void(^task1)(void) = ^{
        NSLog(@"task1--%@",[NSThread currentThread]);
    };

    dispatch_sync(queueConcurrent, task);
    dispatch_sync(queueConcurrent, task1);
    NSLog(@"end");
Copy the code

In the current thread synchronous execution, both serial queue and concurrent queue tasks are executed in order, and the result is consistent with synchronous execution + serial queue

Execute global queues synchronously

Dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0); dispatch_get_global_queue(0, 0); void(^task)(void) = ^{ NSLog(@"task--%@",[NSThread currentThread]); }; void(^task1)(void) = ^{ NSLog(@"task1--%@",[NSThread currentThread]); }; dispatch_sync(queueGlobal, task); dispatch_sync(queueGlobal, task1); NSLog(@"end");Copy the code

The global queue is a concurrent queue in nature, and its result is consistent with synchronous execution + concurrent queue

Execute the main queue synchronously

dispatch_queue_t queueMain = dispatch_get_main_queue(); void(^task)(void) = ^{ NSLog(@"task--%@",[NSThread currentThread]); }; void(^task1)(void) = ^{ NSLog(@"task1--%@",[NSThread currentThread]); }; dispatch_sync(queueMain, task); // I am also a task to be recorded as sync1 dispatch_sync(queueMain, task1); NSLog(@"end");Copy the code

This is a special case that causes deadlock because the statement dispatch_sync(queueMain, task) is executed synchronally; A sync1 task adds a task to the main queue and synchronously executes the task on the current thread (main thread). In other words, a sync1 task is executed only when the task is completed. Because the task is inserted into the main queue after the sync1 task, the main thread will execute the task task only after the sync1 task has been executed, and the sync1 task is executed only after the task has been executed. Therefore, the main thread will enter the deadlock state and cause the crash.

A similar

dispatch_queue_t queueSerial = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL); Void (^task1)(void) = ^{NSLog(@" deadlock "); }; void(^task2)(void) = ^{ dispatch_sync(queueSerial, task1); }; Dispatch_async (queueSerial, task2) dispatch_async(queueSerial, task2);Copy the code

dispatch_async(queueSerial, task2); The task itself is called sync1, but the difference is that sync1 is executed in the current thread (the main thread), Task1 = task1; task1 = task1; task1 = task1; task1 = task1; task1 = task1; Task2 is added to the queueSerial queue first than task1, so tasi1 will be executed only after task2 is finished.

Note: Asynchronous execution + serial queue opens up only one sub-thread

Execute a serial queue asynchronously

dispatch_queue_t queueSerial = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL); void(^task)(void) = ^{ NSLog(@"task--%@",[NSThread currentThread]); }; void(^task1)(void) = ^{ NSLog(@"task1--%@",[NSThread currentThread]); }; dispatch_async(queueSerial, task); Dispatch_async (queueSerial, task1); dispatch_async(queueSerial, task1); NSLog(@"end");Copy the code

End ->task->task1 = end->task->task1 = end->task->task1 = end->task->task1 = end->task->task1 = end->task So task and task1 execute in the same thread

Execute concurrent queues asynchronously

At this point, it is theoretically possible to create multiple child threads

    dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);

    void(^task)(void) = ^{
        NSLog(@"task--%@",[NSThread currentThread]);
    };

    void(^task1)(void) = ^{
        NSLog(@"task1--%@",[NSThread currentThread]);
    };

    dispatch_async(queueConcurrent, task);
    dispatch_async(queueConcurrent, task1);
    NSLog(@"end");
Copy the code

Task1 and task1 May not be executed in the same thread, so it is not possible to determine which of the two tasks will be finished first, because end is printed on the main thread, so end is printed first

Execute the main queue asynchronously

    dispatch_queue_t queueMain = dispatch_get_main_queue();

    void(^task)(void) = ^{
        NSLog(@"task--%@",[NSThread currentThread]);
    };

    void(^task1)(void) = ^{
        NSLog(@"task1--%@",[NSThread currentThread]);
    };

    dispatch_async(queueMain, task);
    dispatch_async(queueMain, task1);
Copy the code

The main queue is also a serial queue, so tasks ->task1 are executed in order. In addition, tasks in the queue can only be executed on the main thread, so both task and task1 are executed on the main thread

Execute global queues asynchronously

    dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);

    void(^task)(void) = ^{
        NSLog(@"task--%@",[NSThread currentThread]);
    };

    void(^task1)(void) = ^{
        NSLog(@"task1--%@",[NSThread currentThread]);
    };

    dispatch_async(queueGlobal, task);
    dispatch_async(queueGlobal, task1);
Copy the code

The global queue is a concurrent queue, so it has the ability to create multiple child threads, or to execute in a single child thread

Interthread communication

We need to put some time-consuming tasks on the child thread and then go back to the main thread to refresh the page

dispatch_queue_t queueMain = dispatch_get_main_queue(); dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0); dispatch_async(queueGlobal, ^{ sleep(3); // Dispatch_async (queueMain, ^{NSLog(@" main thread ")); }); });Copy the code

GCD fence method: dispatch_barrier_async

In Self-cultivation for Programmers: Linking, Loading, and Libraries. There’s a passage in the over-optimization section of the book

“The out-of-order execution capability of the CPU makes our efforts to secure multiple threads extremely difficult. Therefore, to ensure thread safety, it is necessary to prevent CPU swapping. Unfortunately, there is no portable way to prevent order swapping. This is usually done by calling an instruction provided by the CPU, often referred to as a barrier. A barrier prevents the CPU from swapping instructions that preceded it with a barrier, and vice versa. In other words, the barrier command acts like a dam, preventing changes from “penetrating” the dam.

Excerpt from: Yu Jia-zi, Shi Fan, pan Ai-min. “Self-cultivation of Programmers: Linking, Loading, and Libraries.” Apple Books.

To ensure atomicity of certain operations, CUP provides barrier instructions to ensure that subsequent instructions are not executed until the previous instructions have completed their execution. Dispatch_barrier_async basically means the same thing. In the concurrent queue of asynchronous execution, the tasks before dispatch_barrier_Async are executed first, then the tasks in dispatch_barrier_Async are executed, and then the tasks after dispatch_barrier_Async are executed

    dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);

    dispatch_async(queueGlobal, ^{
        NSLog(@"1--%@",[NSThread currentThread]);
    });

    dispatch_async(queueGlobal, ^{
        sleep(2);
        NSLog(@"2--%@",[NSThread currentThread]);
    });

    dispatch_barrier_async(queueGlobal, ^{
        NSLog(@"barrier--%@",[NSThread currentThread]);
    });

    dispatch_async(queueGlobal, ^{
        NSLog(@"3--%@",[NSThread currentThread]);
    });

    dispatch_async(queueGlobal, ^{
        NSLog(@"4--%@",[NSThread currentThread]);
    });
Copy the code

The global queue is a concurrent queue, so I guess I should print out a 1 and a 2, then execute a barrier, and then randomly print out a 3 and a 4

Barrier seems to have no effect just like dispatch_async. After checking some information, I finally found the correct solution of dispatch_barrier_async in the official document

If you create a concurrent queue with dispatch_queue_CREATE, that’s fine, but if it’s a serial queue or a global concurrent queue then it’s the same thing as dispatch_async, and you create your own concurrent queue

    dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queueConcurrent, ^{
        NSLog(@"1--%@",[NSThread currentThread]);
    });

    dispatch_async(queueConcurrent, ^{
        sleep(2);
        NSLog(@"2--%@",[NSThread currentThread]);
    });

    dispatch_barrier_async(queueConcurrent, ^{
        NSLog(@"barrier--%@",[NSThread currentThread]);
    });

    dispatch_async(queueConcurrent, ^{
        NSLog(@"3--%@",[NSThread currentThread]);
    });

    dispatch_async(queueConcurrent, ^{
        NSLog(@"4--%@",[NSThread currentThread]);
    });
    NSLog(@"end");
Copy the code

The result shows that dispatch_barrier_Async does not block the current thread, but only blocks the execution of other tasks in the current asynchronous queue. Official documents also confirm this

dispatch_barrier_sync

Let’s go to the official document for dispatch_barrier_sync first

On the one hand dispatch_barrier_sync blocks the current thread, on the other hand, it may cause deadlock. Let’s verify

    dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queueConcurrent, ^{
        sleep(2);
        NSLog(@"1--%@",[NSThread currentThread]);
    });

    dispatch_async(queueConcurrent, ^{
        NSLog(@"2--%@",[NSThread currentThread]);
    });

    dispatch_barrier_sync(queueConcurrent, ^{
        NSLog(@"barrier--%@",[NSThread currentThread]);
    });

    dispatch_async(queueConcurrent, ^{
        NSLog(@"3--%@",[NSThread currentThread]);
    });

    dispatch_async(queueConcurrent, ^{
        NSLog(@"4--%@",[NSThread currentThread]);
    });
    NSLog(@"end");
Copy the code

We see that dispatch_barrier_sync does block the current thread, and end is output after dispatch_barrier_sync

    dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);

    dispatch_barrier_sync(queueConcurrent, ^{
        NSLog(@"barrier--%@",[NSThread currentThread]);
        dispatch_sync(queueConcurrent, ^{
            NSLog(@"%@",[NSThread currentThread]);
        });
    });
Copy the code

Calling this function and targeting the current queue results in deadlock as mentioned in the document. Anyway, a deadlock was achieved

The documentation also mentions Block_copy

I can see from the source that dispatch_barrier_async does call _dispatch_Block_copy

However, dispatch_barrier_sync has no copy. This may be because dispatch_barrier_sync does not block the thread behind the code to execute, and dispatch_barrier_async does not block the thread, then the function body may be modified ???? So make a copy and leave a hello here

GCD Delay execution method: dispatch_after

After 2 seconds, the task is added to the main queue. When is it executed depends on the SCHEDULING of the CUP

dispatch_queue_t mainQueue = dispatch_get_main_queue(); (dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)), mainQueue, ^{// add task to mainQueue, It depends on the NSLog(@" executed ") of the CUP. });Copy the code

GCD One-time code (only executed once) : dispatch_once

To implement the singleton

static dispatch_once_t onceToken; Dispatch_once (&oncetoken, ^{// singleton});Copy the code

For more information, see the # iOS Multithreading dispatch_once profile

GCD fast iteration method: dispatch_apply

So let’s look at the document dispatch_apply

You can see that dispatch_apply and reentrant and secure concurrent queues are efficient traversal operations that would not be effective if they were a serial queue

    dispatch_apply(10, queueGlobal, ^(size_t index) {
        NSLog(@"%zd--%@",index,[NSThread currentThread]);
    });
    NSLog(@"end");
Copy the code

Dispatch_apply will use multiple threads to iterate, not just child threads, but also the main thread, and it will block the current thread

Dispatch Group

Dispatch_group monitors a set of tasks as a unit. You can put a group of tasks into a group and synchronize their behavior with dispatch_group. You can group these tasks and then execute them asynchronously on the same or different queues. You can listen for the completion of asynchronous tasks without blocking the current thread, or you can block the current thread and wait until the tasks complete.

dispatch_group_notify

    dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);

    dispatch_group_async(group, queueGlobal, ^{
        sleep(2);
        NSLog(@"1--%@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queueGlobal, ^{
        NSLog(@"2--%@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queueGlobal, ^{
        sleep(1);
        NSLog(@"3--%@",[NSThread currentThread]);
    });

    dispatch_group_notify(group, queueGlobal, ^{
        NSLog(@"notify--%@",[NSThread currentThread]);
    });
    NSLog(@"end--%@",[NSThread currentThread]);
Copy the code

The “dispatch_group_notify” task does not block the current thread. It is executed after all other queues in the current group have completed. The “dispatch_group_notify” task can be executed in the main queue, which implements the callback function of the main thread

    dispatch_queue_t queueSerial = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queueMain = dispatch_get_main_queue();
    dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);

    dispatch_group_async(group, queueGlobal, ^{
        sleep(2);
        NSLog(@"1--%@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queueSerial, ^{
        sleep(1);
        NSLog(@"2--%@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queueSerial, ^{
        NSLog(@"3--%@",[NSThread currentThread]);
    });

    dispatch_group_notify(group, queueMain, ^{
        NSLog(@"notify--%@",[NSThread currentThread]);
    });
    NSLog(@"end--%@",[NSThread currentThread]);
Copy the code

dispatch_group_wait

Block the current thread until all tasks in the group have completed

    dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);

    dispatch_group_async(group, queueGlobal, ^{
        sleep(2);
        NSLog(@"1--%@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queueGlobal, ^{
        sleep(1);
        NSLog(@"2--%@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queueGlobal, ^{
        NSLog(@"3--%@",[NSThread currentThread]);
    });

    dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 
    NSLog(@"end--%@",[NSThread currentThread]);
Copy the code

End will not be printed until the first three tasks have completed, but you can also set the blocking timeout to a smaller amount, so that the execution will continue even if the previous tasks have not completed

    dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);

    dispatch_group_async(group, queueGlobal, ^{
        sleep(3);
        NSLog(@"1--%@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queueGlobal, ^{
        sleep(1);
        NSLog(@"2--%@",[NSThread currentThread]);
    });

    dispatch_group_async(group, queueGlobal, ^{
        NSLog(@"3--%@",[NSThread currentThread]);
    });

    dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)));
    NSLog(@"end--%@",[NSThread currentThread]);
Copy the code

Dispatch_group_enter, dispatch_group_leave

Dispatch_group_async is also implemented internally with dispatch_group_Enter and dispatch_group_leave

    dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);

    dispatch_group_enter(group);
    dispatch_async(queueGlobal, ^{
        sleep(3);
        NSLog(@"1--%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queueGlobal, ^{
        sleep(1);
        NSLog(@"2--%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_enter(group);
    dispatch_async(queueGlobal, ^{
        NSLog(@"3--%@",[NSThread currentThread]);
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, queueGlobal, ^{
        NSLog(@"notify--%@",[NSThread currentThread]);
    });

    NSLog(@"end--%@",[NSThread currentThread]);
Copy the code

Implements anddispatch_group_asyncSame effect

The signal semaphore

In development where thread synchronization is often required, a semaphore is a good choice. Dispatch_semaphore_signal semaphore +1, dispatch_semaphore_WAIT semaphore -1. If the semaphore is less than zero, the current thread is blocked

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_async(queueGlobal, ^{ sleep(3); NSLog(@"1--%@",[NSThread currentThread]); dispatch_semaphore_signal(semaphore); // Semaphore +1}); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // semaphores -1 dispatch_async(queueMain, ^{NSLog(@" main thread ")); }); NSLog(@"end--%@",[NSThread currentThread]);Copy the code

Multiple accesses to the same data can make the data insecure, for example

    __block int i = 5;
    while (i>0) {
        dispatch_async(queueGlobal, ^{
            i--;
            NSLog(@"%d--%@",i,[NSThread currentThread]);
        });
    }
Copy the code

Due to the simultaneous modification of I by multiple threads, the result is very different from the expected one, and even the printing of NSLog function is out of order 😂, which can be solved through semaphore

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block int i = 5;
    
    while (i>0) {
        dispatch_async(queueGlobal, ^{
            i--;
            NSLog(@"%d--%@",i,[NSThread currentThread]);
            dispatch_semaphore_signal(semaphore);
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    }
Copy the code

At this point we see the output as expected, because the semaphore control variable I can only be accessed by at most one thread (not necessarily the same thread, but at most one thread at a time).

Refer to the article

IOS Multithreading: “GCD” detailed summary

Dispatch

Source libdispatch

IOS Multithreaded dispatch_once profiling