Multi-threaded tetralogy of GCD

What is GCD?

GCD is the abbreviation of Grand Central Dispatch. Grand Central Dispatch (GCD) is a newer solution for multicore programming developed by Apple. The name was inspired by the Grand Central Terminal in New York.
Developers only need to define the tasks they want to execute and add them to a specific queue, and GCD will decide whether to open a new thread to execute the tasks according to the situation.
insert image description here

dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

GCD is mainly divided into sync synchronization and async asynchronous.
queue: queue.
block: The task you want to perform.

GCD's synchronous and asynchronous and their combinations

dispatch_sync和dispatch_async

同步执行(sync):
1. 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。
2. 只能在当前线程中执行任务,不具备开启新线程的能力。
异步执行(async):
1. 异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。
2. 可以在新的线程中执行任务,具备开启新线程的能力。

区别: Whether to wait for the execution of the tasks in the queue to end, and whether to have the ability to start a new thread .

Serial and concurrent queues, main and global queues

  • Queue (Dispatch Queue): A queue is a special linear table that adopts the FIFO (first in, first out) principle, that is, new tasks are always inserted at the end of the queue, and tasks are always read from the head of the queue Start reading. Each time a task is read, a task is released from the queue.

Serial queue (Serial Dispatch Queue) and concurrent queue (Concurrent Dispatch Queue).

The difference between the two is:
执行顺序不同,以及开启线程数不同。

串行队列(Serial Dispatch Queue):每次只执行一个任务,当前一个任务执行完成后才执行下一个任务。
并发队列(Concurrent Dispatch Queue):多个任务并发执行,所以先执行的任务可能最后才完成(因为具体的执行过程导致)。

insert image description here
insert image description here

The first-in-first-out of the GCD queue reflects the order in which tasks are taken, not the order in which tasks are executed.

Both queues are created from dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)the function .

dispatch_queue_create(const char *_Nullable label,
		dispatch_queue_attr_t _Nullable attr);
// 两个参数的含义,第一个是创造出来的队列的唯一标识符,第二个参数则是队列的类型。
// 返回类型为dispatch_queue_t

main queue and global queue

  • main queue
 // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

The main queue is a special serial queue, and the tasks we append to the main queue will be executed in the main thread.

  • global queue
// 获取全局队列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);

The global queue is actually a concurrent queue created by the system for us. We usually use the global queue.

their six combinations

From the above analysis, we can see that we have two ways of executing tasks and three kinds of execution queues, so there are six combinations.

Serial Queue SerialQueue Concurrent Queue ConcurrentQueue main queue mainQueue
dispatch_sync Will block the current thread and will not open up a new thread. Tasks in the queue are executed serially on the current thread. Will block the current thread and will not open up a new thread. Tasks in the queue are executed serially on the current thread. Will block the current thread and will not open up a new thread. Tasks in the queue are executed serially on the main thread.
dispatch_async Will not block the current thread, will open a new thread, will open a new thread (because it is serial), and the tasks in the queue will be executed in the new thread Will not block the current thread, will open up multiple threads , (because concurrent processing requires multiple threads, queue tasks are executed concurrently in these new threads The current thread will not be blocked, and no new thread will be created (because the tasks of the main thread will be put into the main thread for execution) , and the tasks in the queue will be executed serially in the main thread.

You can see that if you want to use GCD to achieve multi-threaded development , you must use it dispatch_async异步执行.
When actually using these six combinations, there are four angles to consider:

  • Will it block the current thread ? Determined by syncand async, the current thread 同步must be determined, and the current thread must be determined .堵塞异步不会堵塞
  • Will a new thread be opened ? This is determined by the combination. The use dispatch_syncwill definitely not open up a new thread, and the use dispatch_async+主队列will not open a new thread, and the rest may open up a new thread.
  • On the premise of developing threads, what is the number of development ? At this time, it is completely determined by the queue. If we are concurrent, multiple new threads will be opened, while the serial queue will only open one.
  • In which thread is the task in the queue executed, and the execution method ? This is determined jointly, but the queue dominates. The tasks of the serial queue must be executed serially. When a new thread is opened, it will be executed serially on the new thread. If it is not opened, it will be executed in the current thread . Thread tasks are always executed on the main thread .

The following test combination analysis

1. dispatch_sync + concurrent queue

- (void)testSyncConcurrent {
    
    
    NSLog(@"begin-- 并发队列 + 同步执行");
    // 创建一个并发队列
    dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
    // 执行两次任务
    dispatch_sync(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"1--%@", [NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"2--%@", [NSThread currentThread]);
        }
    });
    NSLog(@"end-- 并发队列 + 同步执行");
}

The printed results are as follows:
insert image description here
You can see that although this is a concurrent queue, because dispatch_sync does not have the ability to open up new threads , tasks are still executed in the main thread and processed in order.

2. dispatch_async + concurrent queue

We try to do it asynchronously.

- (void)asyncConcurrent {
    
    
    NSLog(@"begin-- 并发队列 + 异步执行");
    dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"end-- 并发队列 + 异步执行");
}

insert image description here
The following results can be obtained:
three new threads are opened up, and the tasks are carried out alternately and simultaneously.

All tasks are executed after the printed begin-- and end--, indicating that the tasks are not executed immediately, but are executed asynchronously after adding all tasks to the queue. In addition, the current thread does not wait, but starts directly Create a new thread, execute the task in the new thread, because the asynchronous execution does not wait, so you can continue to execute other tasks.

3. dispatch_sync + serial queue

- (void)syncSerial {
    
    
    NSLog(@"begin-- 串行队列 + 同步执行");
    dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 串行队列 + 同步执行");
}

insert image description here
The following results can be obtained:

  • All tasks are executed in the main thread, and no new threads are opened (synchronous execution does not have the ability to open new threads). Due to the serial queue, they are executed one by one in order.
  • All tasks are between the printed begin-- and end--, which means that the task is added to the queue and executed immediately, and the synchronization task needs to wait for the execution of the task in the queue to finish before the next task can be executed.
  • Tasks are executed sequentially, which means that only one task is executed in the serial queue at a time, and the tasks are executed one by one in order.

4. dispatch_async + serial queue

- (void)asyncSerial {
    
    

    NSLog(@"begin-- 串行队列 + 异步执行");
    dispatch_queue_t queue = dispatch_queue_create("net.test.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
    
    
        // 追加任务1
        for (int i = 0; i < 2; ++i) {
    
    
               NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
    
    
        // 追加任务2
        for (int i = 0; i < 2; ++i) {
    
    
               NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
    
    
        // 追加任务3
        for (int i = 0; i < 2; ++i) {
    
    
       NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 串行队列 + 异步执行");

}

insert image description here
The following points can be summarized from the printout:

  • A new thread is opened (asynchronous execution has the ability to open a new thread, but the serial queue can only open one thread), but because the tasks are serial, the tasks are still executed one by one (the serial queue can only There is one task being executed, the tasks are executed sequentially one after the other).
  • All tasks are executed after the printed begin-- and end--, indicating that the task starts asynchronously after adding all tasks to the queue, rather than immediately (asynchronous execution will not do any waiting, and can continue to execute Task).

dispatch_sync + main thread

- (void)syncMain {
    
    
    NSLog(@"begin-- 主队列 + 同步执行");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 主队列 + 同步执行");
}

insert image description here
It can be seen that the program crashed when adding tasks.
This phenomenon is called GCD deadlock.

GCD deadlock problem

It can be seen from the above example that the condition for causing deadlock is to use dispatch_sync and add tasks to the queue where it is located, and the current queue is a serial queue. (The queue he is in refers to dispatch_sync追加任务操作本身的队列).
But why would a deadlock occur in this way?

cause of deadlock

  • Task adding stage : After the program starts, the system will automatically add all the tasks to be executed in the main thread to the main queue. In this example, the system will automatically add viewDidloadthis task, and then blockappend the task to viewDidload.
  • Task execution stage : firstly, the program is executed by viewDidload. During the execution process, dispatch_syncthe main thread will be blocked to execute the block task , but the block task is appended to viewDidloadthe back. The main queue is a serial queue, and it must wait for the execution of the previous task to complete. The next task can be executed, so if blockthe task wants to be executed, it must wait for viewDidload to be executed, and viewDidloadif it wants to execute, it must wait for blockthe task to be executed. The two tasks wait for each other, causing a deadlock.

break deadlock

To break the deadlock, you only need to change one of the above conditions.

  • Solution 1: Add tasks with dispatch_async
- (void)syncMain {
    
    
    NSLog(@"begin-- 主队列 + 同步执行");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
    
    
        for (int i = 0; i < 2; i++) {
    
    
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 主队列 + 同步执行");
}

In this way, the task can be executed. Here, it blockis still added to the main queue, and it is still behind viewDidload, but dispatch_asyncit will not block the main thread . viewDidloadYou don’t have to wait for blockthe execution to be executed, and the block can be executed directly after the execution is completed.

  • Method 2 : Put dispatch_syncthe additional tasks into other queues (both serial and concurrent).
// 我们先创建了一个异步并发队列,然后在队列中同步追加block。
    dispatch_queue_t queue = dispatch_queue_create("testQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_sync(queue, ^{
    
    
            NSLog(@"当前线程:%@",[NSThread currentThread]);
        });
        // 这里全局队列也是可以的。

Blocks are appended to other queues, there is no waiting relationship, and naturally there will be no deadlock.

  • Method 3 : dispatch_syncThe queue where you are located is a concurrent queue
// 我们先获取一个全局队列,然后在队列中异步追加dispatch_sync这个任务。
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
        dispatch_async(queue, ^{
    
    
            NSLog(@"任务1当前线程:%@",[NSThread currentThread]);
            dispatch_sync(queue, ^{
    
    
                NSLog(@"block任务线程%@", [NSThread currentThread]);
            });
            NSLog(@"任务2当前线程:%@", [NSThread currentThread]);
        });

insert image description here
First of all, it is not a queue with the main queue, and will not viewDidloadconflict with it, and the new queue is a concurrent queue. Task 2 can be taken out for execution without waiting for the block task to complete.
So if we add it to another serial queue, it can only be deadlocked with the original main thread, but it may be locked with the new task.

Some common APIs of GCD

  1. dispatch_once
    dispatch_once is used to ensure that a piece of code is executed once in the entire life cycle of the program, so the code inside is 100% thread-safe.
static dispatch_once_t onceToken;
- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    NSLog(@"%ld", onceToken);
    for (int i = 0; i < 10; i++) {
    
    
        [self onceTest];
    }
}
- (void)onceTest {
    
    
    dispatch_once(&onceToken, ^{
    
    
        NSLog(@"%ld", onceToken);
    });
}

The result of the operation is indeed called once. The blind guess here is to judge whether the onceToken decision has been executed. If you learn about it later, you can add it.

  1. dispatch_after
    dispatch_after is used to delay how long to execute a certain task, and the principle of the GCD timer is the same, and it is implemented based on the system kernel. And performSelector: afterDelay and NSTimer are implemented based on RunLoop.
- (void)timeAfter {
    
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    
    
        NSLog(@"1");
    });
}
  1. GCD timer
    We know that NSTimer is implemented based on RunLoop, it may not be on time, so if we want accurate timing, we can use GCD timer. It is not implemented based on RunLoop, not a Timer event source, but based on the kernel.
- (void)GCDTimer {
    
    
    // 创建定时器
    // 最后一个参数的含义:定时器的回调要放在什么队列里
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    // 设置时间
    // start:多少秒后开始
    // intervalInSeconds:时间间隔
    // leewayInSeconds:误差,0
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
    dispatch_source_set_event_handler(timer, ^{
    
    
        NSLog(@"定时器回调");
        
    });
    dispatch_resume(timer);
    // 强引用保住timer
    self.timer = timer;
}

Results of the:
insert image description here

  1. GCD semaphore
    dispatch_semaphore– GCD semaphore is used to control the maximum concurrency of GCD threads. It has an initial value, which is used to specify the maximum concurrency of GCD threads.
    dispatch_semaphoreThe principle is to use dispatch_semaphore_waitand
    dispatch_semaphore_signaltwo functions to achieve. Specifically, whenever a thread comes in to execute
    the dispatch_semaphore_waitfunction, the function will judge the value of the semaphore. If the value of the semaphore is found to be >0, let the value of the semaphore -1, and let this thread execute the code down , if the value of the semaphore is found to be <=0, the thread will be blocked and sleep here, and will be woken up when the value of the semaphore is >0 again; and whenever a thread comes in to execute the function, it means that the thread has finished executing
    the dispatch_semaphore_signaltask , the value of the semaphore will be +1 inside the function. If there is a sleeping thread on it, you can wake up the sleeping thread to come in and execute the code. In this way, the effect of controlling the maximum number of concurrent threads is achieved.

Guess you like

Origin blog.csdn.net/chabuduoxs/article/details/125182152
gcd