1. The GCD
**Grand Central Dispatch (GCD)** is a relatively new solution to multicore programming developed by Apple. It is used to optimize applications to support multi-core processors and other symmetric multiprocessing systems. It is a parallel task that executes on a thread pool basis.
GCD is a very efficient and powerful alternative to technologies such as NSThread. The GCD is fully capable of dealing with complex asynchronous programming issues such as data locking and resource leakage. GCD works by having a program, based on available processing resources, arrange them to queue in parallel to perform specific tasks on any available processor core. The task can be a function or a program segment.
Demo Download Address
The GCD advantages:
- It can be used for multi-core parallel operation
- Will automatically utilize more CPU cores (e.g. Dual-core, quad-core)
- Thread lifecycles are automatically managed (creating threads, scheduling tasks, destroying threads)
- The programmer just tells the GCD what task he wants to perform, and doesn’t have to write any thread management code
2. GCD task and queue
GCD has two cores: tasks and queues
Task: The meaning of performing an operation, in other words the piece of code that you execute in a thread. In GCD they are placed in blocks. There are two ways to execute a task: synchronous execution and asynchronous execution. The main differences are whether to wait for queued tasks to finish and whether to have the ability to start new threads.
- Sync: Synchronizes tasks to a specified queue. The system waits until the tasks in the queue are complete. You can only execute tasks in the current thread and do not have the ability to start new threads.
- Asynchronous execution (async) : Adds a task to a specified queue asynchronously, it does not do any waiting, can continue to execute the task. Tasks can be performed in new threads, with the ability to start new threads.
Note: Asynchronous execution has the ability to start new threads, but it does not necessarily start new threads. This depends on the queue type specified by the task
Queue: The queue here refers to the waiting queue for the execution of the task, that is, the queue used to store the task. A queue is a special type of linear table that uses 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. Each time a task is read, one task is released from the Queue -> Serial Dispatch Queue: Only one task is executed at a time, allowing tasks to be executed one after another
- Concurrent Dispatch Queue: Allows multiple tasks to execute concurrently. Multiple threads can be started and tasks can be executed simultaneously
Note: The concurrency function of a concurrent queue is valid only for asynchronous functions
3. Steps for using GCD
- Create a queue (serial queue or concurrent queue)
- The task is appended to the waiting queue of the task, and the system executes the task based on the task type
3.1 Method of Creating/Obtaining a Queue
- You can use
dispatch_queue_create
To create a Queue, pass in two parameters. The first parameter represents the unique identifier of the Queue, which is used for DEBUG and can be empty. The name of Dispatch Queue is recommended to use the reverse full domain name of the application ID. The second parameter identifies whether it is a serial queue or a concurrent queue.- DISPATCH_QUEUE_SERIAL: serial queue
- DISPATCH_QUEUE_CONCURRENT Indicates a concurrent queue
Dispatch_queue = dispatch_queue_create(" com.yangkejun.gcddemo ", DISPATCH_QUEUE_SERIAL); Dispatch_queue_t queue = dispatch_queue_create(" com.yangkejun.gcddemo ", DISPATCH_QUEUE_CONCURRENT);Copy the code
- For serial queues, GCD provides a special kind of serial Queue: The Main Dispatch Queue.
- All tasks placed in the main queue are executed in the main thread.
The main queue can be obtained using dispatch_get_main_queue().
Dispatch_queue_t queue = dispatch_get_main_queue();Copy the code
- For concurrent queues, GCD provides a Global Dispatch Queue by default.
- This can be obtained using dispatch_get_global_queue. You need to pass two parameters. The first parameter indicates the queue priority, typically DISPATCH_QUEUE_PRIORITY_DEFAULT. The second parameter is useless for the moment, just 0.
Dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);Copy the code
3.2 Methods for Creating a Task
- GCD provides methods for creating tasks that are executed synchronously
dispatch_sync
And asynchronous task creation methodsdispatch_async
{dispatch_sync(queue, ^{// put the task code for synchronous execution}); {dispatch_async(queue, ^{// put the async task code here});Copy the code
Although it takes only two steps to use GCD, since we have two queues (serial/concurrent) and two ways of executing tasks (synchronous/asynchronous), we have four different combinations. The four different combinations are:
- Synchronous execution + concurrent queue
- Asynchronous execution + concurrent queue
- Synchronous execution + serial queue
- Asynchronous execution + serial queue
In fact, I just mentioned two types of special queues: global concurrent queues and main queues. Global concurrent queues can be used as normal concurrent queues. But the main queue is a bit special, so we have two more combinations. So there are six different combinations:
- Synchronous execution + main queue
- Asynchronous execution + main queue
So what are the differences between these different combinations? Here, for convenience, first the results, and then explain. You can see the table results directly
The difference between | Concurrent queue | Serial queues | The home side column |
---|---|---|---|
synchronous | No new thread is started, the task is executed in serial | No new thread is started, the task is executed in serial | Any operation on the current queue sync in the same serial queue results in a deadlock |
asynchronous | New threads are started to execute tasks concurrently | A new thread is started to execute tasks sequentially | If async is in the current queue, no new threads will be started. Asyn the serial queue in another queue opens a new thread |
4. Basic use of GCD
4.1 Synchronous Execution + Concurrent Queue
- When a task is executed in the current thread, a new thread is not started. When a task is completed, the next task is executed.
/** * Synchronous execution + concurrent queue * Features: The current thread executes the task, will not start a new thread, the execution of a task, then execute the next task. */ - (void)syncConcurrent{ NSLog(@"syncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("com.yangkejun.GCDdemo", DISPATCH_QUEUE_CONCURRENT); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; NSLog(@" task 1"); }}); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; NSLog(@" task 2"); }}); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++i) { [NSThread sleepForTimeInterval:2]; NSLog(@" task 3"); }}); NSLog(@"syncConcurrent---end"); }Copy the code
2018-09-01 11:20:35.990306+0800 GCDdemo[7623:1164967] syncConcurrent-- begin 2018-09-01 11:20:37.991056+0800 GCDdemo[7623:1164967] Task 1 2018-09-01 11:20:39.992478+0800 GCDdemo[7623:1164967] Task 2 2018-09-01 11:20:45.994114 +0800 GCDdemo[7623:1164967] Task 3 2018-09-01 11:20:47.997277+0800 GCDdemo[7623:1164967] syncConcurrent---endCopy the code
From the synchronous execution + concurrent queue, you can see:
- All tasks are executed in the current thread (the main thread) and no new threads are started (synchronous execution does not have the ability to start new threads).
- All tasks are executed between the printed syncConcurrent– begin and syncConcurrent– end (synchronous tasks need to wait for queued tasks to finish).
- Tasks are executed in sequence. Reason for sequential execution: Although concurrent queues can start multiple threads and execute multiple tasks simultaneously. However, since you cannot create new threads by yourself, there is only one thread (synchronization tasks do not have the ability to start new threads), so there is no concurrency. And the current thread can only continue to perform the following operations after the current queue task is completed (synchronization task needs to wait for the end of the queue task). So tasks can only be executed one after the other in sequence, not simultaneously.
4.2 Asynchronous Execution + Concurrent Queue
- Multiple threads can be started and tasks can be executed alternately (simultaneously).
/** * Asynchronous execution + concurrent queue * Features: multiple threads can be started, and tasks can be executed alternately (simultaneously). */ - (void)asyncConcurrent { NSLog(@"asyncConcurrent---begin"); dispatch_queue_t queue = dispatch_queue_create("com.yangkejun.GCDdemo", DISPATCH_QUEUE_CONCURRENT); Dispatch_async (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); Dispatch_async (queue, ^{// add task 2 for (int I = 0; i < 2; ++ I) {NSLog(@" task 2"); }}); Dispatch_async (queue, ^{// add task 3 for (int I = 0; i < 3; ++ I) {NSLog(@task 3); }}); NSLog(@"asyncConcurrent---end"); }Copy the code
2018-09-01 11:24:11.550873+0800 GCDdemo[7663:1170782] asyncConcurrent-- begin 2018-09-01 11:24:11.551104+0800 GCDdemo[7663:1170782] asyncConcurrent-- end 2018-09-01 11:24:13.555075+0800 GCDdemo[7663:1170830] Task 3 2018-09-01 11:24:13.555094+0800 GCDdemo[7663:1170828] 2018-09-01 11:24:15.556107+0800 GCDdemo[7663:1170828] Task 2 2018-09-01 11:24:15.556169+0800 GCDdemo[7663:1170829] Task 1Copy the code
In asynchronous execution + concurrent queue we can see:
- In addition to the current thread (the main thread), three more threads are started, and tasks are executed alternately/simultaneously. Asynchronous execution has the ability to start new threads. And the concurrent queue can enable multiple threads to execute multiple tasks simultaneously).
- All tasks are printed
syncConcurrent---begin
andsyncConcurrent---end
It was executed after that. Indicates that the current thread does not wait, but directly starts a new thread and executes tasks in the new thread (asynchronous execution does not wait and can continue to execute tasks).
4.3 Synchronous execution + serial queue
- New threads are not started and tasks are executed on the current thread. Tasks are sequential, executing one task before executing the next
/** * synchronous execution + serial queue * features: no new threads are opened, tasks are executed in the current thread. Tasks are sequential, executing one task before executing the next. */ - (void)syncSerial { NSLog(@"syncSerial---begin"); dispatch_queue_t queue = dispatch_queue_create("com.yangkejun.GCDdemo", DISPATCH_QUEUE_SERIAL); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@" task 2"); }}); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 3); }}); NSLog(@"syncSerial---end"); }Copy the code
2018-09-01 11:32:21.317523+0800 GCDdemo[7812:1184572] syncSerial-- begin 2018-09-01 11:32:21.317523+0800 GCDdemo[7812:1184572] Task 1 2018-09-01 11:32:21.317688+0800 GCDdemo[7812:1184572] Task 2 2018-09-01 11:32:21.318036+0800 GCDdemo[7812:1184572] Task 3 2018-09-01 11:32:21.318398+0800 GCDdemo[7812:1184572] syncSerial---endCopy the code
In synchronous execution + serial queue you can see:
- All tasks are executed in the current thread (the main thread) and no new threads are started (synchronous execution does not have the ability to start new threads).
- All the tasks are being printed
syncConcurrent---begin
andsyncConcurrent---end
(The synchronization task needs to wait for the completion of the queued task). - Tasks are executed sequentially (only one task is executed at a time in a serial queue, and tasks are executed sequentially one after another)
4.4 Asynchronous Execution + serial queue
- New threads are started, but because tasks are serial, one task is executed before the next
/** * Asynchronous execution + serial queue * Features: new threads are started, but because tasks are serial, the next task is executed after the completion of one task. */ - (void)asyncSerial { NSLog(@"asyncSerial---begin"); dispatch_queue_t queue = dispatch_queue_create("com.yangkejun.GCDdemo", DISPATCH_QUEUE_SERIAL); Dispatch_async (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); Dispatch_async (queue, ^{// add task 2 for (int I = 0; i < 2; ++ I) {NSLog(@" task 2"); }}); Dispatch_async (queue, ^{// add task 3 for (int I = 0; i < 3; ++ I) {NSLog(@task 3); }}); NSLog(@"asyncSerial---end"); }Copy the code
2018-09-01 11:34:28.644771+0800 GCDdemo[7841:1188565] asyncSerial-- begin 2018-09-01 11:34:28.645016+0800 GCDdemo[7841:1188565] asyncSerial-- end 2018-09-01 11:34:28.645055+0800 GCDdemo[7841:1188668] Task 1 2018-09-01 Task 1: 2018-09-01 11:34:28.645368+0800 GCDdemo[7841:1188668] Task 2:2018-09-01 11:34:28.645497+0800 GCDdemo[7841:1188668] Task 2 2018-09-01 2018-09-01 11:34:28.645937+0800 GCDdemo[7841:1188668] Task 3Copy the code
In asynchronous execution + serial queue you can see:
- A new thread is started (asynchronous execution has the ability to start new threads, serial queues only have one thread started).
- All tasks are printed
syncConcurrent---begin
andsyncConcurrent---end
(Asynchronous execution does not do any waiting, can continue to execute the task). - Tasks are executed sequentially (only one task is executed at a time in a serial queue, and tasks are executed sequentially one after another)
4.5 Synchronous Execution + Main queue
Synchronous execution + main queue is called from thread to thread with different results, the main thread in the call deadlock, but not in other threads
Main queue: a special serial queue that comes with the GCD
- All tasks placed in the main queue are executed in the main thread
- You can use
dispatch_get_main_queue()
Get the main queue
4.5.1 Call synchronous execution + main queue in the main thread
- Waiting for each other is not feasible
/** * Synchronous execution + main queue * feature (main thread call) : interwaiting card main does not execute. * Features (called by other threads) : no new threads will be started, and after executing one task, the next task will be executed. */ - (void)syncMain { NSLog(@"syncMain---begin"); dispatch_queue_t queue = dispatch_get_main_queue(); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@" task 2"); }}); Dispatch_sync (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 3); }}); NSLog(@"syncMain---end"); }Copy the code
In synchronous execution + main queue can be surprised to find:
- Put in the main thread of the call, will report an error stuck, as shown in the error
Console print:
The 2018-09-01 11:40:20. 241566 + 0800 GCDdemo [7940-1198784] syncMain - the beginCopy the code
Task 1, task 2, and task 3 are no longer executed, and syncmain-end is not printed, and will crash in XCode 9. Why is that?
This is because we execute the syncMain method in the main thread, effectively putting the syncMain task in the queue on the main thread. Synchronous execution waits for tasks in the current queue to complete before continuing. So when we append task 1 to the main queue, task 1 is waiting for the main thread to finish processing the syncMain task. The syncMain task waits for task 1 to complete before executing.
So, we have a situation where the syncMain task and task 1 are waiting for each other to complete. So everyone was waiting for each other, so we got stuck, so our task didn’t work, and syncmain-end didn’t print.
4.5.2 Call synchronous execution + main queue in other threads
- New threads are not started, and one task is executed before the next
// Using the detachNewThreadSelector method of NSThread creates the thread, NSThread detachNewThreadSelector: @Selector (syncMain) toTarget:self withObject:nil];Copy the code
Console printing
2018-09-01 11:45:42.849981+0800 GCDdemo[8014:1207496] syncMain-- begin 2018-09-01 11:45:42.854214+0800 GCDdemo[8014:1207397] Task 1 2018-09-01 11:45:42.854358+0800 GCDdemo[8014:1207397] Task 2 2018-09-01 11:45:42.855585+0800 [8014:1207397] 2018-09-01 11:45:42.856550+0800 GCDdemo task 3 2018-09-01 11:45:42.856742+0800 GCDdemo[8014:1207496] syncMain---endCopy the code
Use synchronous execution + main queue in other threads to see:
- All tasks are executed in the main thread (not the current thread), no new threads are opened (all tasks placed in the main queue are executed in the main thread).
- All tasks are executed between the printed syncConcurrent– begin and syncConcurrent– end (synchronous tasks need to wait for queued tasks to finish).
- Tasks are executed sequentially (the main queue is a serial queue where only one task is executed at a time, and tasks are executed sequentially one after another).
Why isn’t it stuck now? Because the syncMain task is in another thread, and task 1, task 2, and task 3 are appended to the main queue, all three tasks are executed in the main thread. The syncMain task is executed in other threads to append task 1 to the main queue. Because there is no task being executed in the main queue, task 1 in the main queue will be executed directly. After task 1 is executed, task 2 and task 3 will be executed. So I’m not going to get stuck here
4.6 Asynchronous Execution + Main Queue
- Tasks are executed only in the main thread, executing one task before executing the next.
/** * asyncMain {NSLog(@"asyncMain-- begin"); /** * asyncMain {NSLog(@"asyncMain-- begin"); dispatch_queue_t queue = dispatch_get_main_queue(); Dispatch_async (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); Dispatch_async (queue, ^{// add task 2 for (int I = 0; i < 2; ++ I) {NSLog(@" task 2"); }}); Dispatch_async (queue, ^{// add task 3 for (int I = 0; i < 3; ++ I) {NSLog(@task 3); }}); NSLog(@"asyncMain---end"); }Copy the code
2018-09-01 11:50:00.355229+0800 GCDdemo[8072:1214346] asyncMain-- begin 2018-09-01 11:50:00 GCDdemo[8072:1214346] asyncMain-- end 2018-09-01 11:50:00.360197+0800 GCDdemo[8072:1214346 [8072:1214346] Mission 1 2018-09-01 11:50:00 [8072:1214346] Mission 2 2018-09-01 11:50:00 2018-09-01 11:50:00.361327+0800 GCDdemo[8072:1214346] Task 3Copy the code
In the asynchronous execution + main queue you can see:
- All tasks are executed in the current thread (the main thread) and no new threads are started (although asynchronous execution has the ability to start threads, because it is the main queue, all tasks are in the main thread).
- All tasks are executed after the printed syncConcurrent– begin and syncConcurrent– end (asynchronous execution does not do any waiting and can continue the task).
- Tasks are executed sequentially (because the master queue is a serial queue, only one task is executed at a time, and tasks are executed sequentially one after another).
5. Communication between GCD threads
During iOS development, we usually do UI refresh in the main thread, such as: click, scroll, drag and drop events. We usually put time-consuming operations on other threads, such as image downloads, file uploads, etc. Thread to thread communication is used when we sometimes need to return to the main thread after another thread has completed a time-consuming operation.
/** * inter-thread communication */ - (void)communication {// Get global concurrent queue 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) {NSLog(@" async append task "); } // Return to the main thread dispatch_async(mainQueue, ^{// append NSLog(@" append task in the main thread "); }); }); }Copy the code
2018-09-01 11:52:14.446526+0800 GCDdemo 2018-09-01 11:52:14.446526+0800 GCDdemo[8102:1218564] Asynchronous Appending Tasks 2018-09-01 11:52:14.450938+0800 GCDdemo[8102:1218530] Appending tasks executed in the main threadCopy the code
6. Other methods of GCD
6.1 GCD fence method: dispatch_barrier_async
- Sometimes we need to perform two sets of operations asynchronously, and only after the first set of operations is completed can we begin to perform the second set of operations. What we need is a palisade to separate two groups of asynchronously executed operations, which can contain one or more tasks. That’s where it comes in
dispatch_barrier_async
Methods A fence was formed between two operation groups.
The “dispatch_barrier_async” function waits for all the previously added tasks to the concurrent queue to be completed before adding the specified task to the asynchronous queue. After the task appended by the dispatch_barrier_Async function is completed, the asynchronous queue will revert to the normal action, and then the task will be appended to the asynchronous queue and execution will begin.
/** * Barrier method dispatch_barrier_async */ - (void)barrier{dispatch_queue_t queue = dispatch_queue_create("com.yangkejun.GCDdemo", DISPATCH_QUEUE_CONCURRENT); Dispatch_async (queue, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); Dispatch_async (queue, ^{// add task 2 for (int I = 0; i < 3; ++ I) {NSLog(@" task 2"); }}); Dispatch_barrier_async (queue, ^{ i < 2; ++ I) {NSLog(@" barrier"); }}); Dispatch_async (queue, ^{// add task 3 for (int I = 0; i < 3; ++ I) {NSLog(@task 3); }}); Dispatch_async (queue, ^{// add task 4 for (int I = 0; i < 2; ++ I) {NSLog(@" task 4"); }}); }Copy the code
2018-09-01 11:56:55.999942+0800 GCDdemo[8164:1226338] Task 2 2018-09-01 11:56:55.999942+0800 GCDdemo 2018-09-01 11:56:56.000127+0800 GCDdemo[8164:1226341] Task 1: 2018-09-01 11:56:56.000127+0800 GCDdemo 2018-09-01 11:56:56.002212+0800 GCDdemo[8164:1226338] Task 2 2018-09-01 11:56:56.002415+0800 GCDdemo[8164:1226338] Additional task [8164:1226338] Barrier 2018-09-01 11:56:56.004423+0800 GCDdemo[8164:1226338] Task 3 2018-09-01 11:56:56.004423+0800 GCDdemo[8164:1226338] Task 3 2018-09-01 11:56:56.007973+0800 GCDdemo[8164:1226341] Task 4 2018-09-01 11:56:56.008898+0800 Task 3 GCDdemo [8164-1226338]Copy the code
The result of dispatch_barrier_async can be seen:
- After performing the operation in front of the fence, the fence operation is performed, and finally the operation behind the fence
6.2 GCD delay execution method: dispatch_after
- Delayed execution: A task is executed after a specified time (for example, 3 seconds). This can be done using GCD’s dispatch_after function.
Note that the dispatch_after function does not start processing after a specified time, but rather appends tasks to the main queue after a specified time. Strictly speaking, this time is not absolutely accurate, but the dispatch_after function is useful for roughly delaying the execution of tasks.
/** * dispatch_after */ - (void)after{NSLog(@"after-begin"); Int64_t delayInSeconds = 5.0; // Delay time(dispatch_time(DISPATCH_TIME_NOW, (int64_t))(delayInSeconds * NSEC_PER_SEC)), Dispatch_get_main_queue (), ^{// Append the task code to the main queue asynchronously after 5.0 seconds and start NSLog(@"after-end"); }); }Copy the code
As can be seen from the time in the figure:
6.3 GCD One-time code (executed only once) : dispatch_once
- We use GCD’s dispatch_once function when we create singletons, or when we have code that only executes once during the entire program. use
dispatch_once
The dispatch_once function ensures that a piece of code is executed only once during the course of a program, and that dispatch_once is thread safe even in multithreaded environments.
/** * dispatch_token */ - (void) {static dispatch_once_t onceToken; Dispatch_once (&onceToken, ^{// execute code only once(thread safe by default)}); }Copy the code
6.4 GCD fast iteration method: dispatch_apply
- Normally we would use a for loop to iterate, but GCD gives us the function dispatch_apply to iterate quickly. Dispatch_apply Appends a specified task to a specified queue at a specified number of times and waits for the completion of all queues.
If dispatch_apply is used in a serial queue, it is executed synchronously in the same order as the for loop. But that doesn’t make sense to iterate quickly. We can use concurrent queues for asynchronous execution. For example, if you’re going through the numbers 0 through 5, the for loop is going through one element at a time. Dispatch_apply can traverse multiple numbers simultaneously (asynchronously) in multiple threads. Another point is that dispatch_apply waits for all tasks to complete, whether in a serial queue or an asynchronous queue, just like a synchronous operation or the dispatch_group_WAIT method in a queue group.
/** * Quick iterative method 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); }); NSLog(@"apply---end"); }Copy the code
2018-09-01 12:04:06.176242+0800 GCDdemo[8269:1238786] apply-- begin 2018-09-01 12:04:06.177222+0800 GCDdemo[8269:1238786] 1 2018-09-01 12:04:06.177222+0800 GCDdemo[8269:1238836] 1 2018-09-01 12:04:06.177237+0800 [8269:1238834] 2 2018-09-01 12:04:06.177257+0800 GCDdemo[8269:1238837] 3 2018-09-01 12:04:06.17784 +0800 GCDdemo[8269:1238786] 5 2018-09-01 12:04:06.177384+0800 GCDdemo[8269:1238836] 4 2018-09-01 12:04:06.177673+0800 GCDdemo[8269:1238786] apply---endCopy the code
Because tasks are executed asynchronously in a concurrent queue, the execution time of each task varies, and the order in which it ends. But apply- end must be executed at the end. This is because the dispatch_apply function waits for all tasks to complete
6.5 GCD queue group: dispatch_group
Sometimes there is a need to execute two time-consuming tasks asynchronously and then return to the main thread when both time-consuming tasks have been executed. In this case, we can use GCD’s queue groups.
- Call the queue group’s dispatch_group_async to put the task into the queue and then the queue into the queue group. Or use the combination of dispatch_group_Enter and dispatch_group_leave of queue groups to implement dispatch_group_async.
- Call the queue group’s dispatch_group_notify to return the task to the specified thread. Or you can use dispatch_group_WAIT to return to the current thread and continue execution down (blocking the current thread)
6.5.1 dispatch_group_notify
- Listen for the completion status of tasks in the group. When all tasks are completed, add tasks to the group and execute them.
/** * Queue group dispatch_group_notify */ - (void)groupNotify{NSLog(@"group-- begin"); dispatch_group_t group = dispatch_group_create(); Dispatch_group_async (group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// Add task 1 for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); ^{dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// Add task 2 for (int I = 0; i < 3; ++ I) {NSLog(@" task 2"); }}); For (int I = 0; for (int I = 0; for (int I = 0; i < 2; ++ I) {NSLog(@" after async task 1, async task 2, async task 1 "); } NSLog(@"group---end"); }); }Copy the code
2018-09-01 12:06:47.296416+0800 GCDdemo[8327:1243795] group-- begin 2018-09-01 12:06:47.296645+0800 GCDdemo[8327:1243901] Task 1 2018-09-01 12:06:47.296646+0800 GCDdemo[8327:1243902] Task 2 2018-09-01 12:06:47.296780+0800 GCDdemo[8327:1243901] Task 1 2018-09-01 12:06:47.296873+0800 GCDdemo[8327:1243902] Task 2 2018-09-01 12:06:47.297396+0800 GCDdemo[8327:1243902] task 2 2018-09-01 12:06:47.301247+0800 2018-09-01 12:06:47.301386+0800 GCDdemo[8327:1243795] 2018-09-01 12:06:47.301485+0800 GCDdemo[8327:1243795] group-- endCopy the code
The output of the dispatch_group_notify code shows that a task in the dispatch_group_notify block is executed only after all tasks are executed.
Tactical fix packs for 6.5.2 dispatch_group_wait
- Suspend the current thread (block the current thread) and wait for the completion of the task in the specified group before continuing execution.
/** * queue group dispatch_group_wait */ - (void)groupWait{NSLog(@"group-- begin"); dispatch_group_t group = dispatch_group_create(); Dispatch_group_async (group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// Add task 1 for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); ^{dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// Add task 2 for (int I = 0; i < 2; ++ I) {NSLog(@" task 2"); }}); Dispatch_group_wait (group, DISPATCH_TIME_FOREVER); NSLog(@"group---end"); }Copy the code
2018-09-01 12:09:25.744072+0800 GCDdemo[8366:1248429] group-- begin 2018-09-01 12:09:25.744328+0800 GCDdemo[8366:1248479] 2018-09-01 12:09:25.744328+0800 GCDdemo[8366:1248479] 2018-09-01 12:09:25.744469+0800 GCDdemo[8366:1248429] group---endCopy the code
According to the output of dispatch_group_wait code, operations after dispatch_group_wait are performed only after all tasks are executed. However, using dispatch_group_WAIT blocks the current thread.
6.5.3 dispatch_group_enter, dispatch_group_leave
- Dispatch_group_enter Indicates that a task is added to a group and executed once. This is equivalent to the number of unfinished tasks in the group +1
- Dispatch_group_leave indicates that a task has left the group and is executed once. This is equivalent to the number of unfinished tasks in the group -1.
- Only when the number of unfinished tasks in the dispatch_group_wait group is 0, the block of dispatch_group_WAIT is unblocked and the tasks added to dispatch_group_notify are executed.
/** * queue group dispatch_group_wait */ - (void)groupWait{NSLog(@"group-- begin"); dispatch_group_t group = dispatch_group_create(); Dispatch_group_async (group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// Add task 1 for (int I = 0; i < 2; ++ I) {NSLog(@task 1); }}); ^{dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{// Add task 2 for (int I = 0; i < 2; ++ I) {NSLog(@" task 2"); }}); Dispatch_group_wait (group, DISPATCH_TIME_FOREVER); NSLog(@"group---end"); } /** * queue group dispatch_group_enter, dispatch_group_leave */ - (void)groupEnterAndLeave{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, ^{for (int I = 0; i < 2; ++ I) {NSLog(@task 1); } dispatch_group_leave(group); }); dispatch_group_enter(group); Dispatch_async (queue, ^{// add task 2 for (int I = 0; i < 2; ++ I) {NSLog(@" task 2"); } dispatch_group_leave(group); }); For (int I = 0; int I = 0; for (int I = 0; i < 2; ++ I) {NSLog(@" return to main thread when all previous asynchronous operations are completed "); } NSLog(@"group---end"); }); }Copy the code
2018-09-01 12:11:26.418948+0800 GCDdemo[8393:1252025] group-- begin 2018-09-01 12:11:26.419198+0800 GCDdemo[8393:1252093] Task 2 2018-09-01 12:11:26.419202+0800 GCDdemo[8393:1252092] Task 1 2018-09-01 12:11:26.419366+0800 GCDdemo[8393:1252092] Task 1 2018-09-01 12:11:26.419432+0800 GCDdemo[8393:1252093] Task 2 2018-09-01 12:11:26.424470+0800 2018-09-01 12:11:26.424751+0800 GCDdemo[8393:1252025] returns to the main thread after the previous asynchronous operations are completed. 2018-09-01 12:11:26.425571+0800 GCDdemo[8393:1252025] group-- endCopy the code
As shown in the operation results of dispatch_group_Enter and dispatch_group_leave codes, tasks in dispatch_group_notify are executed only after all tasks are executed. The combination of “dispatch_group_Enter” and “dispatch_group_leave” is equivalent to “dispatch_group_async”.
6.6 GCD semaphore: dispatch_semaphore
- A Semaphore in GCD is a Dispatch Semaphore, which is a signal that holds a count. Similar to the railings used to cross a highway toll booth. Open the railing when it is possible to pass, and close the railing when it is not possible to pass. In Dispatch Semaphore, a count is used to accomplish this function, waiting when the count is zero and not passing. When the count is 1 or greater, the count is subtracted by 1 and passes without waiting.
Dispatch Semaphore provides three functions.
- Dispatch_semaphore_create: Creates a Semaphore and initializes the total number of signals
- Dispatch_semaphore_signal: Sends a signal and increases the total number of signals by 1
- Dispatch_semaphore_wait: reduces the total semaphore by 1. When the total semaphore is 0, it will wait (blocking the thread), otherwise it will execute normally.
Note: The semaphore is used only if you figure out which thread you want to wait (block) and which thread you want to continue executing, and then use the semaphore.
The Dispatch Semaphore is mainly used in actual development:
- Keep threads synchronized to convert asynchronously executed tasks to synchronously executed tasks
- Ensure thread safety by locking the thread
6.6.1 Dispatch Semaphore Thread synchronization
- Dispatch Semaphore implements thread synchronization, converting asynchronous execution tasks to synchronous execution tasks.
/** * semaphoreSync */ - (void)semaphoreSync{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, ^{// append task 1 NSLog(@" task 1"); number = 10; dispatch_semaphore_signal(semaphore); }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"semaphore---end,number = %d",number); }Copy the code
2018-09-01 12:21:30.061743+0800 GCDdemo[8488:1267170] semaphore-- begin 2018-09-01 12:21:30.061998+0800 GCDdemo[8488:1267223] task 1 2018-09-01 12:21:30.062167+0800 GCDdemo[8488:1267170] Semaphore --end,number = 10Copy the code
Semaphore –end = 10; number = 10; It was printed later. And the output number is 10. This is because asynchronous execution does not do any waiting and can continue executing the task. Task 1 is appended to the queue without waiting. The dispatch_semaphore_WAIT method is executed. When semaphore == 0, the current thread enters the wait state. Then, asynchronous task 1 starts executing. Semaphore == 1. The “dispatch_semaphore_wait” method reduces the total semaphore by 1. The blocked thread (main thread) resumes. Semaphore –end,number = 10. In this way, thread synchronization is realized and asynchronous execution tasks are transformed into synchronous execution tasks
6.6.2 Dispatch Semaphore Thread Security and Thread Synchronization (Locking threads)
- Thread-safe: If your code is in a process where multiple threads are running at the same time, these threads may run the code at the same time. It is thread-safe if the result of each run is the same as that of a single threaded run, and if the values of other variables are the same as expected.
In general, global variables are thread-safe if each thread has only read and no write operations on them. When multiple threads are writing (changing variables) at the same time, thread synchronization is generally considered, otherwise thread safety may be compromised.
- Thread synchronization: it can be understood that thread A and thread B cooperate together. When A is executed to A certain extent, it depends on some result of thread B, so it stops to indicate that thread B is running. B does what he says and gives the result to A; A Continue the operation.
Here’s a simple example: Two people are talking. Two people cannot speak at the same time to avoid inaudible (operation conflict). Wait for one person to finish (one thread terminates the operation), and the other will say (another thread starts the operation).
Next, let’s simulate the way train tickets are sold to implement NSThread thread safety and solve the thread synchronization problem.
Scene: There are 50 train tickets in total, and there are two Windows for selling train tickets, one for Beijing and the other for Shanghai. Train tickets will be sold at both Windows until all tickets are sold out
6.6.2.2 Thread Safety (Locking with Semaphore)
To consider thread-safe code:
/** * Thread safe: use semaphore locking * initialize the number of train tickets, ticket window (thread safe), and start selling tickets */ - (void)initTicketStatusSave{NSLog(@"semaphore-- begin"); semaphoreLock = dispatch_semaphore_create(1); ticketCount = 10; Dispatch_queue_t queue1 = dispatch_queue_create(" com.yangkejun.gcddemo1 ", DISPATCH_QUEUE_SERIAL); // dispatch_queue_t queue2 = dispatch_queue_create(" com.yangkejun.gcddemo2 ", DISPATCH_QUEUE_SERIAL); __weak typeof(self) weakSelf = self; dispatch_async(queue1, ^{ [weakSelf saleTicketSafe]; }); dispatch_async(queue2, ^{ [weakSelf saleTicketSafe]; }); } /** * sell train tickets (thread safe) */ - (void)saleTicketSafe{while (1) {// DISPATCH_TIME_FOREVER); If (ticketCount > 0) {if (ticketCount > 0); NSLog(@"%@", [NSString stringWithFormat:@" %d window :%@", ticketCount, [NSThread currentThread]]); [NSThread sleepForTimeInterval: 0.2]; } else {// close the ticket window NSLog(@" all tickets are sold out :%@",[NSThread currentThread]); // Dispatch_semaphore_signal (semaphoreLock); break; } // Dispatch_semaphore_signal (semaphoreLock); }}Copy the code
Console printing
2018-09-01 12:56:372291 +0800 GCDdemo[8931:1322530] Semaphore --begin 2018-09-01 12:56:372646 +0800 GCDdemo[8931:1322575] Remaining votes :9 Window :<NSThread: 0x6000004676c0>{number = 3, name = (null)} 2018-09-01 12:56:00.577730+0800 GCDdemo[8931:1322577] 0x604000261f40>{number = 4, name = (null)} 2018-09-01 12:56:00.780380+0800 GCDdemo[8931:1322575] 0x6000004676c0>{number = 3, name = (null)} 2018-09-01 12:56:00.984349+0800 GCDdemo[8931:1322577] 0x604000261f40>{number = 4, name = (null)} 2018-09-01 12:56:01.188601+0800 GCDdemo[8931:1322575] 0x6000004676c0>{number = 3, name = (null)} 2018-09-01 12:56:01.393087+0800 0x604000261f40>{number = 4, name = (null)} 2018-09-01 12:56:01.598030+0800 GCDdemo[8931:1322575] 0x6000004676c0>{number = 3, name = (null)} 2018-09-01 12:56:01.803240+0800 GCDdemo[8931:1322577] 0x604000261f40>{number = 4, name = (null)} 2018-09-01 12:56:02.007515+0800 GCDdemo[8931:1322575] 0x6000004676c0>{number = 3, name = (null)} 2018-09-01 12:56:02.211508+0800 GCDdemo[8931:1322577] 0x604000261f40>{number = 4, Name = (null)} 2018-09-01 12:56:02.416522+0800 GCDdemo[8931:1322575] 0x6000004676c0>{number = 3, Name = (null)} 2018-09-01 12:56:02.416968+0800 GCDdemo[8931:1322577] 0x604000261f40>{number = 4, name = (null)}Copy the code
As you can see, after using the dispatch_Semaphore mechanism with thread safety in mind, the number of votes is correct and there is no confusion. We have solved the problem of multiple threads synchronizing.
6.7 Disabling the GCD Task
- Close outstanding: calls
dispatch_block_cancel
To cancel
Note You must use dispatch_block_CREATE to create dispatch_block_t
This method can be used to cancel the items that have not been executed. If they have been executed, they cannot be canceled
- Close running: Use
dispatch_block_testcancel
methods
7. GCD lazy package use
It has been packaged into macros and simple functions for ease of use
7.1 the macro encapsulation
// Synchronization blocks the main thread #pragma mark ********** 11. #define kGCD_MAIN_ASYNC(main_queue_block) dispatch_async(dispatch_get_main_queue(), #define kGCD_QUEUE_ASYNC(global_queue_block) dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), Global_queue_block) #define kGCD_ONCE_BLOCK(onceBlock) static dispatch_once_t onceToken; dispatch_once(&onceToken, onceBlock); #define kGCD_GROUP_ASYNC(group_ASYNc_block,group_notify_block) \ dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); \ dispatch_group_t group = dispatch_group_create(); \ dispatch_group_async(group, queue, group_async_block); \ dispatch_group_notify(group, queue, ^{\ dispatch_async(dispatch_get_main_queue(), group_notify_block); KGCD_Async_MainQueue ({self.button.padding = 20; }); #define kGCD_Async_MainQueue(x) \ __weak typeof(self) weakSelf = self; \ dispatch_async(dispatch_get_main_queue(), ^{ \ typeof(weakSelf) self = weakSelf; \ {x} \ });Copy the code
7.2 Simple function encapsulation
#pragma mark-gcd thread processing ns_dispatch_queue_t kGCD_queue(void) {dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); return queue; } NS_INLINE void kGCD_main(dispatch_block_t block) {dispatch_queue_t queue = dispatch_get_main_queue(); if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(queue)) == 0) { block(); }else{ if ([[NSThread currentThread] isMainThread]) { dispatch_async(queue, block); }else{ dispatch_sync(queue, block); }} NS_INLINE void kGCD_async(dispatch_block_t block) {dispatch_queue_t queue = kGCD_queue(); if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(queue)) == 0) { block(); }else{ dispatch_async(queue, block); NS_INLINE void kGCD_group_notify(dispatch_block_t notify,dispatch_block_t block,...) { dispatch_queue_t queue = kGCD_queue(); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, block); va_list args; dispatch_block_t arg; va_start(args, block); while ((arg = va_arg(args, dispatch_block_t))) { dispatch_group_async(group, queue, arg); } va_end(args); dispatch_group_notify(group, queue, notify); } ns_dispatch_queue_t kGCD_barrier(dispatch_block_t block,dispatch_block_t barrier) {dispatch_queue_t queue = kGCD_queue(); dispatch_async(queue, block); dispatch_barrier_async(queue, ^{ dispatch_async(dispatch_get_main_queue(), barrier); }); return queue; } NS_INLINE void kGCD_once(dispatch_block_t block) {static dispatch_once_t onceToken; dispatch_once(&onceToken, block); } NS_INLINE void kGCD_after(int64_t delayInSeconds, dispatch_block_t block) { dispatch_queue_t queue = kGCD_queue(); dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(time, queue, block); } NS_INLINE void kGCD_after_main(int64_t delayInSeconds, dispatch_block_t block) { dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(time, dispatch_get_main_queue(), block); } NS_INLINE void kGCD_main_after(int64_t delayInSeconds, dispatch_block_t block) { dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(time, dispatch_get_main_queue(), block); } NS_INLINE void kgcd_iterations (int iterations, void(^block)(size_t idx)) {dispatch_queue_t queue = kGCD_queue(); dispatch_apply(iterations, queue, block); } NS_INLINE void kGCD_apply_array(NSArray * temp, void(^block)(id obj, size_t index)) { void (^xxblock)(size_t) = ^(size_t index){ block(temp[index],index); }; dispatch_apply(temp.count, kGCD_queue(), xxblock); } static dispatch_source_t gcd_timer = nil; NS_INLINE dispatch_source_t kGCD_timer(int64_t delayInSeconds, dispatch_block_t block) { if (gcd_timer) dispatch_source_cancel(gcd_timer); dispatch_queue_t queue = kGCD_queue(); gcd_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_source_set_timer(gcd_timer, dispatch_walltime(nil, 0), delayInSeconds * NSEC_PER_SEC, 0); dispatch_source_set_event_handler(gcd_timer, block); dispatch_resume(gcd_timer); return gcd_timer; }Copy the code