iOS的多线程主要有三种方式,NSThread、GCD(Grand Central Dispatch)NSOperationQueue
开始,在iOS2发布的时候,苹果同步推出了NSthread和NSOperation。其中NSthread比较简单,仅提供了创建队列、开始、取消、关闭等简单动作。而NSOperation就更高级一些,他是基于队列的任务管理工具,有任务关系、优先级、并发限制、任务取消等功能,相对于NSthread他封装的更好一些,也重一些。这2种方式基本满足了当时开发者的需求,简单开异步任务就用NSThread,任务之间存在关系就用NSOperation + NSOperationQueue来实现。
2年后,出于降低能耗及更好的适配苹果的硬件等目的,随着iOS4的发布,苹果推出了GCD。GCD的能力基本和NSOperation类似,主要差异是他更轻量、灵活。GCD 提供了一个底层的并发框架,其中包含了线程池和队列调度等机制,用于更有效地管理并发任务。推出GCD的同时,苹果也顺带把NSOperationQueue的内部实现做了优化,应用了GCD的一些(或大部分)研发成果。所以可以根据个人习惯选择多线程工具。
NSThread
- (void)createAndStartThread {
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runTask) object:nil];
[thread start];
}
- (void)runTask {
NSLog(@"线程开始: %@", [NSThread currentThread]);
// 模拟耗时任务
[NSThread sleepForTimeInterval:1.0];
// 退出
[NSThread exit];
//
NSLog(@"这行不会执行");
}
NSOperation
最基本就不写了,感觉和调用方法没啥区别
依赖关系
- (void)operationWithDependencies {
NSLog(@"create operationWithDependencies on %@", NSThread.currentThread);
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务 1 执行 %@", NSThread.currentThread);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务 2 执行 %@", NSThread.currentThread);
}];
// operation1 等待 operation2 完成
[operation1 addDependency:operation2];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation1, operation2] waitUntilFinished:NO];
NSLog(@"operationWithDependencies cimmit");
}
打印结果
create operationWithDependencies on <_NSMainThread: 0x60000170c000>{number = 1, name = main}
operationWithDependencies cimmit
任务 2 执行 <NSThread: 0x600001752500>{number = 4, name = (null)}
任务 1 执行 <NSThread: 0x600001763200>{number = 6, name = (null)}
如果我们设置了 waitUntilFinished YES,则相同于同步队列,程序会最后打印“operationWithDependencies cimmit”
其他功能
- 设置最大并发数
- 取消所有队列
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
// 设置最多并发 2 个任务
queue.maxConcurrentOperationCount = 2;
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务1执行 %@", NSThread.currentThread);
}];
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"任务2执行 %@", NSThread.currentThread);
}];
[queue addOperations:@[operation1, operation2] waitUntilFinished:YES];
// 取消所有队列
[queue cancelAllOperations];
GCD(Grand Central Dispatch)
并发队列
异步任务在非主线程中执行
dispatch_queue_t queue = dispatch_queue_create("com.example.myQueue", DISPATCH_QUEUE_CONCURRENT);
// 同步任务(阻塞当前线程)
dispatch_sync(queue, ^{
NSLog(@"同步任务 - %@", [NSThread currentThread]);
});
// 异步任务(不阻塞当前线程)
dispatch_async(queue, ^{
NSLog(@"异步任务 - %@", [NSThread currentThread]);
});
全局并发
各自线程不一致,无序
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"全局并发队列任务1 - %@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"全局并发队列任务2 - %@", [NSThread currentThread]);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"全局并发队列任务3 - %@", [NSThread currentThread]);
});
串行队列
以下代码推荐小白朋友反复执行观察规律。串行队列有个特点,加入队列的任务会严格按着加入顺序执行,无论他是同步还是异步。当同步任务被添加到队列后,程序会等待同步任务前加入的所有任务执行完毕,然后执行同步任务,然后继续往后执行。其中同步任务在主线程执行,异步任务在同一个子线程执行。
dispatch_queue_t queue = dispatch_queue_create("com.example.barrier", nil);
dispatch_async(queue, ^{
NSLog(@"异步任务1 %@", NSThread.currentThread);
});
dispatch_async(queue, ^{
NSLog(@"异步任务2 %@", NSThread.currentThread);
});
dispatch_sync(queue, ^{
NSLog(@"同步任务 %@", NSThread.currentThread);
});
dispatch_async(queue, ^{
NSLog(@"异步任务3 %@", NSThread.currentThread);
});
NSLog(@"主线程任务");
以上代码,4个任务的顺序是绝对不变的,主线程任务一定会在同步任务后执行,可能在3前,可能在3后,这事不确定的。
任务组 dispatch_group
dispatch_group_t group = dispatch_group_create();
// 自动入离队1
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务1");
});
// 自动入离队2
dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
NSLog(@"任务2");
});
// 手动入队
dispatch_group_enter(group);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延时任务完成");
// 手动离队
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"所有任务完成,回到主线程");
});
栅栏任务
dispatch_queue_t queue = dispatch_queue_create("com.example.barrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"任务1");
});
dispatch_async(queue, ^{
NSLog(@"任务2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"栅栏任务");
});
dispatch_async(queue, ^{
NSLog(@"任务3");
});
信号量
可以用于控制并发数,实现锁的效果等
- (void)concurrentTaskControl {
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2); // 最大并发数为2
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
for (int i = 0; i < 5; i++) {
dispatch_async(queue, ^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 请求信号量
NSLog(@"任务 %d 开始 - %@", i, [NSThread currentThread]);
sleep(2); // 模拟耗时任务
NSLog(@"任务 %d 完成", i);
dispatch_semaphore_signal(semaphore); // 释放信号量
});
}
}
其他场景
1、延迟执行dispatch_after,我经常用它解决一些定期权限检查之类的,就可以不用定时器
2、单例dispatch_once_t
3、并行遍历dispatch_apply
一些细节对比
1)任务组:DispatchGroup和NSOperationQueue类似,区别在于DispatchGroup提供的函数notify(queue:execute:)、enter()、leave()等,使得任务和队列之间的关系更灵活,不必强依赖于任务的状态。而OperationQueue中任务的整体管理依赖任务的状态,OperationQueue取消之后,如果任务还未开始执行,将不再执行;如果任务正在执行中,可继续执行,特殊情况需要终断的,可通过Operation的isCancelled属性来判断是否被取消了。
2)GCD的通过信号量来控制某个队列并发数,而OperationQueue用的是maxConcurrentOperationCount属性来控制,用起来差异不大,OperationQueue的代码更少、更直观。
3)GCD没有任务依赖关系的直接设置,但是可以通过栅栏(dispatchBarriers)来实现先后任务等待,NSOperation可以直接设置谁依赖谁,更直接。
4)GCD和NSOperation都能设置任务优先级,但是不要以为高优先级的任务就一定比低优先级的任务先执行,具体的执行时间还受到其他因素的影响,比如操作的依赖关系、操作的并发性设置等。