iOS中的常见多线程方案
容易混淆的术语
有 4 个术语比较容易混淆:同步
、异步
、并发
、串行
同步
和异步
主要影响:能不能开启新的线程
同步
:在当前线程中执行任务,不具备开启新线程的能力异步
:在新的线程中执行任务,具备开启新线程的能力
并发
和串行
主要影响:任务的执行方式
并发
:多个任务并发(同时)执行串行
:一个任务执行完毕后,再执行下一个任务
GCD的常用函数
GCD
源码:https://github.com/apple/swift-corelibs-libdispatch
同步 vs 异步
同步和异步操作的主要区别在于 是否等待操作执行完成
,亦即是否阻塞当前线程。
同步操作会等待操作执行完成后再继续执行接下来的代码,而异步操作则恰好相反,它会在调用后立即返回,不会等待操作的执行结果。
用 同步
的方式执行任务:
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block)
用 异步
的方式执行任务:
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block)
并发 vs 串行
从本质上来说,串行和并发的主要区别在于 允许同时执行的任务数量
。
并发队列
(Concurrent Dispatch Queue
)
- 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
- 并发功能只有在
异步
(dispatch_async
)函数下才有效
串行队列
(Serial Dispatch Queue
)
- 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
GCD
创建队列:
// DISPATCH_QUEUE_SERIAL 串行队列
// DISPATCH_QUEUE_CONCURRENT 并行队列
dispatch_queue_t queue = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL);
各种队列的执行效果
死锁
使用 sync
函数往 当前串行队列
中添加任务,会卡住当前的串行队列(产生死锁)。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t serial = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL);
dispatch_async(serial, ^{
// 死锁
dispatch_sync(serial, ^{
NSLog(@"任务 - %@", [NSThread currentThread]);
});
});
// 死锁
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"任务 - %@", [NSThread currentThread]);
});
}
sync
的特点:立马
在当前线程执行任务,执行完毕才能继续往下执行。
串行队列
的特点:排队执行,先进先出(FIFO
)。
这样就会导致当前没有没有执行完,又希望执行新的任务,就会卡住。
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
:全局队列是并发队列
,有高、默认、低和后台 4 个优先级。相同优先级的队列获取多次,获得的是同一个队列。
dispatch_get_main_queue()
:主队列是串行队列
,主队列中的任务都是在主线程中依次执行的。
子线程中的定时器
带 afterDelay
的函数底层使用了定时器
,定时器是需要添加到 RunLoop
里面去的。子线程默认没有启动 RunLoop
,所以定时器没法工作。
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
// 打印顺序: 1 3
}
- (void)test
{
NSLog(@"2");
}
RunLoop
执行定时器是在 被某个消息唤醒时
执行的,并不是立马执行。
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
// 打印顺序: 1 3 2
}
- (void)test
{
NSLog(@"2");
}
让子线程中的定时器生效:
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
});
// 打印顺序: 1 3 2
}
- (void)test
{
NSLog(@"2");
}
GNUstep
苹果没有开源 Foundation
框架源码,但在 GNU
计划的 GNUstep
项目中,将 Cocoa
的 OC
库重新开源实现了一遍。虽然 GNUstep
不是苹果官方源码,但还是具有一定的参考价值。
下载地址:http://wwwmain.gnustep.org/resources/downloads.php
可以在 NSRunLoop.m
中找到下面代码,可以证实我们上面的结论:
- (void) performSelector: (SEL)aSelector
withObject: (id)argument
afterDelay: (NSTimeInterval)seconds
{
NSRunLoop *loop = [NSRunLoop currentRunLoop];
GSTimedPerformer *item;
item = [[GSTimedPerformer alloc] initWithSelector: aSelector
target: self
argument: argument
delay: seconds];
[[loop _timedPerformers] addObject: item];
RELEASE(item);
[loop addTimer: item->timer forMode: NSDefaultRunLoopMode];
}
子线程自动退出
和上面的问题类似,子线程默认没有启动 RunLoop
,所以线程任务执行完毕后自动退出。比如下面代码,打印 1
后程序崩溃:
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
}];
[thread start];
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
- (void)test
{
NSLog(@"2");
}
在子线程中启动 RunLoop
,即可正常打印 1 2
:
- (void)viewDidLoad {
[super viewDidLoad];
NSThread *thread = [[NSThread alloc] initWithBlock:^{
NSLog(@"1");
// 子线程休眠,等待消息
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}];
[thread start];
// 发送消息,唤醒子线程,执行任务
[self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}
- (void)test
{
NSLog(@"2");
}
队列组
队列组可以实现让任务分组执行,比如下面代码实现了 子线程 并发执行
任务1
和任务2
,然后再回到主线程执行任务3
:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"任务1");
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"任务2");
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"任务3");
});
下面代码实现了 子线程 并发执行
任务1
和任务2
,然后再执行任务3
和任务4
:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"任务1");
}
});
dispatch_group_async(group, queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"任务2");
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"任务3");
}
});
dispatch_group_notify(group, queue, ^{
for (int i = 0; i < 10; i++) {
NSLog(@"任务4");
}
});