iOS multithreading: GCD

iOS multithreading: GCD

1. Introduction to GCD

Grand Central Dispatch (GCD) is a newer solution for multi-core programming developed by Apple. It is mainly used to optimize applications to support multi-core processors and other symmetric multi-processing systems. It is a concurrent task executed on the basis of the thread pool model. First introduced in Mac OS X 10.6 Snow Leopard, it can also be used in iOS 4 and above.

Why use GCD? Because GCD has many advantages, as follows:

GCD can be used for multi-core parallel computing. GCD will automatically use more CPU cores (such as dual-core, quad-core). GCD will automatically manage the life cycle of threads (create threads, schedule tasks, destroy threads). Programmers just need to tell What task does GCD want to perform without writing any thread management code

2. GCD tasks and queues

Before learning GCD, let's understand the two core concepts in GCD: tasks and queues.

Task : It means to perform an operation, in other words, the piece of code you execute in a thread. It is placed in the block in GCD. There are two ways to perform tasks: synchronous execution (sync) and asynchronous execution (async). The main difference between the two is: whether to wait for the completion of the task in the queue, and whether to have the ability to open a new thread.

Synchronous execution (sync) :

  1. Synchronously add tasks to the specified queue. Before the execution of the added task ends, it will wait until the tasks in the queue are completed before continuing.
  2. Can only perform tasks in the current thread, and does not have the ability to open a new thread.

Asynchronous execution (async) :

  1. Asynchronously add tasks to the specified queue, it will not do any waiting and can continue to execute the task.
  2. Can perform tasks in new threads and have the ability to open new threads.

To give a simple example: you want to call Xiao Ming and Xiao Bai. Synchronous execution means that when you call Xiaoming, you cannot call Xiaobai at the same time. You can call Xiaobai only after you finish calling Xiaoming (waiting for the task execution to end). And only the current phone can be used (does not have the ability to open a new thread). Asynchronous execution means that when you call Xiaoming, you can call Xiaobai directly without waiting for the end of the conversation with Xiaoming. You don't need to wait for the end of the call with Xiaoming to call (you don't need to wait for the task execution to end). In addition to the current phone, you can also use other available phones (with the ability to open new threads).

Note: Asynchronous execution (async) has the ability to open new threads, but it does not necessarily open new threads. This is related to the type of queue specified by the task

Queue (Dispatch Queue) : The queue here refers to the waiting queue for executing tasks, that is, the queue for storing tasks. The queue is a special linear table that uses the FIFO (first in, first out) principle, that is, new tasks are always inserted at the end of the queue, and when tasks are read, they are always read from the head of the queue. Each time a task is read, a task is released from the queue. The structure of the queue can refer to the following figure. There are two types of queues in GCD: serial queue and concurrent queue . Both conform to the FIFO (First In First Out) principle. The main difference between the two is: the execution order is different, and the number of open threads is different.

Serial Dispatch Queue :

Only one task is executed at a time. Let the tasks be executed one after another. (Only one thread is started, after a task is executed, the next task is executed)

Concurrent Dispatch Queue :

Multiple tasks can be executed concurrently (simultaneously). (You can open multiple threads and perform tasks at the same time)

The concurrent function of the concurrent queue is only valid under the asynchronous (dispatch_async) function

3. How to use GCD

  1. Create a queue (serial queue or concurrent queue)
  2. The task is appended to the waiting queue of the task, and then the task will be executed systematically according to the task type (synchronous execution or asynchronous execution)

3.1 Queue creation method/acquisition method

  1. You can use dispatch_queue_create to create a queue, you need to pass in two parameters, the first parameter represents the unique identifier of the queue, used for DEBUG, can be empty, the name of Dispatch Queue is recommended to use the full domain name of the application ID; second; This parameter is used to identify whether it is a serial queue or a concurrent queue. DISPATCH_QUEUE_SERIAL means serial queue, DISPATCH_QUEUE_CONCURRENT means concurrent queue
// 
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
 
  1. For serial queues, GCD provides a special serial queue: Main Dispatch Queue

(1) All tasks placed in the main queue will be executed in the main thread (2) The main queue can be obtained using dispatch_get_main_queue()

// 
dispatch_queue_t queue = dispatch_get_main_queue();
 
  1. For concurrent queues, GCD provides a global concurrent queue (Global Dispatch Queue) by default

You can use dispatch_get_global_queue to get it. You need to pass in two parameters. The first parameter indicates the priority of the queue. Generally, DISPATCH_QUEUE_PRIORITY_DEFAULT is used. The second parameter is temporarily useless, just use 0.

// 
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 

3.2 How to create a task

GCD provides dispatch_sync and dispatch_async to create tasks for synchronous execution and dispatch_async for asynchronous tasks.

// 
dispatch_sync(queue, ^{
   // 
});
// 
dispatch_async(queue, ^{
   // 
});
 

Although there are only two steps to use GCD, since we have two queues (serial queue/concurrent queue) and two task execution methods (synchronous execution/asynchronous execution), then we have four different combinations. The four different combinations are:

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

In fact, I just mentioned two special queues: global concurrent queue and main queue. The global concurrent queue can be used as a normal concurrent queue. But because the main queue is a bit special, we have two more combinations. So there are six different combinations

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

So what is the difference between these different combinations?

the differenceConcurrent queueSerial queueMain queue
SynchronizeNo new threads are started, tasks are executed seriallyNo new threads are started, tasks are executed seriallyNo new threads are started, tasks are executed serially
asynchronousHave a new thread to execute tasks concurrentlyA new thread (1) is opened, and tasks are executed seriallyNo new threads are started, tasks are executed serially

It can be seen from the above figure that only asynchronous tasks + non-main queues have the ability to open threads, whether threads can be opened depends on the task, threads cannot be opened synchronously, new threads are opened asynchronously, and how many threads are opened depends on the queue, and one is opened serially. , Open one or more in parallel

4. Basic use of GCD

Let's talk about the two execution methods of concurrent queues first

4.1 Synchronous execution + concurrent queue

Perform tasks in the current thread, will not start a new thread, execute a task, and then execute the next task

/**
 *   +  
 *  
 */
- (void)syncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"syncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_sync(queue, ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_sync(queue, ^{
       // 3
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
    });
    
    NSLog(@"syncConcurrent---end");
}
 

Output result: 2019-02-15 11:15:46.343931+0800 Queue and synchronous and asynchronous [2833:84300] currentThread---<NSThread: 0x600001e8c680>{number = 1, name = main}

2019-02-15 11:15:46.344092+0800 Queue and synchronous and asynchronous[2833:84300] syncConcurrent---begin 2019-02-15 11:15:46.344227+0800 Queue and synchronous and asynchronous[2833:84300] 1-- -<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344329+0800 Queue and synchronous and asynchronous [2833:84300] 1---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344453+0800 Queue and synchronous and asynchronous [2833:84300] 2---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11: 15:46.344554+0800 Queue and synchronous and asynchronous[2833:84300] 2---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344647+0800 Queue and synchronous and asynchronous[2833 :84300] 3---<NSThread: 0x600001e8c680>{number = 1, name = main} 2019-02-15 11:15:46.344740+0800 Queue and synchronous and asynchronous [2833:84300] 3---<NSThread: 0x600001e8c680 >{number = 1, name = main} 2019-02-15 11:15:46.344838+0800 Queue and synchronous and asynchronous[2833:84300] syncConcurrent---end

From the synchronous execution + concurrent queue, you can see:

  1. All tasks are executed in the current thread (main thread), no new threads are opened (synchronous execution does not have the ability to open new threads)
  2. Synchronous tasks need to wait for the completion of the task execution in the queue
  3. The tasks are executed sequentially. The reason for sequential execution: Although the concurrent queue can open multiple threads and execute multiple tasks at the same time. But because it cannot create a new thread by itself, only the current thread is the thread (synchronized tasks do not have the ability to start a new thread), so there is no concurrency. Moreover, the current thread can only continue to perform the following operations after waiting for the execution of the tasks being executed in the current queue (synchronized tasks need to wait for the execution of the tasks in the queue to end). So tasks can only be executed one by one in order, not at the same time

4.2 Asynchronous execution + concurrent queue

Multiple threads can be opened, and tasks are executed alternately (simultaneously)

/**
 *   +  
 *  
 */
- (void)asyncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"asyncConcurrent---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_async(queue, ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_async(queue, ^{
       // 3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
    });
    
    NSLog(@"asyncConcurrent---end");
}
 

Output result 2019-02-15 14:30:29.722104+0800 Queue and synchronous and asynchronous [7592:229106] currentThread---<NSThread: 0x6000005ce900>{number = 1, name = main}

2019-02-15 14:30:29.722276+0800 Queue and synchronous and asynchronous[7592:229106] asyncConcurrent---begin 2019-02-15 14:30:29.722390+0800 Queue and synchronous and asynchronous[7592:229106] asyncConcurrent-- -end 2019-02-15 14:30:31.723953+0800 Queue and synchronous and asynchronous [7592:229185] 3---<NSThread: 0x6000005aa500>{number = 5, name = (null)} 2019-02-15 14: 30:31.723953+0800 Queue and synchronous and asynchronous [7592:229188] 1---<NSThread: 0x6000005aae40>{number = 3, name = (null)} 2019-02-15 14:30:31.723970+0800 Queue and synchronous and asynchronous [7592:229187] 2---<NSThread: 0x6000005af700>{number = 4, name = (null)} 2019-02-15 14:30:33.724463+0800 Queue and synchronous and asynchronous [7592:229187] 2--- <NSThread: 0x6000005af700>{number = 4, name = (null)} 2019-02-15 14:30:33.724463+0800 Queue and synchronous and asynchronous[7592:229185] 3---<NSThread: 0x6000005aa500>{number = 5 , name = (null)) 2019-02-15 14:30:33.724535+0800 Queue and synchronous and asynchronous[7592:229188] 1---<NSThread: 0x6000005aae40>{number = 3, name = (null)}

It can be seen in the asynchronous execution + concurrent queue :

  1. In addition to the current thread (main thread), the system has opened 3 threads, and tasks are executed alternately/simultaneously. (Asynchronous execution has the ability to open new threads. And concurrent queues can open multiple threads and execute multiple tasks at the same time).
  2. All tasks are executed after syncConcurrent---begin and syncConcurrent---end are printed. Explain that the current thread does not wait, but directly starts a new thread, and executes the task in the new thread (asynchronous execution does not wait and can continue to execute the task)

4.3 Synchronous execution + serial queue

Will not start a new thread, perform tasks in the current thread. Tasks are serial, after executing one task, execute the next task

/**
 *   +  
 *  
 */
- (void)syncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"syncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    dispatch_sync(queue, ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    dispatch_sync(queue, ^{
       // 3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
    });
    
    NSLog(@"syncSerial---end");
}
 

Output result 2019-02-15 14:33:40.462668+0800 Queue and synchronous and asynchronous [7679:232473] currentThread---<NSThread: 0x600000e05400>{number = 1, name = main}

2019-02-15 14:33:40.462831+0800 Queue and synchronous and asynchronous[7679:232473] syncSerial---begin 2019-02-15 14:33:42.464177+0800 Queue and synchronous and asynchronous[7679:232473] 1-- -<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:44.465682+0800 Queue and synchronous and asynchronous[7679:232473] 1---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:46.466694+0800 Queue and synchronous and asynchronous [7679:232473] 2---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14: 33:48.467783+0800 Queue and synchronous and asynchronous[7679:232473] 2---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:50.468335+0800 Queue and synchronous and asynchronous[7679 :232473] 3---<NSThread: 0x600000e05400>{number = 1, name = main} 2019-02-15 14:33:52.469841+0800 Queue and synchronous and asynchronous [7679:232473] 3---<NSThread: 0x600000e05400 >{number = 1, name = main} 2019-02-15 14:33:52.470107+0800 Queue and synchronous and asynchronous[7679:232473] syncSerial---end

In the synchronous execution + serial queue, you can see:

  1. All tasks are executed in the current thread (main thread), and no new threads are opened (synchronous execution does not have the ability to open new threads).
  2. All tasks are executed between the printed syncConcurrent---begin and syncConcurrent---end (synchronous tasks need to wait for the execution of the tasks in the queue to end).
  3. The tasks are executed in sequence (only one task is executed in the serial queue at a time, and the tasks are executed in sequence one after another)

4.4 Asynchronous execution + serial queue

A new thread will be started, but because the task is serial, one task is executed before the next task is executed

/**
 *   +  
 *  
 */
- (void)asyncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"asyncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    dispatch_async(queue, ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    dispatch_async(queue, ^{
       // 3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
    });
    
    NSLog(@"asyncSerial---end");
}
 

Output result 2019-02-15 14:35:51.554014+0800 Queue and synchronous and asynchronous [7746:234367] currentThread---<NSThread: 0x60000305e940>{number = 1, name = main}

2019-02-15 14:35:51.554171+0800 Queue and synchronous and asynchronous[7746:234367] asyncSerial---begin 2019-02-15 14:35:51.554302+0800 Queue and synchronous and asynchronous[7746:234367] asyncSerial-- -end 2019-02-15 14:35:53.555205+0800 Queue and synchronous and asynchronous [7746:234408] 1---<NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14: 35:55.556721+0800 Queue and synchronous and asynchronous [7746:234408] 1---<NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14:35:57.560216+0800 Queue and synchronous and asynchronous [7746:234408] 2---<NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14:35:59.561222+0800 Queue and synchronous and asynchronous [7746:234408] 2--- <NSThread: 0x60000303cd00>{number = 3, name = (null)} 2019-02-15 14:36:01.566033+0800 Queue and synchronous and asynchronous[7746:234408] 3---<NSThread: 0x60000303cd00>{number = 3 , name = (null)) 2019-02-15 14:36:03.570689+0800 Queue and synchronous and asynchronous [7746:234408] 3---<NSThread: 0x60000303cd00>{number = 3, name = (null)}

You can see in the asynchronous execution + serial queue :

  1. A new thread is opened (asynchronous execution has the ability to open a new thread, and the serial queue only opens one thread)
  2. All tasks are executed after syncConcurrent---begin and syncConcurrent---end are printed (asynchronous execution will not do any waiting, and you can continue to execute tasks)
  3. The tasks are executed in sequence (only one task is executed in the serial queue at a time, and the tasks are executed in sequence one by one

Let's talk about the special queue we mentioned earlier: the main queue .

Main queue : a special serial queue that comes with GCD

  1. All tasks placed in the main queue will be executed in the main thread
  2. You can use dispatch_get_main_queue() to get the main queue

4.5 Synchronous execution + main queue

Synchronous execution + main queue calling results in different threads is also different, calling in the main thread will cause deadlock, but in other threads will not

4.5.1 Call synchronous execution + main queue in the main thread

Waiting for each other and getting stuck is not feasible

/**
 *   +  
 *  ( ) 
 *  ( ) 
 */
- (void)syncMain {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"syncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_sync(queue, ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_sync(queue, ^{
       // 3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
    });
    
    NSLog(@"syncMain---end");
}
 

Output result: 2019-02-15 14:42:25.410352+0800 Queue and synchronous and asynchronous [7914:239431] currentThread---<NSThread: 0x6000014f5400>{number = 1, name = main}

2019-02-15 14:42:25.410503+0800 Queue and synchronous and asynchronous [7914:239431] syncMain---begin

In the synchronous execution + main queue, you can find surprisingly:

Using synchronous execution + main queue in the main thread, tasks 1, task 2, and task 3 added to the main thread are no longer executed, and syncMain---end is not printed, and a crash will be reported on XCode 9. Why is this?

This is because we execute the syncMain method in the main thread, which is equivalent to putting the syncMain task in the queue of the main thread. Synchronous execution will wait for the tasks in the current queue to be executed before proceeding. Then when we add task 1 to the main queue, task 1 is waiting for the main thread to finish processing the syncMain task. The syncMain task needs to wait for the completion of task 1 before it can be executed. So, the current situation is that both the syncMain task and task 1 are waiting for each other to finish executing. In this way, everyone is waiting for each other, so we are stuck, so our task cannot be executed, and syncMain---end is not printed. What if it is not called in the main thread, but in other threads?

4.5.2 Call synchronous execution + main queue in other threads

Will not start a new thread, execute a task, and then execute the next task

//  NSThread   detachNewThreadSelector  
 selector  
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];
 

Output result 2019-02-15 14:44:06.075954+0800 Queue and synchronous and asynchronous [7968:241022] currentThread---<NSThread: 0x6000008cb080>{number = 3, name = (null)}

2019-02-15 14:44:06.076113+0800 Queue and synchronous and asynchronous[7968:241022] syncMain---begin 2019-02-15 14:44:08.090721+0800 Queue and synchronous and asynchronous[7968:240949] 1-- -<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:10.092199+0800 Queue and synchronous and asynchronous[7968:240949] 1---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:12.095315+0800 Queue and synchronous and asynchronous [7968:240949] 2---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14: 44:14.096453+0800 Queue and synchronous and asynchronous[7968:240949] 2---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:16.099389+0800 Queue and synchronous and asynchronous[7968 :240949] 3---<NSThread: 0x60000089a940>{number = 1, name = main} 2019-02-15 14:44:18.099756+0800 Queue and synchronous and asynchronous [7968:240949] 3---<NSThread: 0x60000089a940 >{number = 1, name = main} 2019-02-15 14:44:18.100123+0800 Queue and synchronous and asynchronous[7968:241022] syncMain---end

Use synchronous execution + main queue in other threads to see:

  1. All tasks are executed in the main thread (not the current thread), and no new threads are started (all tasks placed in the main queue will be executed in the main thread).
  2. All tasks are executed between the printed syncConcurrent---begin and syncConcurrent---end (synchronous tasks need to wait for the execution of the tasks in the queue to end).
  3. Tasks are executed in order (the main queue is a serial queue, only one task is executed at a time, and the tasks are executed in order one by one)

Why is it not stuck now? Because the syncMain task is placed in other threads, and task 1, task 2, and task 3 are all appended to the main queue, these three tasks will be executed in the main thread. The syncMain task is executed in other threads to append task 1 to the main queue. Because the main queue has no tasks currently being executed, task 1 of the main queue will be executed directly. After task 1 is executed, task 2 will be executed again. 3. So there is no thread jamming here

4.6 Asynchronous execution + main queue

Only execute tasks in the main thread, after executing one task, execute the next task.

/**
 *   +  
 *  
 */
- (void)asyncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"asyncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_async(queue, ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_async(queue, ^{
       // 3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
    });
    
    NSLog(@"asyncMain---end");
}
 

Output result: 2019-02-15 14:47:49.136074+0800 Queue and synchronous and asynchronous [8068:243847] currentThread---<NSThread: 0x600002c65400>{number = 1, name = main}

2019-02-15 14:47:49.136221+0800 Queue and synchronous and asynchronous[8068:243847] asyncMain---begin 2019-02-15 14:47:49.136320+0800 Queue and synchronous and asynchronous[8068:243847] asyncMain-- -end 2019-02-15 14:47:51.153197+0800 Queue and synchronous and asynchronous [8068:243847] 1---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:47: 53.153685+0800 Queue and synchronous and asynchronous[8068:243847] 1---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:47:55.155159+0800 Queue and synchronous and asynchronous[8068:243847 ] 2---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02-15 14:47:57.156706+0800 Queue and synchronous and asynchronous [8068:243847] 2---<NSThread: 0x600002c65400>{ number = 1, name = main} 2019-02-15 14:47:59.158292+0800 Queue and synchronous and asynchronous [8068:243847] 3---<NSThread: 0x600002c65400>{number = 1, name = main} 2019-02 -15 14:48:01.159829+0800 Queue and synchronous and asynchronous [8068:243847] 3---<NSThread: 0x600002c65400>{number = 1, name = main}

In the asynchronous execution + main queue, you can see:

  1. All tasks are executed in the current thread (main thread), and no new threads are opened (although asynchronous execution has the ability to open threads, because it is the main queue, all tasks are in the main thread).
  2. All tasks are executed after syncConcurrent---begin and syncConcurrent---end are printed (asynchronous execution will not do any waiting, and you can continue to execute tasks).
  3. Tasks are executed in sequence (because the main queue is a serial queue, only one task is executed at a time, and the tasks are executed in sequence one by one

5. Communication between GCD threads

In the iOS development process, we generally refresh the UI in the main thread, such as clicking, scrolling, dragging and other events. We usually put some time-consuming operations in other threads, such as image downloading, file uploading and other time-consuming operations. And when we sometimes complete time-consuming operations in other threads and need to return to the main thread, then the communication between threads is used

/**
 *  
 */
- (void)communication {
   // 
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
   // 
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    
    dispatch_async(queue, ^{
       // 
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
        
       // 
        dispatch_async(mainQueue, ^{
           // 
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        });
    });
}
 

Output result 2019-02-15 14:51:38.214783+0800 Queue and synchronous and asynchronous [8174:247133] 1---<NSThread: 0x600001de02c0>{number = 3, name = (null)}

2019-02-15 14:51:40.220222+0800 Queue and synchronous and asynchronous [8174:247133] 1---<NSThread: 0x600001de02c0>{number = 3, name = (null)} 2019-02-15 14:51: 42.221872+0800 Queue and synchronous and asynchronous [8174:247009] 2---<NSThread: 0x600001d8d300>{number = 1, name = main}

You can see that the task is executed first in other threads, and after the execution is completed, it returns to the main thread to perform the corresponding operation of the main thread.

6. Other methods of GCD

6.1 GCD barrier method: dispatch_barrier_async

We sometimes need to perform two sets of operations asynchronously, and after the first set of operations is executed, we can start to perform the second set of operations, so we need a fence-like method to separate the two sets of asynchronously executed operation groups. Of course The operation group here can contain one or more tasks, which requires the dispatch_barrier_async method to form a barrier between the two operation groups. The dispatch_barrier_async function will wait for the tasks previously added to the concurrent queue to be executed, and then append the specified tasks to the asynchronous queue. Then after the tasks added by the dispatch_barrier_async function are executed, the asynchronous queue resumes the normal action, and then the tasks are added to the asynchronous queue and started to execute. The details are shown in the figure below:

/**
 *   dispatch_barrier_async
 */
- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    dispatch_async(queue, ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_barrier_async(queue, ^{
       //  barrier
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"barrier---%@",[NSThread currentThread]);// 
        }
    });
    
    dispatch_async(queue, ^{
       // 3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
    });
    dispatch_async(queue, ^{
       // 4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"4---%@",[NSThread currentThread]);     // 
        }
    });
}
 

Output result 2019-02-15 15:35:08.097053+0800 Queue and synchronous and asynchronous [9256:283315] 2---<NSThread: 0x600001ff1e40>{number = 4, name = (null)}

2019-02-15 15:35:08.097050+0800 Queue and synchronous and asynchronous [9256:283316] 1---<NSThread: 0x600001fdbf40>{number = 3, name = (null)} 2019-02-15 15:35: 10.099652+0800 Queue and synchronous and asynchronous[9256:283315] 2---<NSThread: 0x600001ff1e40>{number = 4, name = (null)} 2019-02-15 15:35:10.099652+0800 Queue and synchronous and asynchronous[9256 :283316] 1---<NSThread: 0x600001fdbf40>{number = 3, name = (null)} 2019-02-15 15:35:12.100542+0800 Queue and synchronous and asynchronous[9256:283315] barrier---<NSThread : 0x600001ff1e40>{number = 4, name = (null)} 2019-02-15 15:35:14.106086+0800 Queue and synchronous and asynchronous[9256:283315] barrier---<NSThread: 0x600001ff1e40>{number = 4, name = (null)) 2019-02-15 15:35:16.111681+0800 Queue and synchronous and asynchronous [9256:283316] 4---<NSThread: 0x600001fdbf40>{number = 3, name = (null)} 2019-02- 15 15:35:16.111683+0800 Queue and synchronous and asynchronous [9256:283315] 3---<NSThread: 0x600001ff1e40>{number = 4, name = (null)} 2019-02-15 15:35:18.116375+0800 queue and synchronous and asynchronous[9256:283316] 4---<NSThread: 0x600001fdbf40>{number = 3, name = (null)} 2019-02-15 15:35:18.116375+0800 queue and synchronous and asynchronous[9256 :283315] 3---<NSThread: 0x600001ff1e40>{number = 4, name = (null)}

It can be seen in the execution result of dispatch_barrier_async related code:

After performing the operations in front of the fence, perform the fence operation, and finally perform the operations behind the fence.

6.2 GCD delay execution method: dispatch_after

We often encounter such a requirement: perform a task after a specified time (for example, 3 seconds). It can be implemented with the dispatch_after function of GCD. It should be noted that the dispatch_after function does not start processing after the specified time, but appends the task to the main queue after the specified time. Strictly speaking, this time is not absolutely accurate, but if you want to roughly delay the execution of the task, the dispatch_after function is very effective

/**
 *   dispatch_after
 */
- (void)after {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"asyncMain---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
       //2.0 
        NSLog(@"after---%@",[NSThread currentThread]); // 
    });
}
 

6.3 GCD one-time code (execute only once): dispatch_once

We use the dispatch_once function of GCD when we create a singleton or have code that is executed only once during the entire program running. Using the dispatch_once function can ensure that a certain piece of code is executed only once during the running of the program, and even in a multi-threaded environment, dispatch_once can also ensure thread safety.

/**
 *  dispatch_once
 */
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
       // 1 ( )
    });
}
 

6.4 GCD fast iteration method: dispatch_apply

Usually we use a for loop to traverse, but GCD provides us with a fast iterative function dispatch_apply. dispatch_apply appends the specified task to the specified queue according to the specified number of times, and waits for the execution of all queues to end

We can use asynchronous queues to traverse at the same time. For example, to traverse the 6 numbers from 0 to 5, the method of the for loop is to take out one element at a time and traverse one by one. dispatch_apply can traverse multiple numbers at the same time.

/**
 *   dispatch_apply
 */
- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}
 

Output result 2019-02-15 15:39:01.005273+0800 Queue and synchronous and asynchronous [9366:286604] apply---begin

2019-02-15 15:39:01.006663+0800 Queue and synchronous and asynchronous [9366:286652] 3---<NSThread: 0x60000022c500>{number = 5, name = (null)} 2019-02-15 15:39: 01.006663+0800 Queue and synchronous and asynchronous[9366:286655] 1---<NSThread: 0x600000203840>{number = 3, name = (null)} 2019-02-15 15:39:01.006663+0800 Queue and synchronous and asynchronous[9366 :286651] 2---<NSThread: 0x60000022ca00>{number = 4, name = (null)} 2019-02-15 15:39:01.006663+0800 Queue and synchronous and asynchronous [9366:286604] 0---<NSThread : 0x600000249400>{number = 1, name = main} 2019-02-15 15:39:01.006859+0800 Queue and synchronous and asynchronous[9366:286652] 5---<NSThread: 0x60000022c500>{number = 5, name = ( null)} 2019-02-15 15:39:01.006869+0800 Queue and synchronous and asynchronous [9366:286651] 4---<NSThread: 0x60000022ca00>{number = 4, name = (null)} 2019-02-15 15 :39:01.006979+0800 Queue and synchronous and asynchronous [9366:286604] apply---end

It can be seen from the execution result of dispatch_apply related code:

  1. 0~5 The printing order is indefinite, and apply--end is printed at the end

Because the tasks are executed asynchronously in the concurrent queue, the execution time of each task is variable, and the final end sequence is also variable, but apply---end must be executed at the end. This is because the dispatch_apply function will wait for the completion of all tasks

6.5 GCD queue group: dispatch_group

Sometimes we have such a requirement: to execute two time-consuming tasks asynchronously, and then return to the main thread to perform the task after the two time-consuming tasks are completed. At this time we can use the GCD queue group

  1. Call dispatch_group_async of the queue group to put the task in the queue first, and then put the queue into the queue group. Or use the combination of dispatch_group_enter and dispatch_group_leave of the queue group to implement dispatch_group_async.
  2. Call dispatch_group_notify of the queue group to return to the specified thread to perform the task. Or use dispatch_group_wait to return to the current thread and continue to execute downward (will block the current thread)

6.5.1 dispatch_group_notify

Monitor the completion status of the tasks in the group. When all tasks are executed, add tasks to the group and execute the tasks.

/**
 *   dispatch_group_notify
 */
- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // 1 2 
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
        NSLog(@"group---end");
    });
}
 

Output result: 2019-02-15 15:44:48.852840+0800 Queue and synchronous and asynchronous [9515:291211] currentThread---<NSThread: 0x600003ff4040>{number = 1, name = main}

2019-02-15 15:44:48.852983+0800 Queue and synchronous and asynchronous[9515:291211] group---begin 2019-02-15 15:44:50.854857+0800 Queue and synchronous and asynchronous[9515:291265] 2-- -<NSThread: 0x600003fbb000>{number = 4, name = (null)} 2019-02-15 15:44:50.854867+0800 Queue and synchronous and asynchronous[9515:291262] 1---<NSThread: 0x600003f90dc0>{number = 3, name = (null)) 2019-02-15 15:44:52.860365+0800 Queue and synchronous and asynchronous [9515:291265] 2---<NSThread: 0x600003fbb000>{number = 4, name = (null)} 2019 -02-15 15:44:52.860383+0800 Queue and synchronous and asynchronous [9515:291262] 1---<NSThread: 0x600003f90dc0>{number = 3, name = (null)} 2019-02-15 15:44:54.862032 +0800 Queue and synchronous and asynchronous [9515:291211] 3---<NSThread: 0x600003ff4040>{number = 1, name = main} 2019-02-15 15:44:56.863549+0800 Queue and synchronous and asynchronous [9515:291211] 3---<NSThread: 0x600003ff4040>{number = 1, name = main} 2019-02-15 15:44:56.863819+0800 Queue and synchronous and asynchronous [9515:291211] group---end

It can be seen from the output results of dispatch_group_notify related code that: After all tasks are executed, the tasks in the dispatch_group_notify block are executed.

6.5.2 dispatch_group_wait

Pause the current thread (block the current thread) and wait for the execution of the tasks in the specified group to complete before continuing execution.

/**
 *   dispatch_group_wait
 */
- (void)groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
    });
    
   // 
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}
 

Output: 2019-02-15 15:47:24.820145+0800 Queue and synchronous and asynchronous [9593:293510] currentThread---<NSThread: 0x600000306940>{number = 1, name = main}

2019-02-15 15:47:24.820309+0800 queue and synchronous and asynchronous[9593:293510] group---begin 2019-02-15 15:47:26.824388+0800 queue and synchronous and asynchronous[9593:293548] 2-- -<NSThread: 0x6000003622c0>{number = 3, name = (null)} 2019-02-15 15:47:26.824388+0800 Queue and synchronous and asynchronous[9593:293547] 1---<NSThread: 0x60000035dcc0>{number = 4, name = (null)) 2019-02-15 15:47:28.826498+0800 Queue and synchronous and asynchronous [9593:293547] 1---<NSThread: 0x60000035dcc0>{number = 4, name = (null)} 2019 -02-15 15:47:28.826498+0800 Queue and synchronous and asynchronous [9593:293548] 2---<NSThread: 0x6000003622c0>{number = 3, name = (null)} 2019-02-15 15:47:28.826852 +0800 Queue and synchronous and asynchronous [9593:293510] group---end

It can be seen from the output results of dispatch_group_wait related code: When all tasks are completed, the operations after dispatch_group_wait are executed. However, using dispatch_group_wait will block the current thread

dispatch_group_enter, dispatch_group_leave

  1. dispatch_group_enter indicates that a task is appended to the group and executed once, which is equivalent to the number of unfinished tasks in the group +1
  2. dispatch_group_leave indicates that a task has left the group and is executed once, which is equivalent to the number of unfinished tasks in the group -1.
  3. When the number of unfinished tasks in the group is 0, dispatch_group_wait will be unblocked and the tasks appended to dispatch_group_notify will be executed
/**
 *   dispatch_group_enter dispatch_group_leave
 */
- (void)groupEnterAndLeave
{
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
       // 1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"1---%@",[NSThread currentThread]);     // 
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
       // 2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"2---%@",[NSThread currentThread]);     // 
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       // .
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];             // 
            NSLog(@"3---%@",[NSThread currentThread]);     // 
        }
        NSLog(@"group---end");
    });
    
//  // 
//   dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
//
//   NSLog(@"group---end");
}
 

Output result: 2019-02-15 15:53:00.944543+0800 Queue and synchronous and asynchronous [9750:298146] currentThread---<NSThread: 0x6000011e2900>{number = 1, name = main}

2019-02-15 15:53:00.944726+0800 Queue and synchronous and asynchronous[9750:298146] group---begin 2019-02-15 15:53:02.949542+0800 Queue and synchronous and asynchronous[9750:298194] 1-- -<NSThread: 0x6000011862c0>{number = 3, name = (null)} 2019-02-15 15:53:02.949542+0800 Queue and synchronous and asynchronous[9750:298197] 2---<NSThread: 0x600001186300>{number = 4, name = (null)) 2019-02-15 15:53:04.950626+0800 Queue and synchronous and asynchronous [9750:298194] 1---<NSThread: 0x6000011862c0>{number = 3, name = (null)} 2019 -02-15 15:53:04.950653+0800 Queue and synchronous and asynchronous [9750:298197] 2---<NSThread: 0x600001186300>{number = 4, name = (null)} 2019-02-15 15:53:06.952269 +0800 queue and synchronous and asynchronous[9750:298146] 3---<NSThread: 0x6000011e2900>{number = 1, name = main} 2019-02-15 15:53:08.953768+0800 queue and synchronous and asynchronous[9750:298146] 3---<NSThread: 0x6000011e2900>{number = 1, name = main} 2019-02-15 15:53:08.954036+0800 Queue and synchronous and asynchronous [9750:298146] group---end

It can be seen from the running results of dispatch_group_enter and dispatch_group_leave related codes that the tasks in dispatch_group_notify will be executed after all tasks are completed. The combination of dispatch_group_enter and dispatch_group_leave here is actually equivalent to dispatch_group_async

6.6 GCD semaphore: dispatch_semaphore

The semaphore in GCD refers to Dispatch Semaphore, which is a signal that holds a count. Similar to the railing of a toll station on a high-speed road, when you can pass, open the railing, when you can't pass, close the railing. In Dispatch Semaphore, use the count to complete This function, when the count is 0, waits and cannot pass. When the count is 1 or greater than 1, the count is decremented by 1 without waiting, and it can be passed. Dispatch Semaphore provides three functions.

  1. dispatch_semaphore_create: Create a Semaphore and initialize the total amount of signals
  2. dispatch_semaphore_signal: send a signal to increase the total amount of signal by 1
  3. dispatch_semaphore_wait: You can reduce the total semaphore by 1, when the total semaphore is 0, it will wait forever (blocking the thread), otherwise it can be executed normally.

Note: The premise of using semaphore is: think about which thread you need to handle waiting (blocking), and which thread you want to continue executing, and then use the semaphore.

Dispatch Semaphore is mainly used in actual development:

  1. Keep threads synchronized and convert asynchronous execution tasks to synchronous execution tasks
  2. Ensure thread safety, lock the thread

6.6.1 Dispatch Semaphore thread synchronization

In our development, we will encounter such a requirement: asynchronously execute time-consuming tasks, and use the results of asynchronous execution to perform some additional operations. In other words, it is equivalent to converting an asynchronously executed task into a synchronously executed task. For example: the tasksForKeyPath: method in AFURLSessionManager.m in AFNetworking. By introducing the semaphore, it waits for the result of asynchronous execution of the task, obtains the tasks, and then returns to the tasks.

- (NSArray *)tasksForKeyPath:(NSString *)keyPath {
    __block NSArray *tasks = nil;
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
            tasks = dataTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
            tasks = uploadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
            tasks = downloadTasks;
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
            tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
        }

        dispatch_semaphore_signal(semaphore);
    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;
}

 

Next, let's use Dispatch Semaphore to realize thread synchronization and convert asynchronous execution tasks into synchronous execution tasks.

/**
 * semaphore  
 */
- (void)semaphoreSync {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
       // 1
        [NSThread sleepForTimeInterval:2];             // 
        NSLog(@"1---%@",[NSThread currentThread]);     // 
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}
 

Output result:

2018-02-23 22:22:26.521665+0800 YSC-GCD-demo[20642:5246341] currentThread---<NSThread: 0x60400006bc80>{number = 1, name = main} 2018-02-23 22:22:26.521869 +0800 YSC-GCD-demo[20642:5246341] semaphore---begin 2018-02-23 22:22:28.526841+0800 YSC-GCD-demo[20642:5246638] 1---<NSThread: 0x600000272300>{number = 3, name = (null)} 2018-02-23 22:22:28.527030+0800 YSC-GCD-demo[20642:5246341] semaphore---end,number = 100

From the code that Dispatch Semaphore implements thread synchronization, you can see: semaphore---end is printed after number = 100; is executed. And the output number is 100. This is because asynchronous execution does not do any waiting, and the task can continue to be executed. Asynchronous execution adds task 1 to the queue without waiting, and then executes the dispatch_semaphore_wait method. . At this time semaphore == 0, and the current thread enters the waiting state. Then, asynchronous task 1 starts to execute. After task 1 is executed to dispatch_semaphore_signal, the total semaphore is increased by 1, at this time semaphore == 1, and the dispatch_semaphore_wait method reduces the total semaphore by 1, and the blocked thread (main thread) resumes execution. Finally, print semaphore---end, number = 100. In this way, thread synchronization is realized, and asynchronous execution tasks are converted into synchronous execution tasks.

6.6.2 Dispatch Semaphore thread safety and thread synchronization (locks for threads)

Thread safety : If there are multiple threads running at the same time in the process where your code is located, these threads may run this code at the same time. If the result of each run is the same as the result of a single-threaded run, and the values of other variables are the same as expected, it is thread-safe. If each thread has only read operations on global variables and static variables, but no write operations, in general, this global variable is thread-safe; if multiple threads perform write operations (change variables) at the same time, generally you need to consider Thread synchronization, otherwise it may affect thread safety.

Thread synchronization : It can be understood that thread A and thread B cooperate together. When A executes to a certain extent, it depends on a certain result of thread B, so it stops and signals B to run; B executes according to the word, and then gives the result to A; Go ahead. A simple example is: two people chat together. Two people cannot speak at the same time to avoid unintelligible hearing (operation conflict). Wait for one person to finish (one thread ends the operation), and the other one (the other thread starts the operation again). Below, we simulate the way of train ticket sales to achieve NSThread thread safety and solve thread synchronization problems. Scenario: There are a total of 50 train tickets, and there are two windows for selling train tickets, one is the Beijing train ticket sales window, and the other is the Shanghai train ticket sales window. Two windows sell train tickets at the same time, while stocks last

Not thread safe (does not use semaphore)

Let's first look at code that does not consider thread safety:

/**
 *   semaphore
 *  ( ) 
 */
- (void)initTicketStatusNotSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"semaphore---begin");
    
    self.ticketSurplusCount = 50;
    
   //queue1  
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
   //queue2  
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketNotSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}

/**
 *  ( )
 */
- (void)saleTicketNotSafe {
    while (1) {
        
        if (self.ticketSurplusCount > 0) { //
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@" %d  %@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {//
            NSLog(@" ");
            break;
        }
        
    }
}
 

Output result (partial):

2018-02-23 22:25:35.789072+0800 YSC-GCD-demo[20712:5258914] currentThread---<NSThread: 0x604000068880>{number = 1, name = main} 2018-02-23 22:25:35.789260 +0800 YSC-GCD-demo[20712:5258914] semaphore---begin 2018-02-23 22:25:35.789641+0800 YSC-GCD-demo[20712:5259176] Remaining votes: 48 Window: <NSThread: 0x60000027db80> {number = 3, name = (null)} 2018-02-23 22:25:35.789646+0800 YSC-GCD-demo[20712:5259175] Remaining votes: 49 Window: <NSThread: 0x60000027e740>{number = 4, name = (null)} 2018-02-23 22:25:35.994113+0800 YSC-GCD-demo[20712:5259175] Remaining votes: 47 Window: <NSThread: 0x60000027e740>{number = 4, name = (null)} 2018 -02-23 22:25:35.994129+0800 YSC-GCD-demo[20712:5259176] Remaining votes: 46 Window: <NSThread: 0x60000027db80>{number = 3, name = (null)} 2018-02-23 22: 25:36.198993+0800 YSC-GCD-demo[20712:5259176] Remaining votes: 45 Window: <NSThread: 0x60000027db80>{number = 3,name = (null)) ......

It can be seen that when thread safety is not considered and semaphore is not used, the number of votes is confused. This obviously does not meet our needs, so we need to consider thread safety.

6.6.2.2 Thread safety (using semaphore to lock)

Consider thread-safe code:

/**
 *   semaphore  
 *  ( ) 
 */
- (void)initTicketStatusSave {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 
    NSLog(@"semaphore---begin");
    
    semaphoreLock = dispatch_semaphore_create(1);
    
    self.ticketSurplusCount = 50;
    
   //queue1  
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
   //queue2  
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });
    
    dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

/**
 *  ( )
 */
- (void)saleTicketSafe {
    while (1) {
       // 
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        
        if (self.ticketSurplusCount > 0) { //
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@" %d  %@", self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        } else {//
            NSLog(@" ");
            
           // 
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }
        
       // 
        dispatch_semaphore_signal(semaphoreLock);
    }
}
 

The output result is:

2018-02-23 22:32:19.814232+0800 YSC-GCD-demo[20862:5290531] currentThread---<NSThread: 0x6000000783c0>{number = 1, name = main} 2018-02-23 22:32:19.814412 +0800 YSC-GCD-demo[20862:5290531] semaphore---begin 2018-02-23 22:32:19.814837+0800 YSC-GCD-demo[20862:5290687] Remaining votes: 49 Window: <NSThread: 0x6040002709c0> {number = 3, name = (null)} 2018-02-23 22:32:20.017745+0800 YSC-GCD-demo[20862:5290689] Remaining votes: 48 Window: <NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:20.222039+0800 YSC-GCD-demo[20862:5290687] Remaining votes: 47 Window: <NSThread: 0x6040002709c0>{number = 3, name = (null)}. ..... 2018-02-23 22:32:29.024817+0800 YSC-GCD-demo[20862:5290689] Remaining votes: 4 Window: <NSThread: 0x60000046c640>{number = 4, name = (null)} 2018 -02-23 22:32:29.230110+0800 YSC-GCD-demo[20862:5290687] Remaining votes: 3 Window: <NSThread: 0x6040002709c0>{number = 3,name = (null)} 2018-02-23 22:32:29.433615+0800 YSC-GCD-demo[20862:5290689] Number of votes remaining: 2 Window: <NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:29.637572+0800 YSC-GCD-demo[20862:5290687] Remaining votes: 1 Window: <NSThread: 0x6040002709c0>{number = 3, name = (null)} 2018-02-23 22 :32:29.840234+0800 YSC-GCD-demo[20862:5290689] Number of votes remaining: 0 Window: <NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:30.044960+0800 YSC-GCD-demo[20862:5290687] All train tickets are sold out 2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] All train tickets are sold out840234+0800 YSC-GCD-demo[20862:5290689] Number of votes remaining: 0 Window: <NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:30.044960+0800 YSC-GCD -demo[20862:5290687] All train tickets are sold out 2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] All train tickets are sold out840234+0800 YSC-GCD-demo[20862:5290689] Number of votes remaining: 0 Window: <NSThread: 0x60000046c640>{number = 4, name = (null)} 2018-02-23 22:32:30.044960+0800 YSC-GCD -demo[20862:5290687] All train tickets are sold out 2018-02-23 22:32:30.045260+0800 YSC-GCD-demo[20862:5290689] All train tickets are sold out

It can be seen that after considering thread safety, after using the dispatch_semaphore mechanism, the number of votes obtained is correct, and there is no confusion. We also solved the problem of synchronization of multiple threads.