经常听到这样的问题 "你在处理多线程的时候 遇到过什么问题 或者说 你使用过多线程吗 如何操作的"
具体 我也没听过 别人是怎么回答的,我也没太想好怎样回答才算全面,今天利用工作空余时间好好系统学一下,从以下几个角度学习
1 理论
2 举例子运用
3 实际开发注意要点
一 理论
1 线程和进程
a 一般运行一个程序就是一个进程. 一个程序至少有一个进程,一个进程至少有一个线程
b 线程,是进程的一部分,一个没有线程的进程可以被看作是单线程的 (相对进程而言,线程是一个更加接近于执行体的概念)
c 进程可以创建线程 也可以创建进程
d 线程由进程管理, 线程之间 线程和父进程之间(创建线程的进程)共享内存变量 (需要策略实现)
e 进程之间一般不可以直接共享内存变量,需要一些进程间的控制共享内存变量
2 iOS 多线程编程技术(这里主讲GCD)
NSThread NSOperation GCD
(1) NSThread
优点: 使用起来比 其他两个更轻量级
缺点:需要自己管理线程的生命周期,线程同步. 线程同步的加锁会又一定的系统开销
(2)NSOperation
优点:无需关心线程管理,数据同步.关键是操作执行操作.
相关类:抽象类 NSInvocationOperation 和 NSBlockOperation
创建NSOperation子类对象,把对象添加到NSOperationqueue队列里执行
(3)GCD (grand central dispatch)
iOS4后开始使用,是代替以上两种多线程方法的强大技术,GCD中FIFO队列称为 dispatch queue
dispatch queue :
a 串行队列: serial 队列 队里任务 是一个一个地执行
dispatch_queue_t queue = dispatch_queue_create("com.xxx.serialQueue",DISPATCH_QUEUE_SERIAL);// 自己创建一个串行队列
b 并行队列:concurrent 队列 通常任务会并发的执行
dispatch_queue_t queue = dispatch_queue_create("com.xxx.concurrentQueue",DISPATCH_QUEUE_CONCURRENT);// 自己创建一个并行队列
c 全局队列 : 系统级别的,直接拿来用,是一种并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
d 主队列 :一个应用程序对应唯一一个主队列,也是系统级别,直接拿来用,多线程开发使用主队列更新UI. 是一种串行队列
dispatch_queue_t queue = dispatch_get_main_queue();
二 举例运用
dispatch queue
#define global_queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define main_queue dispatch_get_main_queue()
(1)dispatch_async
耗时懂得操作,数据处理,IO读取数据库什么的 在另一个线程中处理,然后通知主线程更新UI
常用方法:
dispatch_async(global_queue, ^{ //耗时间性能的处理放在这 dispatch_sync(main_queue, ^{ //主线程更新UI }); });
(2)dispatch_group_async
可以实现监听一组任务是否完成, 完成后得到通知再执行其他操作
常用方法举例:下载 5张图片(或者说有五个独立数据请求),下载完了更新UI (都请求完成后执行其他操作)
- (void)fun { // 创建一个组 dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, global_queue, ^{ //开启一个任务1 }); dispatch_group_async(group, global_queue, ^{ // 开启一个任务2 });
//开启一个任务3
//开启一个任务4
//开启一个任务5
dispatch_group_notify(group, main_queue, ^{
// 等group中的所有任务都执行完毕, 再回到主线程执行其他操作 }); }
(3)dispathch_barrier_async(栅栏函数)
void dispatch_barrier_async ( dispatch_queue_t queue, dispatch_block_t block );
参数 queue: 将barrier添加到那个队列
block: barrier block 代码块
"在它前面的的任务执行后它才执行,在它后面的任务需要等它执行完成后才能执行".
使用情况是和并行队列一起使用,"同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用", 这个queue 一定是 自己创建的 并发队列,如果是 使用全局并发队列或者是一个串行队列,那么这个函数等同于dispatch_async函数.
使用举例:
- (void)barrier { dispatch_queue_t queue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);//这个queue 一定是 自己创建的 并发队列,如果是 使用全局并发队列或者是一个串行队列,那么这个函数等同于dispatch_async函数. dispatch_async(queue, ^{ NSLog(@"任务1"); }); dispatch_async(queue, ^{ NSLog(@"任务2"); }); dispatch_barrier_async(queue, ^{ NSLog(@"barrier"); }); dispatch_async(queue, ^{ NSLog(@"任务3"); }); dispatch_async(queue, ^{ NSLog(@"任务4"); }); }
打印结果
任务1
任务2 (1 和 2 并发 顺序不定)
barrier
任务3
任务4 (3 和 4 并发 顺序不定)
(4)dispatch_apply
执行N次的某一个代码片段
dispatch_apply(5, global_queue, ^(size_t index){ //此处任务执行5次 });
三 实际开发注意要点
重点是关注多线程死锁问题,和正确选择多线程策略
关于死锁,主要参考大学操作系统课程标配讲解:
死锁原因
(1)资源竞争 资源分配不当
(2)系统资源不足
(3)进程运行推进顺序不合适
死锁条件:
(1)互斥:进程在某一时间内独占资源
(2)请求与保持:进程已经拥有当前资源,但是又申请新资源
(3)不可剥夺: 进程的资源没使用完 就不可以强行从资源占有者处争夺资源
(4)循环等待 : 出现闭环
死锁预防:(破坏上述四个必要条件的一个或者几个)
(1)破坏上述四个必要条件的一个或者几个 来防止死锁产生
(2)鸵鸟算法,发生错误概率极小 可忽略
(3)仔细检查对资源的动态分配情况,来预防死锁
(4)检测死锁并且恢复
(5)避免死锁的一个通用的经验法则是:当几个线程都要访问共享资源A、B、C时,保证使每个线程都按照同样的顺序去访问它们,比如都先访问A,在访问B和C。这样不会产生闭环.
参考:
https://developer.apple.com/reference/dispatch#//apple_ref/c/func/dispatch_barrier_async