浅谈iOS多线程
浅谈iOS多线程
首先,先看看进程和线程的概念。
图1.1
这一块不难理解,重点点下他们的几个重要区别:
1,地址空间和资源:进程可以申请和拥有系统资源,线程不行。资源进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。
2,通信:进程间需要用到IPC(这个可以谁总结开个课),线程可以直接读写进程的数据段来通信(需要涉及锁,下面会简单讲到)。
3,调度和切换:线程快,进程慢。
好了,切回主题,iOS多线程技术,一般有三个,NSThread,NSOperation/NSOperationQueue,GCD(Grand Central Dispatch),好,首先先抛出一些概念。
NSThread
- 使用NSThread对象建立一个线程非常方便。
- 但是!要使用NSThread管理多个线程非常困难,不推荐使用。
- 技巧!使用[NSThread currentThread]跟踪任务所在线程,适用于这三种技术。
NSOperation/NSOperationQueue
- 是使用GCD实现的一套Objective-C的API。
- 是面向对象的线程技术。
- 提供了一些在GCD中不容易实现的特性,如:限制最大并发数量、操作之间的依赖关系。
- 苹果官方现在建议大家使用该多线程技术,但现在仍然很多人使用GCD技术,因为GCD使用较为方便。
GCD —— Grand Central Dispatch
- Grand Central Dispatch (GCD)是Apple开发的一个多核编程的解决方法,称作大中心调度。该方法在Mac OS X 10.6雪豹中首次推出,并随后被引入到了iOS4.0中。GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术。
- 是基于C语言的底层API。
- 用Block定义任务,使用起来非常灵活便捷。
- 提供了更多的控制能力以及操作队列中所不能使用的底层函数。
以上就是对于这三种技术的简单介绍,接着再来介绍下几个基本的概念,
队列:队列是先进先出(FIFO)结构的,在iOS中,队列主要的任务是负责线程的创建、回收工作,不论什么队列和什么任务都不需要程序员参与,减轻了程序员的工作。有GCD队列和自定义队列。根据这些,我们实际上可以操作四种队列,全局队列,主队列,串行队列,并行队列。
任务:分为同步任务和异步任务。同步任务:必须按照顺序执行的任务,异步任务:执行顺序不一定,哪个先抢占到资源哪个先执行(我试过将多个异步任务并发执行,系统会将这些任务分配给若干个线程,然后由线程调度执行,分配到同一线程的任务还是按顺序执行的)。
于是乎,将这些队列和任务组合,就会出现8种组合,赶紧投票吧,分享会上用代码来详细讲解下这8种组合的效果。
下面简单介绍下以上三个技术在代码中的使用:
NSThread 使用它经常是用来查看当前线程:[NSThread currentThread](用来判断是否在主线程执行也很方便),因为其他的确实不好用,也不推荐用。
NSOperation/NSOperationQueue 虽说是苹果基于GCD实现的面相对象的线程技术,但是感觉用起来没有GCD方便,下面简单介绍下它的几个用法
NSOperation 对应任务 有以下三种用法
1 NSInvocationOperation
2 NSBlockOperation
3 自定义NSOperation 子类
1 和 2 我给出初始化方法 大家就一眼能知道区别和用法了。
NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task) object:nil];// 调用start方法执行操作op操作
[op start];
NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"task0---%@", [NSThread currentThread]);
}];
[op start];
恩 其实就是调用的是block还是方法的区别了。注意 start后是指在当前线程同步执行任务。
自定义子类也说下
主要操作就是继承NSOperation,然后在.m文件中实现- (void)mian方法,然后运行start的时候就会运行main方法里的代码。
另外 任务提供执行完成的回调
op.completeBlock可以监听一个操作执行完毕的时刻,这个block里面可以添加一些我们需要执行的操作,这个block里面的操作仍然是在子线程执行,但不一定和被监听的操作在同一个线程
接着讲下NSOperationQueue
NSOperationQueue 对应队列 且是并行队列。
先解决个疑惑,如何使用串行队列?NSOperationQueue提供了一个设置最大并发数的方法setMaxConcurrentOperationCount: 只要将最大并发数设置为1,就是串行队列了。
首先 NSOperationQueue 可以获取到主队列 或者自己创建。
NSOperationQueue 提供 addOperation和addOperationWithBlock方法 名字取得很好,一眼就可以看出用法了,前者用来添加任务,后者用block的方式来添加任务。
同时提供cancal单个任务和cancelAllOperations所有任务的方式,不能cancel正在执行的任务。
同时提供获取挂起状态和设置挂起状态的方法。
isSuspended : 判断是否挂起
setSuspended: YES表示挂起,NO表示恢复
同样 不能对正在执行的任务设置。
任务间的执行先后,可以设置依赖来排序。
[op2 addDependency:op1];
Op1执行完成后才执行op2.
其实简单的操作用NSOperation比起GCD来要美观一下,个人感觉。
最后讲重点GCD 我觉得它之所以强大是因为它提供了不止前面这些功能还提供了一些常用的额外功能,下面我来一一讲解。
几个简单的和前面功能类似的
dispatch_get_global_queue
全局队列
并行队列
dispatch_get_main_queue
主队列
主线程中的唯一队列
dispatch_queue_create
自定义队列
DISPATCH_QUEUE_SERIAL
串
DISPATCH_QUEUE_CONCURRENT
并行
dispatch_sync 同步线程
dispatch_async 异步线程
这些用法和前面类似 到时8个组合用GCD简单演示下,相当于两个一起理解了。
之所以说GCD比NSOperation强大,就是他不仅能实现NSOperation能实现的,还有很多很好用的方法,下面一一讲解:
dispatch_once 一次性代码 使用dispatch_once保证代码只被执行一次 使用场景:单例对象的初始化 这个大家接触的很多了 不多讲了
dispatch_group_t 队列组 使用这个可以很好的控制任务调度
我们经常有需求 需要在一个页面请求多个接口 当多个接口都完成后执行刷新UI的操作 这个时候我们的操作一般都是多个异步返回的时候各自判断 其实用dispatch_group_t 可以很好的解决这个问题
思路如下
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 异步取数据A
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 异步取数据B
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程刷新UI
});
dispatch_group_notify是队列组中所有任务都完成后会发送的通知。
思考下:我们的业务场景经常能用到这个思路,大家有没例子的?
dispatch_after 这个其实也挺经常用到的 延迟执行方法 网上说只是延迟提交,并不是延迟立刻执行 会有些许的时间差。
dispatch_barrier_async 确保提交的闭包是指定队列中在特定时段唯一在执行的一个 这个可以当锁用。
//创建队列
self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
//改变setter
- (void)setCount:(NSUInteger)count forKey:(NSString *)key
{
key = [key copy];
//确保所有barrier都是async异步的
dispatch_barrier_async(self.isolationQueue, ^(){
if (count == 0) {
[self.counts removeObjectForKey:key];
} else {
self.counts[key] = @(count);
}
});
}
这是网上找的代码 实际上synchronized可以更好的完成这个锁操作,这是后话啦,有空再分析这个。实际上他的用法也比较多,到时搞个demo分享下。
dispatch_apply 可进行快速迭代
用来替换对前一次计算没有依赖的for循环会优化很多,例如
for (int i = 0; i < 999 ; i++) {
dispatch_async(concurrentQueue, ^{
NSLog(@"wrong %d",i);
//do something hard
});
}
改用dispatch_apply 来处理
dispatch_apply(999, concurrentQueue, ^(size_t i){
NSLog(@"correct %zu",i);
//do something hard
});
Dispatch IO 文件操作
用到的方式是多个线程去读取不同的数据块,然后再合并。
Dispatch source 这个也是程序优化的一道利器,且支持多种场景,对于程序优化很有帮助,这块可以大力研究下,用Dispatch source来取代一些回调函数可以有效的提高程序执行的效率,codereview下我们的代码,应该有能够用上这块的(到时奉上)。
Dispatch Semaphore这个类似于操作系统中的信号量,可以用来解决死锁的问题。这个其实是个很有趣的领域,
dispatch_semaphore_wait
等待信号量
需要
dispatch_semaphore_signal
执行后才能跳过。配合使用可以解决很多复杂的线程问题。
发挥下想象力:通过这个信号量,是不是用很好的方式解决我们请求等待同步的问题?
最后稍微讲下锁
建议使用
synchronized就可解决大部分的问题了 给出ElearningConfig中的代码示例
实际上就是这段被{}包起来的代码只会被一个线程调用,而锁就是self对象。意思就是,执行代码块的时候会对self上锁,当下一个线程需要对代码块调用时,需要等待上一个程序解锁self才可以。
好啦,更多精彩内容,我们分享会见!