IOS - 多线程 基础
什么是线程
线程(英语:thread)是操作系统能够进行运算调度的最小单位。
什么是进程
进程(英语:process),是计算机中已运行程序的实体
一个进程中可以并发多个线程,每条线程并行执行不同的任务。
什么是程序
程序(英语:procedure),指特定的一系列动作、行动或操作,而这些活动、动作或操作必须以相同方式运行,借此在相同环境下恒常得出相同的结果(例如紧急应变程序)
他们的区分方法
程序真正运行在计算机中的实体,就变成了进程,(正在运行的程序叫进程)而进程可以包含一个或多个线程。
OC三种线程技术
1.NSThread------每个NSThread对象对应一个线程,量级较轻(真正的多线程)
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期、线程同步、睡眠以及唤醒。线程同步对数据的加锁会有一定的系统开销
2.NSOperation------NSOperation/NSOperationQueue 面向对象的线程技术,基于GCD
优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上
添加操作之间的依赖关系,方便的控制执行顺序
设定操作执行的优先级
可以很方便的取消一个操作的执行
使用 KVO 观察对操作执行状态的更改
3.GCD------Grand Central Dispatch(派发) 是基于C语言的框架,可以充分利用多核,是苹果推荐使用的多线程技术
优点: GCD主要与block结合使用。代码简洁高效。执行效率稍微高点。
会自动管理线程的生命周期
会自动利用更多的CPU内核
GCD队列及执行方式
串行队列:一个接一个的执行任务 dispatch_queue_create("pw_GCD", DISPATCH_QUEUE_SERIAL)
并发队列:可以同时调度多个任务 dispatch_queue_create("pw_GCD", DISPATCH_QUEUE_CONCURRENT)
全局队列:dispatch_get_global_queue(0, 0)
同步 : 一个任务没有结束,就不会执行下一个任务
异步 : 不用等待任务执行完毕,就会执行下一个任务 (会开启子线程 )
串行队列,同步执行 不会开辟线程 会按顺序执行
串行队列,异步执行 会开线程 会按顺序执行
并发队列 同步执行 不会开线程 会按顺序执行
并发队列 异步执行 会开线程 不会按顺序执行
Operation队列及执行方式
主队列:凡是添加到主队列中的操作,都会放到主线程中执行(注:不包括操作使用addExecutionBlock:添加的额外操作,额外操作可能在其他线程执行 )[NSOperationQueue mainQueue]
operation跟GCD的区别
- GCD是底层的C语言,operation是基于GCD封装
- operation可以随时取消已经设定要准备执行的任务,GCD需要很复杂的代码才能取消
- 能够将KVO应用在operation中,监听operation是否完成或取消,能比GCD更加有效的掌控后台线程
- operation能够设置优先级,在同一并行队列中任务区分先后执行。
- NSOperation的高级:最大并发数,控制线程个数,优化了线程的暂停、继续、取消功能
线程安全
线程锁
1、互斥锁 NSLock :对于互斥锁,当一个线程获得这个锁之后,其他想要获得此锁的线程将会被阻塞,直到该锁被释放
@synchronized(锁对象) { // 需要锁定的代码 } 适用线程不多,任务量不大的多线程加锁
优缺点:
2、条件锁:NSCondition [lock lockWhenCondition:1];
优点:线程安全
缺点:性能很低
3、递归锁:NSRecursiveLock
使用递归锁可以在一个线程中反复获取锁而不造成死锁,这个过程中会记录获取锁和释放锁的次数,只有最后两者平衡锁才被最终释放。
使用格式
@synchronized(锁对象) { // 需要锁定的代码 }
优点:能有效防止因多线程抢夺资源造成的数据安全问题
缺点:需要消耗大量的CPU资源
4、自旋锁:OSSpinLock
当一个线程获得锁之后,其他线程将会一直循环在哪里查看是否该锁被释放(此锁比较适用于锁的持有者保存时间较短的情况下)
不能保证线程安全
线程安全 - GCD 栅栏方法:dispatch_barrier_async
我们有时需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于 栅栏 一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以包含一个或多个任务。
//前面的耗时操作执行完毕之后
dispatch_barrier_async(queue, ^{
// 追加任务 barrier
[NSThread sleepForTimeInterval:2]; // 模拟耗时操作
NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
});
// 栅栏方法 才会继续执行
避免数据竞争
注意:全局队列 禁用了栅栏函数
线程安全 - GCD 信号量
====定义====:就是一种可用来控制访问资源的数量的标识,设定了一个信号量,在线程访问之前,加上信号量的处理,则可告知系统按照我们指定的信号量数量来执行多个线程
GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于过高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在 Dispatch Semaphore 中,使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过。
ispatch_semaphore_create:创建一个 Semaphore 并初始化信号的总量
dispatch_semaphore_signal:发送一个信号,让信号总量加 1
dispatch_semaphore_wait:可以使总信号量减 1,信号总量小于 0 时就会一直等待(阻塞所在线程),否则就可以正常执行。
dispatch_semaphore_create(1): 传入值必须 >=0, 若传入为 0 则阻塞线程并等待timeout,时间到后会执行其后的语句
dispatch_semaphore_wait(signal, overTime):可以理解为 lock,会使得 signal 值 -1
dispatch_semaphore_signal(signal):可以理解为 unlock,会使得 signal 值 +1
注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量。
主要作用:
- 保持线程同步,将异步执行任务转换为同步执行任务
- 保证线程安全,为线程加锁
定时器
NSTimer定时器不够准确,会受runloop 模式的影响
GCD里面的定时器不会受到runloop 模式影响,需要注意的是创建的定时器需要被强引用,不然在block回调之前就会被释放
CADisplayLink 一个能让我们保证和屏幕刷新率同步的频率,将特定的内容画到屏幕上的定时器类
比较常用的是前两个