The main concepts involved
- Task: Code placed in a block that can be executed either sync or async
- Queue:Serial Queue 和 Concurrent Queue
- Main queue: Essentially a serial queue
- Global queue: Essentially a concurrent queue
Knowledge:
- Tasks in the main queue must be executed on the main thread. The main thread can execute tasks that are not in the main queue
- Synchronous execution does not open up child threads and executes on the current thread
- Asynchronous execution has the ability to create subthreads, but not necessarily, as we’ll see later
- Synchronous execution A serial queue executes sequentially
- Synchronous execution Concurrent queues are still executed sequentially
- Executing a serial queue asynchronously creates a child thread that executes the queued tasks in order
- Asynchronous execution of a concurrent queue has the ability to create multiple child threads
There are two execution modes of tasks, four types of queues, and a total of eight combinations, as follows:
Synchronous execution + serial queue
dispatch_queue_t queueSerial = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
void(^task)(void) = ^{
NSLog(@"task--%@",[NSThread currentThread]);
};
void(^task1)(void) = ^{
NSLog(@"task1--%@",[NSThread currentThread]);
};
dispatch_sync(queueSerial, task);
dispatch_sync(queueSerial, task1);
NSLog(@"end");
Copy the code
End = task1; end = task1; end = task1; end = task1;
dispatch_sync(queueSerial, task);
Copy the code
- Synchronous execution
dispatch_sync
Block current thread (main thread) - Serial queue
queueSerial
Insert task intask
- Executes on the current thread (main thread)
task
task task
Task completion unblocks the current thread (main thread)
dispatch_sync(queueSerial, task1); / / in the same wayCopy the code
- Synchronous execution
dispatch_sync
Block current thread (main thread) - Serial queue
queueSerial
Insert task intask1
- Executes on the current thread (main thread)
task1
task task1
Task completion unblocks the current thread (main thread)
Task ->task1->end
Synchronous execution + concurrent queue
dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);
void(^task)(void) = ^{
NSLog(@"task--%@",[NSThread currentThread]);
};
void(^task1)(void) = ^{
NSLog(@"task1--%@",[NSThread currentThread]);
};
dispatch_sync(queueConcurrent, task);
dispatch_sync(queueConcurrent, task1);
NSLog(@"end");
Copy the code
In the current thread synchronous execution, both serial queue and concurrent queue tasks are executed in order, and the result is consistent with synchronous execution + serial queue
Execute global queues synchronously
Dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0); dispatch_get_global_queue(0, 0); void(^task)(void) = ^{ NSLog(@"task--%@",[NSThread currentThread]); }; void(^task1)(void) = ^{ NSLog(@"task1--%@",[NSThread currentThread]); }; dispatch_sync(queueGlobal, task); dispatch_sync(queueGlobal, task1); NSLog(@"end");Copy the code
The global queue is a concurrent queue in nature, and its result is consistent with synchronous execution + concurrent queue
Execute the main queue synchronously
dispatch_queue_t queueMain = dispatch_get_main_queue(); void(^task)(void) = ^{ NSLog(@"task--%@",[NSThread currentThread]); }; void(^task1)(void) = ^{ NSLog(@"task1--%@",[NSThread currentThread]); }; dispatch_sync(queueMain, task); // I am also a task to be recorded as sync1 dispatch_sync(queueMain, task1); NSLog(@"end");Copy the code
This is a special case that causes deadlock because the statement dispatch_sync(queueMain, task) is executed synchronally; A sync1 task adds a task to the main queue and synchronously executes the task on the current thread (main thread). In other words, a sync1 task is executed only when the task is completed. Because the task is inserted into the main queue after the sync1 task, the main thread will execute the task task only after the sync1 task has been executed, and the sync1 task is executed only after the task has been executed. Therefore, the main thread will enter the deadlock state and cause the crash.
A similar
dispatch_queue_t queueSerial = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL); Void (^task1)(void) = ^{NSLog(@" deadlock "); }; void(^task2)(void) = ^{ dispatch_sync(queueSerial, task1); }; Dispatch_async (queueSerial, task2) dispatch_async(queueSerial, task2);Copy the code
dispatch_async(queueSerial, task2); The task itself is called sync1, but the difference is that sync1 is executed in the current thread (the main thread), Task1 = task1; task1 = task1; task1 = task1; task1 = task1; task1 = task1; Task2 is added to the queueSerial queue first than task1, so tasi1 will be executed only after task2 is finished.
Note: Asynchronous execution + serial queue opens up only one sub-thread
Execute a serial queue asynchronously
dispatch_queue_t queueSerial = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL); void(^task)(void) = ^{ NSLog(@"task--%@",[NSThread currentThread]); }; void(^task1)(void) = ^{ NSLog(@"task1--%@",[NSThread currentThread]); }; dispatch_async(queueSerial, task); Dispatch_async (queueSerial, task1); dispatch_async(queueSerial, task1); NSLog(@"end");Copy the code
End ->task->task1 = end->task->task1 = end->task->task1 = end->task->task1 = end->task->task1 = end->task So task and task1 execute in the same thread
Execute concurrent queues asynchronously
At this point, it is theoretically possible to create multiple child threads
dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);
void(^task)(void) = ^{
NSLog(@"task--%@",[NSThread currentThread]);
};
void(^task1)(void) = ^{
NSLog(@"task1--%@",[NSThread currentThread]);
};
dispatch_async(queueConcurrent, task);
dispatch_async(queueConcurrent, task1);
NSLog(@"end");
Copy the code
Task1 and task1 May not be executed in the same thread, so it is not possible to determine which of the two tasks will be finished first, because end is printed on the main thread, so end is printed first
Execute the main queue asynchronously
dispatch_queue_t queueMain = dispatch_get_main_queue();
void(^task)(void) = ^{
NSLog(@"task--%@",[NSThread currentThread]);
};
void(^task1)(void) = ^{
NSLog(@"task1--%@",[NSThread currentThread]);
};
dispatch_async(queueMain, task);
dispatch_async(queueMain, task1);
Copy the code
The main queue is also a serial queue, so tasks ->task1 are executed in order. In addition, tasks in the queue can only be executed on the main thread, so both task and task1 are executed on the main thread
Execute global queues asynchronously
dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);
void(^task)(void) = ^{
NSLog(@"task--%@",[NSThread currentThread]);
};
void(^task1)(void) = ^{
NSLog(@"task1--%@",[NSThread currentThread]);
};
dispatch_async(queueGlobal, task);
dispatch_async(queueGlobal, task1);
Copy the code
The global queue is a concurrent queue, so it has the ability to create multiple child threads, or to execute in a single child thread
Interthread communication
We need to put some time-consuming tasks on the child thread and then go back to the main thread to refresh the page
dispatch_queue_t queueMain = dispatch_get_main_queue(); dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0); dispatch_async(queueGlobal, ^{ sleep(3); // Dispatch_async (queueMain, ^{NSLog(@" main thread ")); }); });Copy the code
GCD fence method: dispatch_barrier_async
In Self-cultivation for Programmers: Linking, Loading, and Libraries. There’s a passage in the over-optimization section of the book
“The out-of-order execution capability of the CPU makes our efforts to secure multiple threads extremely difficult. Therefore, to ensure thread safety, it is necessary to prevent CPU swapping. Unfortunately, there is no portable way to prevent order swapping. This is usually done by calling an instruction provided by the CPU, often referred to as a barrier. A barrier prevents the CPU from swapping instructions that preceded it with a barrier, and vice versa. In other words, the barrier command acts like a dam, preventing changes from “penetrating” the dam.
Excerpt from: Yu Jia-zi, Shi Fan, pan Ai-min. “Self-cultivation of Programmers: Linking, Loading, and Libraries.” Apple Books.
To ensure atomicity of certain operations, CUP provides barrier instructions to ensure that subsequent instructions are not executed until the previous instructions have completed their execution. Dispatch_barrier_async basically means the same thing. In the concurrent queue of asynchronous execution, the tasks before dispatch_barrier_Async are executed first, then the tasks in dispatch_barrier_Async are executed, and then the tasks after dispatch_barrier_Async are executed
dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);
dispatch_async(queueGlobal, ^{
NSLog(@"1--%@",[NSThread currentThread]);
});
dispatch_async(queueGlobal, ^{
sleep(2);
NSLog(@"2--%@",[NSThread currentThread]);
});
dispatch_barrier_async(queueGlobal, ^{
NSLog(@"barrier--%@",[NSThread currentThread]);
});
dispatch_async(queueGlobal, ^{
NSLog(@"3--%@",[NSThread currentThread]);
});
dispatch_async(queueGlobal, ^{
NSLog(@"4--%@",[NSThread currentThread]);
});
Copy the code
The global queue is a concurrent queue, so I guess I should print out a 1 and a 2, then execute a barrier, and then randomly print out a 3 and a 4
Barrier seems to have no effect just like dispatch_async. After checking some information, I finally found the correct solution of dispatch_barrier_async in the official document
If you create a concurrent queue with dispatch_queue_CREATE, that’s fine, but if it’s a serial queue or a global concurrent queue then it’s the same thing as dispatch_async, and you create your own concurrent queue
dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queueConcurrent, ^{
NSLog(@"1--%@",[NSThread currentThread]);
});
dispatch_async(queueConcurrent, ^{
sleep(2);
NSLog(@"2--%@",[NSThread currentThread]);
});
dispatch_barrier_async(queueConcurrent, ^{
NSLog(@"barrier--%@",[NSThread currentThread]);
});
dispatch_async(queueConcurrent, ^{
NSLog(@"3--%@",[NSThread currentThread]);
});
dispatch_async(queueConcurrent, ^{
NSLog(@"4--%@",[NSThread currentThread]);
});
NSLog(@"end");
Copy the code
The result shows that dispatch_barrier_Async does not block the current thread, but only blocks the execution of other tasks in the current asynchronous queue. Official documents also confirm this
dispatch_barrier_sync
Let’s go to the official document for dispatch_barrier_sync first
On the one hand dispatch_barrier_sync blocks the current thread, on the other hand, it may cause deadlock. Let’s verify
dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queueConcurrent, ^{
sleep(2);
NSLog(@"1--%@",[NSThread currentThread]);
});
dispatch_async(queueConcurrent, ^{
NSLog(@"2--%@",[NSThread currentThread]);
});
dispatch_barrier_sync(queueConcurrent, ^{
NSLog(@"barrier--%@",[NSThread currentThread]);
});
dispatch_async(queueConcurrent, ^{
NSLog(@"3--%@",[NSThread currentThread]);
});
dispatch_async(queueConcurrent, ^{
NSLog(@"4--%@",[NSThread currentThread]);
});
NSLog(@"end");
Copy the code
We see that dispatch_barrier_sync does block the current thread, and end is output after dispatch_barrier_sync
dispatch_queue_t queueConcurrent = dispatch_queue_create("com.conc", DISPATCH_QUEUE_CONCURRENT);
dispatch_barrier_sync(queueConcurrent, ^{
NSLog(@"barrier--%@",[NSThread currentThread]);
dispatch_sync(queueConcurrent, ^{
NSLog(@"%@",[NSThread currentThread]);
});
});
Copy the code
Calling this function and targeting the current queue results in deadlock as mentioned in the document. Anyway, a deadlock was achieved
The documentation also mentions Block_copy
I can see from the source that dispatch_barrier_async does call _dispatch_Block_copy
However, dispatch_barrier_sync has no copy. This may be because dispatch_barrier_sync does not block the thread behind the code to execute, and dispatch_barrier_async does not block the thread, then the function body may be modified ???? So make a copy and leave a hello here
GCD Delay execution method: dispatch_after
After 2 seconds, the task is added to the main queue. When is it executed depends on the SCHEDULING of the CUP
dispatch_queue_t mainQueue = dispatch_get_main_queue(); (dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)), mainQueue, ^{// add task to mainQueue, It depends on the NSLog(@" executed ") of the CUP. });Copy the code
GCD One-time code (only executed once) : dispatch_once
To implement the singleton
static dispatch_once_t onceToken; Dispatch_once (&oncetoken, ^{// singleton});Copy the code
For more information, see the # iOS Multithreading dispatch_once profile
GCD fast iteration method: dispatch_apply
So let’s look at the document dispatch_apply
You can see that dispatch_apply and reentrant and secure concurrent queues are efficient traversal operations that would not be effective if they were a serial queue
dispatch_apply(10, queueGlobal, ^(size_t index) {
NSLog(@"%zd--%@",index,[NSThread currentThread]);
});
NSLog(@"end");
Copy the code
Dispatch_apply will use multiple threads to iterate, not just child threads, but also the main thread, and it will block the current thread
Dispatch Group
Dispatch_group monitors a set of tasks as a unit. You can put a group of tasks into a group and synchronize their behavior with dispatch_group. You can group these tasks and then execute them asynchronously on the same or different queues. You can listen for the completion of asynchronous tasks without blocking the current thread, or you can block the current thread and wait until the tasks complete.
dispatch_group_notify
dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queueGlobal, ^{
sleep(2);
NSLog(@"1--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queueGlobal, ^{
NSLog(@"2--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queueGlobal, ^{
sleep(1);
NSLog(@"3--%@",[NSThread currentThread]);
});
dispatch_group_notify(group, queueGlobal, ^{
NSLog(@"notify--%@",[NSThread currentThread]);
});
NSLog(@"end--%@",[NSThread currentThread]);
Copy the code
The “dispatch_group_notify” task does not block the current thread. It is executed after all other queues in the current group have completed. The “dispatch_group_notify” task can be executed in the main queue, which implements the callback function of the main thread
dispatch_queue_t queueSerial = dispatch_queue_create("com.serial", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queueMain = dispatch_get_main_queue();
dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queueGlobal, ^{
sleep(2);
NSLog(@"1--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queueSerial, ^{
sleep(1);
NSLog(@"2--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queueSerial, ^{
NSLog(@"3--%@",[NSThread currentThread]);
});
dispatch_group_notify(group, queueMain, ^{
NSLog(@"notify--%@",[NSThread currentThread]);
});
NSLog(@"end--%@",[NSThread currentThread]);
Copy the code
dispatch_group_wait
Block the current thread until all tasks in the group have completed
dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queueGlobal, ^{
sleep(2);
NSLog(@"1--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queueGlobal, ^{
sleep(1);
NSLog(@"2--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queueGlobal, ^{
NSLog(@"3--%@",[NSThread currentThread]);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"end--%@",[NSThread currentThread]);
Copy the code
End will not be printed until the first three tasks have completed, but you can also set the blocking timeout to a smaller amount, so that the execution will continue even if the previous tasks have not completed
dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);
dispatch_group_async(group, queueGlobal, ^{
sleep(3);
NSLog(@"1--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queueGlobal, ^{
sleep(1);
NSLog(@"2--%@",[NSThread currentThread]);
});
dispatch_group_async(group, queueGlobal, ^{
NSLog(@"3--%@",[NSThread currentThread]);
});
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2*NSEC_PER_SEC)));
NSLog(@"end--%@",[NSThread currentThread]);
Copy the code
Dispatch_group_enter, dispatch_group_leave
Dispatch_group_async is also implemented internally with dispatch_group_Enter and dispatch_group_leave
dispatch_queue_t queueGlobal = dispatch_get_global_queue(0, 0);
dispatch_group_enter(group);
dispatch_async(queueGlobal, ^{
sleep(3);
NSLog(@"1--%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queueGlobal, ^{
sleep(1);
NSLog(@"2--%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queueGlobal, ^{
NSLog(@"3--%@",[NSThread currentThread]);
dispatch_group_leave(group);
});
dispatch_group_notify(group, queueGlobal, ^{
NSLog(@"notify--%@",[NSThread currentThread]);
});
NSLog(@"end--%@",[NSThread currentThread]);
Copy the code
Implements anddispatch_group_async
Same effect
The signal semaphore
In development where thread synchronization is often required, a semaphore is a good choice. Dispatch_semaphore_signal semaphore +1, dispatch_semaphore_WAIT semaphore -1. If the semaphore is less than zero, the current thread is blocked
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_async(queueGlobal, ^{ sleep(3); NSLog(@"1--%@",[NSThread currentThread]); dispatch_semaphore_signal(semaphore); // Semaphore +1}); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // semaphores -1 dispatch_async(queueMain, ^{NSLog(@" main thread ")); }); NSLog(@"end--%@",[NSThread currentThread]);Copy the code
Multiple accesses to the same data can make the data insecure, for example
__block int i = 5;
while (i>0) {
dispatch_async(queueGlobal, ^{
i--;
NSLog(@"%d--%@",i,[NSThread currentThread]);
});
}
Copy the code
Due to the simultaneous modification of I by multiple threads, the result is very different from the expected one, and even the printing of NSLog function is out of order 😂, which can be solved through semaphore
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int i = 5;
while (i>0) {
dispatch_async(queueGlobal, ^{
i--;
NSLog(@"%d--%@",i,[NSThread currentThread]);
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
Copy the code
At this point we see the output as expected, because the semaphore control variable I can only be accessed by at most one thread (not necessarily the same thread, but at most one thread at a time).
Refer to the article
IOS Multithreading: “GCD” detailed summary
Dispatch
Source libdispatch
IOS Multithreaded dispatch_once profiling