主要涉及解决线程间同步及资源共享方面的解决方案。
iOS系统为我们提供的多线程技术方案有哪些?
GCD(使用频度最高),NSOperation(图片异步下载),NSThread(常驻线程)
NSThread
文字简述:(1)当创建好一个线程并开启线程(CPU或许在调度其他的线程,所以也许不会马上调度新开启的这个线程,因为CPU是来回切换执行的),那么就会把这个线程对象放进图中绿色的可调度线程池里面进入就绪状态等待CPU调度,(2)假设过了一会CPU(CPU是切换执行的,会分配给这个线程一个时间片)有时间了切换到这个线程上调度,那么这个线程就进入运行状态,如果这期间CPU在分配的时间片里没有把这个线程执行完毕又停下这个线程的执行去调度其他的线程,那么会记录此时这个线程执行的状态再切换到其他线程,此刻这个线程状态重新变为就绪状态(3)如果这个线程调用sleep方法/等待同步锁,那么这个线程就进入阻塞状态,sleep到时/得到同步锁,才会重新进入就绪状态(4)线程的死亡有自然死亡和非自然死亡,自然死亡就是线程执行完毕,非自然死亡就是发生异常或者程序强制退出了
起了线程名字,如若该线程出现异常,可以快速定位到问题。
线程执行的时间不由我们确定,每次的执行都不确定,像刚刚上面这种情况线程是不安全的,不能保证数据和预期的结果是一致的。
下面是解决方案:用互斥锁来防止因多线程抢夺资源造成的数据安全问题
注意⚠️:如果[多线程]访问同一个资源,那么必须使用同一把锁才能锁住,在开发中,尽量不要加锁,能在服务端做尽量在服务端做,如果必须要加锁,一定要记住,锁的范围不能太大,哪里有[安全隐患]就加在哪里。
技巧:因为必须使用同一把锁,开发中如果需要加锁,直接使用 self 即可。
线程间通讯
GCD
Test1
Test1文字简述:
viewDidLoad和Block都是在主线程调用,那么两者都被加入主队列(主队列本身就是串行队列)中,(重点是队列相互等待,而不是线程问题,不管是不是主队列 是要是同一个队列 都会发生死锁)由于主队列先进先出FIFO的特性,执行完viewDidLoad才能执行block,可是block是同步在ViewDidLoad方法里,所以就造成了互相等待的死锁问题。
Test2
test2文字简述:
重点在于是加在了不同的队列。block是加在一个自定义队列,而不是与viewDidLoad同一个队列,所以先执行viewDidLoad,执行到block的时候,因为是同步的方式 所以会先把serialQueue上的任务dosomething执行完,就会继续往下执行,所以是没有问题的,不存在相互等待的问题
Test3
Test3答案简述:输出:1,2,3,4,5,这是对同步并发队列的理解
global_queue为全局并发队列,里面的任务为并发执行。
按代码顺序输出1,(同步方式提交任务到并发队列还是串行队列,它都是在当前线程完成)同步方式提交一个任务到全局并发队列中,那么打印2在主线程中执行,同步方式提交一个任务到全局并发队列中,那么打印3在主线程中执行,然后打印4,打印5
Test4
文字简述:
- performSelector:withObject:afterDelay:其实就是在内部创建了一个NSTimer,然后会添加到当前线程的Runloop中
- 因为方法里有延迟执行,所以内部有NSTimer计时,timer只有在runloop开启的状态下才能执行,而这个block块是异步方式,子线程的runloop默认情况下是关闭状态,所以不会调用printLog
那么怎样才可以打印1,2,3呢?把子线程的runloop打开不就好了吗?是的 在log3上面,performSelector下面这个位置加上
[[NSRunLoop currentRunLoop] run];
那么疑问又来了 是不是在block块里哪个位置都可以比如log1下面?答案是不行的 下面截出run的一段官方文档如下:
Discussion
If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the `NSDefault<wbr>Run<wbr>Loop<wbr>Mode`by repeatedly invoking [runMode:beforeDate:](apple-reference-documentation://hcGlc34FMW). In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
Manually removing all known input sources and timers from the run loop is not a guarantee that the run loop will exit. macOS can install and remove additional input sources as needed to process requests targeted at the receiver’s thread. Those sources could therefore prevent the run loop from exiting.
意思就是如果当前runloop没有任何事件(source、timer、observer)的话,那么它会马上退出,所以应该写在selector下面,这样就可以保证开启runloop的时候 有事件timer,那么runloop就不会退出了
Test5 多读单写
多读单写方案:
dispatch_barrier_async(dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT), ^{
//写操作
});
为什么要同步读取呢?因为获得数据是想可以很快得到的,实现多读就是objectforkey可以在多个线程调用,所以多个线程传入并发队列,也就实现了多读。
Test6: dispatch_group_async
dispatch_queue_t concurrent_queue = dispatch_queue_create("concurrent_queue", DISPATCH_QUEUE_CONCURRENT);
//创建一个队列组
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 3; i ++) {
//异步组分派到并发队列当中
//在队列组group中的concurrent_queue队列中添加任务
dispatch_group_async(group, concurrent_queue, ^{
//下载
});
}
//会执行dispatch_group_notify这个方法,但是里面的block块只会当队列中的人物全部执行完毕才会执行
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//当添加到组中的所有任务执行完成之后会调用该block
});
Test7
1.NSOperation任务执行状态有:
- isReady. 当前任务是否就绪
- isExcecuting 是否处于正在执行中
- isFinish 是否已完成
- isCancel led 是否已取消
2.状态控制:
- 如果只重写了main方法,底层控制变更任务执行完成状态,以及任务退出
- 如果重写了start方法,自行控制任务状态
只重写main源码分析:首先看start方法:首先是创建一个自动释放池,然后获取线程的优先级,然后进行一系列状态异常判断,如果没有异常,会判断是否处于正在执行中,如果是没有执行,则设置为正在执行的状态,之后会在判断当前任务是否被取消,如果没有被取消,就会调用NSOperation 的 main函数,之后调用finish,之后调用自动释放池的release操作。
Test8 NSThead
test:NSThead启动流程的内部实现:首先创建一个NSThead之后会调用start方法,然后来启动线程,在start方法内部会创建一个pThead线程,然后会指定pThead中的一个启动函数,在启动函数中会调用pThead所定义的main函数, 之后在main函数中会通过target performSelector来执行目标函数selector,最后调用exit来结束这个线程
test: 如果要实现一个常驻线程的话,可以在selector方法里维护一个runloop,实现事件的运行循环,从而达到实现常驻线程的目的
Test8 锁
iOS当中有哪些锁?
- @synchronized:一般在创建单例对象的时候使用,从而保证在多线程情况下创建对象是唯一的。
- atomic:修饰属性的关键字;对被修饰对象进行原子操作(不负责使用,就是只对赋值setter保证线程安全 但是对于可变数组的使用add delete不保证线程安全的)
- OSSpinLock自旋锁:循环等待访问,不释放当前资源(比如去房间有人会一直敲门),用于轻量级数据访问,简单的int值 +1/-1操作
- NSLock解决线程同步问题 来保证各个线程互斥进入自己的临界区
- NSRecursiveLock 递归锁
-
dispatch_semaphore_t
简述:锁已经使用过了,所以需要等到锁解开才可以再次访问加锁,线程被阻塞住了,这就造成了死锁。
简述:在这种情况下,我们就可以使用NSRecursiveLock。它可以允许同一线程多次加锁,而不会造成死锁。递归锁会跟踪它被lock的次数。每次成功的lock都必须平衡调用unlock操作。只有所有达到这种平衡,锁最后才能被释放,以供其它线程使用。