GCD 多线程
同步任务、异步任务
两者的区别在于:
- 同步任务需要等待任务执行完之后才返回
- 异步任务无需等待任务执行完,直接返回
苹果的官方文档也有描述
同步任务
Submits a block object for execution and returns after that block finishes executing.
异步任务
Submits a block for asynchronous execution on a dispatch queue and returns immediately.
串行队列、并发队列
两者的区别在于:
- 串行队列中的任务顺序执行,上一个任务执行完后,下一个任务才接着执行,遵循FIFO(先进先出)的原则
- 并发队列中的任务可以并发执行。
两种任务 + 两种队列组合使用
| 任务/队列 | 串行队列 | 并发队列 | 主队列(串行队列) |
|---|---|---|---|
| 同步 | 不会开启子线程,任务串行执行(当前队列中嵌套添加一个同步任务会造成死锁) | 不会开启子线程,任务串行执行 | 主线程调会造成死锁,其他线程调用不会 |
| 异步 | 会开启子线程,任务串行执行 | 会开启子线程,任务并发执行 | 不会开启子线程,任务在主线程串行执行。 |
同步 + 串行
- (void)syncSerrialTask {
// 同步任务 + 串行队列
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]);
}
运行结果:
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[46700:394383] end execute task 1 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
2022-01-11 14:07:56.139288+0800 Demo[46700:394383] start execute task 2 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
2022-01-11 14:07:58.140514+0800 Demo[46700:394383] end execute task 2 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
2022-01-11 14:07:58.140797+0800 Demo[46700:394383] start execute task 3 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
2022-01-11 14:08:00.141964+0800 Demo[46700:394383] end execute task 3 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
2022-01-11 14:08:00.142212+0800 Demo[46700:394383] start execute task 4 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
2022-01-11 14:08:02.143410+0800 Demo[46700:394383] end execute task 4 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
2022-01-11 14:08:02.143661+0800 Demo[46700:394383] start execute task 5 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
2022-01-11 14:08:04.144890+0800 Demo[46700:394383] end execute task 5 on thread <NSThread: 0x60000192c600>{number = 1, name = main}
从结果可以看出,任务串行执行,没有开启新的线程。
同步 + 并发
- (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];
}
运行结果:
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}
任务串行执行,没有开启新的线程。
异步 + 串行
- (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];
}
运行结果:
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)}
1,5 号任务在主线程串行执行,2,3,4 号任务在子线程执行,且是串行执行。从结果可以看出创建了新的线程。
异步 + 并发
- (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];
}
运行结果:
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)}
1,5 号任务在主线程串行执行,2,3,4 号任务在子线程执行,且是并发执行。从结果可以看出创建了新的线程。
当前串行队列嵌套加入同步任务导致死锁
- (void)syncSerrialTask {
// 同步任务 + 串行队列
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];
}
运行结果:
造成死锁,运行时崩溃。
造成死锁的原因分析
在
dispatch_sync(queue, ^{
[self runTaskWithNum:3];
dispatch_sync(queue, ^{
[self runTaskWithNum:4];
});
});
代码中,任务 3 和 4 被当做一个整体同步加入到了串行队列中,之后又将任务 4 又被同步加入到串行队列,最后导致的就是: 任务 4 如果想要被执行,则之前加入到队列中的任务 3、4 作为一个整体的任务必须先执行完,但任务 3、 4 作为一个整体本身又包含一个任务 4,最后导致的结果就是互相等待,造成死锁。
主线程中调用同步+主队列也会触发死锁
原因是因为主线程的任务放在主队列中,主队列本身也是个串行队列,所以跟上文中的例子一样,向当前队列中嵌套加入了同步任务,造成了死锁。
线程同步
假设现在有 N 个苹果,两只猴子,一只猴子每次吃 2 个苹果,一只猴子每次吃 3 个苹果,两只猴子同时吃,知道剩余的苹果不够任意一只猴子吃时结束,如何用多线程来模拟这个过程。这是笔者在面试阿里时遇到的一道笔试题。
不考虑线程安全问题
@interface MonkeyEatApple ()
@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(@"%@ 猴子吃了 %ld 个苹果, 还剩 %ld 个苹果", monkeyStr, appleCount, self.appleCount);
} else {
NSLog(@"%@ 想吃,但没得吃了", monkeyStr);
break;
}
}
}
@end
输出结果
2022-01-12 23:04:04.913983+0800 Demo[93179:739398] B 猴子吃了 2 个苹果, 还剩 13 个苹果
2022-01-12 23:04:04.913984+0800 Demo[93179:739397] A 猴子吃了 3 个苹果, 还剩 12 个苹果
2022-01-12 23:04:05.418122+0800 Demo[93179:739398] B 猴子吃了 2 个苹果, 还剩 10 个苹果
2022-01-12 23:04:05.419167+0800 Demo[93179:739397] A 猴子吃了 3 个苹果, 还剩 7 个苹果
2022-01-12 23:04:05.923410+0800 Demo[93179:739397] A 猴子吃了 3 个苹果, 还剩 4 个苹果
2022-01-12 23:04:05.923410+0800 Demo[93179:739398] B 猴子吃了 2 个苹果, 还剩 5 个苹果
2022-01-12 23:04:06.428653+0800 Demo[93179:739398] B 猴子吃了 2 个苹果, 还剩 3 个苹果
2022-01-12 23:04:06.428653+0800 Demo[93179:739397] A 猴子吃了 3 个苹果, 还剩 2 个苹果
2022-01-12 23:04:06.933991+0800 Demo[93179:739398] B 猴子吃了 2 个苹果, 还剩 0 个苹果
2022-01-12 23:04:06.933991+0800 Demo[93179:739397] A 猴子吃了 3 个苹果, 还剩 -1 个苹果
2022-01-12 23:04:06.934244+0800 Demo[93179:739398] A 想吃,但没得吃了
2022-01-12 23:04:06.934282+0800 Demo[93179:739397] B 想吃,但没得吃了
从输出可以看出来,在不考虑线程同步的情况下,数据会出现错乱,一共 15 个苹果,B 猴子吃了 2 个之后还剩余 13 个, A 猴子又吃了 3 个,本应该剩余 10 个,但输出显示还剩余 12 个。
这里笔者把苹果总数设置成 6,观察输出结果
2022-01-13 00:03:07.904433+0800 Demo[95516:779892] A 猴子吃了 3 个苹果, 还剩 3 个苹果
2022-01-13 00:03:07.904433+0800 Demo[95516:779890] B 猴子吃了 2 个苹果, 还剩 4 个苹果
2022-01-13 00:03:08.409648+0800 Demo[95516:779890] B 猴子吃了 2 个苹果, 还剩 1 个苹果
2022-01-13 00:03:08.409650+0800 Demo[95516:779892] A 猴子吃了 3 个苹果, 还剩 0 个苹果
2022-01-13 00:03:08.409862+0800 Demo[95516:779892] A 想吃,但没得吃了
2022-01-13 00:03:08.409860+0800 Demo[95516:779890] B 想吃,但没得吃了
上图即是对输出结果的一种详细说明,显然在线程不做同步的情况下,数据发生错乱。
使用信号量做线程同步
- (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(@"%@ 猴子吃了 %ld 个苹果, 还剩 %ld 个苹果", monkeyStr, appleCount, self.appleCount);
} else {
dispatch_semaphore_signal(self.semaphore);
NSLog(@"%@ 想吃,但没得吃了", monkeyStr);
break;
}
dispatch_semaphore_signal(self.semaphore);
}
}
输出结果
2022-01-13 00:39:28.969801+0800 Demo[96939:804913] B 猴子吃了 2 个苹果, 还剩 4 个苹果
2022-01-13 00:39:29.475415+0800 Demo[96939:804914] A 猴子吃了 3 个苹果, 还剩 1 个苹果
2022-01-13 00:39:29.475550+0800 Demo[96939:804913] B 想吃,但没得吃了
2022-01-13 00:39:29.475571+0800 Demo[96939:804914] A 想吃,但没得吃了
文章首发于个人博客 点击跳转