The GCD multithreading
Synchronous and asynchronous tasks
The difference between the two is:
- A synchronization task returns only after the task is complete
- An asynchronous task returns directly without waiting for the completion of the task
It’s also described in Apple’s official documentation
Synchronization task
Submits a block object for execution and returns after that block finishes executing.
Asynchronous tasks
Submits a block for asynchronous execution on a dispatch queue and returns immediately.
Serial queue, concurrent queue
The difference between the two is:
- Tasks in a serial queue are executed sequentially, followed by the next task after the previous task is executed, following the FIFO (first in, first out) principle
- Tasks in a concurrent queue can be executed concurrently.
Two tasks and two queues are used in combination
Task/queue | Serial queues | Concurrent queue | Main queue (serial queue) |
---|---|---|---|
synchronous | Child threads will not be started and tasks will be executed sequentially (adding a synchronization task nested in the current queue will cause deadlock) | Child threads are not started and tasks are executed sequentially | Main thread calls cause deadlocks, other thread calls do not |
asynchronous | Child threads are opened and tasks are executed sequentially | Child threads are started and tasks are executed concurrently | Child threads are not started and tasks are executed serially on the main thread. |
Synchronous + serial
- (void)syncSerrialTask {
// Synchronization task + serial queue
dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_SERIAL);
[self runTaskWithNum:1];
dispatch_sync(queue, ^{
[self runTaskWithNum:2];
});
dispatch_sync(queue, ^{
[self runTaskWithNum:3];
});
dispatch_sync(queue, ^{
[self runTaskWithNum:4];
});
[self runTaskWithNum:5];
}
- (void)runTaskWithNum:(NSUInteger)num {
NSLog(@"start execute task %lu on thread %@", num, [NSThread currentThread]);
sleep(2);
NSLog(@"end execute task %lu on thread %@", num, [NSThread currentThread]);
}
Copy the code
Running results:
2022-01-11 14:07:54.137625+0800 Demo[46700:394383] Start execute task 1 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:07:56.139009+0800 Demo[467000:394383] End execute task 1 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:07:56.139288+0800 Demo[467000:394383] Start execute task 2 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:07:58.140514+0800 Demo[467000:394383] end execute task 2 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:07:58.140797+0800 Demo[467000:394383] Start execute task 3 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:00.141964+0800 Demo[467000:394383] End execute task 3 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:00.142212+0800 Demo[467000:394383] Start execute task 4 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:02.143410+0800 Demo[467000:394383] End execute task 4 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:02.143661+0800 Demo[467000:394383] Start execute task 5 on thread <NSThread: 0x60000192c600>{number = 1, Name = main} 2022-01-11 14:08:04.144890+0800 Demo[467000:394383] End execute task 5 on thread <NSThread: 0x60000192c600>{number = 1, name = main}Copy the code
As you can see from the result, the task is executed sequentially without starting a new thread.
Synchronization + concurrency
- (void)syncConcurrentTask {
dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_CONCURRENT);
[self runTaskWithNum:1];
dispatch_sync(queue, ^{
[self runTaskWithNum:2];
});
dispatch_sync(queue, ^{
[self runTaskWithNum:3];
});
dispatch_sync(queue, ^{
[self runTaskWithNum:4];
});
[self runTaskWithNum:5];
}
Copy the code
Running results:
2022-01-11 14:11:42.240424+0800 Demo[46833:397762] start execute task 1 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:44.241164+0800 Demo[46833:397762] end execute task 1 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:44.241326+0800 Demo[46833:397762] start execute task 2 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:46.242443+0800 Demo[46833:397762] end execute task 2 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:46.242588+0800 Demo[46833:397762] start execute task 3 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:48.243767+0800 Demo[46833:397762] end execute task 3 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:48.244037+0800 Demo[46833:397762] start execute task 4 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:50.245273+0800 Demo[46833:397762] end execute task 4 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:50.245550+0800 Demo[46833:397762] start execute task 5 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
2022-01-11 14:11:52.246746+0800 Demo[46833:397762] end execute task 5 on thread <NSThread: 0x600002af0700>{number = 1, name = main}
Copy the code
The task is executed serially without starting a new thread.
Asynchronous + serial
- (void)asyncSerrialTask {
dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_SERIAL);
[self runTaskWithNum:1];
dispatch_async(queue, ^{
[self runTaskWithNum:2];
});
dispatch_async(queue, ^{
[self runTaskWithNum:3];
});
dispatch_async(queue, ^{
[self runTaskWithNum:4];
});
[self runTaskWithNum:5];
}
Copy the code
Running results:
2022-01-11 14:15:01.846326+0800 Demo[46949:400738] start execute task 1 on thread <NSThread: 0x60000204c180>{number = 1, name = main}
2022-01-11 14:15:03.847623+0800 Demo[46949:400738] end execute task 1 on thread <NSThread: 0x60000204c180>{number = 1, name = main}
2022-01-11 14:15:03.847914+0800 Demo[46949:400738] start execute task 5 on thread <NSThread: 0x60000204c180>{number = 1, name = main}
2022-01-11 14:15:03.847950+0800 Demo[46949:400839] start execute task 2 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:05.849427+0800 Demo[46949:400738] end execute task 5 on thread <NSThread: 0x60000204c180>{number = 1, name = main}
2022-01-11 14:15:05.851383+0800 Demo[46949:400839] end execute task 2 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:05.851655+0800 Demo[46949:400839] start execute task 3 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:07.856812+0800 Demo[46949:400839] end execute task 3 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:07.856949+0800 Demo[46949:400839] start execute task 4 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
2022-01-11 14:15:09.862166+0800 Demo[46949:400839] end execute task 4 on thread <NSThread: 0x600002031f80>{number = 3, name = (null)}
Copy the code
Tasks 1,5 are executed serially on the main thread, and tasks 2, 3, and 4 are executed serially on child threads. You can see from the result that a new thread was created.
Asynchronous + concurrent
- (void)asyncConcurrentTask {
dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_CONCURRENT);
[self runTaskWithNum:1];
dispatch_async(queue, ^{
[self runTaskWithNum:2];
});
dispatch_async(queue, ^{
[self runTaskWithNum:3];
});
dispatch_async(queue, ^{
[self runTaskWithNum:4];
});
[self runTaskWithNum:5];
}
Copy the code
Running results:
2022-01-11 14:20:19.425785+0800 Demo[47168:405302] start execute task 1 on thread <NSThread: 0x6000020384c0>{number = 1, name = main}
2022-01-11 14:20:21.426931+0800 Demo[47168:405302] end execute task 1 on thread <NSThread: 0x6000020384c0>{number = 1, name = main}
2022-01-11 14:20:21.427064+0800 Demo[47168:405302] start execute task 5 on thread <NSThread: 0x6000020384c0>{number = 1, name = main}
2022-01-11 14:20:21.427119+0800 Demo[47168:405392] start execute task 2 on thread <NSThread: 0x60000205b440>{number = 5, name = (null)}
2022-01-11 14:20:21.427143+0800 Demo[47168:405394] start execute task 4 on thread <NSThread: 0x600002069540>{number = 3, name = (null)}
2022-01-11 14:20:21.427142+0800 Demo[47168:405395] start execute task 3 on thread <NSThread: 0x60000207e380>{number = 4, name = (null)}
2022-01-11 14:20:23.428279+0800 Demo[47168:405302] end execute task 5 on thread <NSThread: 0x6000020384c0>{number = 1, name = main}
2022-01-11 14:20:23.431917+0800 Demo[47168:405394] end execute task 4 on thread <NSThread: 0x600002069540>{number = 3, name = (null)}
2022-01-11 14:20:23.432210+0800 Demo[47168:405392] end execute task 2 on thread <NSThread: 0x60000205b440>{number = 5, name = (null)}
2022-01-11 14:20:23.432252+0800 Demo[47168:405395] end execute task 3 on thread <NSThread: 0x60000207e380>{number = 4, name = (null)}
Copy the code
Tasks 1,5 are executed serially on the main thread, and tasks 2, 3, and 4 are executed concurrently on child threads. You can see from the result that a new thread was created.
The current serial queue is locked because it is nested with synchronization tasks
- (void)syncSerrialTask {
// Synchronization task + serial queue
dispatch_queue_t queue = dispatch_queue_create("com.thatisawesome.demo", DISPATCH_QUEUE_SERIAL);
[self runTaskWithNum:1];
dispatch_sync(queue, ^{
[self runTaskWithNum:2];
});
dispatch_sync(queue, ^{
[self runTaskWithNum:3];
dispatch_sync(queue, ^{
[self runTaskWithNum:4];
});
});
[self runTaskWithNum:5];
}
Copy the code
Running results:
Cause a deadlock and crash at runtime.
Deadlock cause analysis
in
dispatch_sync(queue, ^{
[self runTaskWithNum:3];
dispatch_sync(queue, ^{
[self runTaskWithNum:4];
});
});
Copy the code
In the code, tasks 3 and 4 are synchronously added to the serial queue as a whole, and then task 4 is synchronously added to the serial queue, resulting in: If task 4 wants to be executed, tasks 3 and 4 added to the queue as a whole must be executed first. However, tasks 3 and 4 as a whole also contain one task 4. As a result, they wait for each other, resulting in deadlock.
A deadlock is also triggered by a call to synchronization + main queue in the main thread
The reason is that the tasks of the main thread are placed in the main queue, which is also a serial queue. As in the example in this article, synchronization tasks are nested into the current queue, resulting in deadlock.
Thread synchronization
Suppose we now have N apples, two monkeys, one monkey eats 2 apples at a time, one monkey eats 3 apples at a time, both monkeys eat at the same time, until there are not enough apples left for each monkey to eat, how to simulate this process with multiple threads. This is the author in the interview ali encountered a pen test.
Don’t worry about thread safety
@interface MonkeyEatApple (a)
@property (nonatomic, assign) NSInteger appleCount;
@property (nonatomic, strong) dispatch_semaphore_t semaphore;
@end
@implementation MonkeyEatApple
- (instancetype)initWithAppleCount:(NSInteger)appleCount {
if (self = [super init]) {
_appleCount = appleCount;
}
return self;
}
- (void)startEat {
dispatch_queue_t queue1 = dispatch_queue_create("club.thatisawesome.queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("club.thatisawesome.queue2", DISPATCH_QUEUE_SERIAL);
self.semaphore = dispatch_semaphore_create(1);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf eatAppleWithCount:2];
});
dispatch_async(queue2, ^{
[weakSelf eatAppleWithCount:3];
});
}
- (void)eatAppleWithCount:(NSInteger)appleCount {
while (YES) {
NSString *monkeyStr = @"A";
if (appleCount == 2) {
monkeyStr = @"B";
}
if (self.appleCount >= 2) {
[NSThread sleepForTimeInterval:0.5];
self.appleCount -= appleCount;
NSLog(@"%@ monkey ate %ld apples, there are %ld apples left", monkeyStr, appleCount, self.appleCount);
} else {
NSLog(@"%@ wants to eat, but there's no more to eat.", monkeyStr);
break;
}
}
}
@end
Copy the code
The output
[93179:739398] B Monkey eats 2 apples, [93179:739397] A Monkey eats three apples. [93179:739398] B Monkey eats two apples, 2022-01-12 23:04:05.419167+0800 Demo[93179:739397] A Monkey eats three apples, 2022-01-12 23:04:05.923410+0800 Demo[93179:739397] 2022-01-12 23:04:05.923410+0800 Demo[93179:739398] B Monkey eats two apples, [93179:739398] B Monkey eats two apples, 2022-01-12 23:04:06.428653+0800 Demo[93179:739397] A Monkey eats three apples, 2022-01-12 23:04:06.933991+0800 Demo[93179:739398] B Monkey eats two apples, 2022-01-12 23:04:06.933991+0800 Demo[93179:739397] A Monkey eats three apples, 2022-01-12 23:04:06.934244+0800 Demo[93179:739398] A want to eat, 2022-01-12 23:04:06.934282+0800 Demo[93179:739397] B: I want to eat, but I don't have any foodCopy the code
From the output, it can be seen that without considering thread synchronization, the data will be distorted. There are 15 apples in total. Monkey B ate 2 apples, but there are 13 apples left.
Here I set the total number of apples to 6 and observe the output
[95516:779892] A Monkey eats three apples, 2022-01-13 00:03:07.904433+0800 Demo[95516:779890] B Monkey eats two apples, 2022-01-13 00:03:08.409648+0800 Demo[95516:779890] B Monkey eats two apples, [95516:779892] A Monkey eats three apples. 2022-01-13 00:03:08.409862+0800 Demo[95516:779892] A want to eat, 2022-01-13 00:03:08.409860+0800 Demo[95516:779890] B: I want to eat, but I don't have any moreCopy the code
Above is a detailed illustration of the output. It is clear that the data is corrupted when the thread is not synchronized.
Use semaphores for thread synchronization
- (void)eatAppleWithCount:(NSInteger)appleCount {
while (YES) {
dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER);
NSString *monkeyStr = @"A";
if (appleCount == 2) {
monkeyStr = @"B";
}
if (self.appleCount >= 2) {
[NSThread sleepForTimeInterval:0.5];
self.appleCount -= appleCount;
NSLog(@"%@ monkey ate %ld apples, there are %ld apples left", monkeyStr, appleCount, self.appleCount);
} else {
dispatch_semaphore_signal(self.semaphore);
NSLog(@"%@ wants to eat, but there's no more to eat.", monkeyStr);
break;
}
dispatch_semaphore_signal(self.semaphore); }}Copy the code
The output
[96939:804913] B The monkey ate two apples, 2022-01-13 00:39:29.475415+0800 Demo[96939:804914] A Monkey eats three apples, 2022-01-13 00:39:29.475550+0800 Demo[96939:804913] B want to eat, 2022-01-13 00:39:29.475571+0800 Demo[96939:804914] A: I want to eat, but I don't have to eatCopy the code
First post in personal blog click jump