OC底层原理之多线程原理

本章内容

关于多线程的各种概念。

本章目的

了解这些概念的东西,以便于后续对多线程的底层实现进行研究。会针对GCD底层实现libdispatch进行研究的(其实iOS和安卓的多线程都是对pthread的封装实现,因为pthread很完善了,苹果也没有自行再开发一套)

提示:本文概念东西十分多,看着容易瞌睡

线程与进程

进程:在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程都运行在其专用的且受保护的内存空间内。(认为是APP)

线程:线程是进程的基本执行单元,一个进程的所有任务都在线程中执行。进程要想执行任务就必须得有线程,也就是说线程至少会有一条。(认为是APP内的主线程等。)

补充:线程是没有独立内存地址空间的,只能用进程的,TLS(也就是之前我在objc_init方法中提到的)认为是本地的线程暂存空间,感觉两者很矛盾?

引用《程序员自我修养》书中:线程的局部存储空间是某些操作系统为线程单独提供的私有空间,但通常只具备有限的容量,很少。并不属于线程,而是操作系统维他安排的

俩者关系

  1. 地址空间:同一进程的线程共享本进程的地址空间,而进程之间是独立的地址空间
  2. 资源拥有:同一进程内的线程共享本进程的资源如内存、I/O、CPU等,而进程之间的资源是独立的

image.png

补充

APP是单进程的,但是iOS系统是多进程的,所以说iOS的APP不能进行进程通讯。但是安卓APP可以进程之间通讯。

多线程

既然iOS的APP是单进程,那么在耗时的操作的时候就需要用到多线程去处理哪些复杂的东西了

多线程的原理:当我们有很多任务要进行执行的时候,CPU就会进行调度线程去执行任务,例如线程有1-3,线程1正在执行任务,那么CPU就会调度线程2安排任务让线程2执行。

也就是说:CPU来回不断的切换去调度线程处理任务。所以多线程并不是真正的并发(在同一时间内同时执行),所以要实现真正意义上多线程的话就需要:多核。

如果要减少CPU资源,那么我们要开的线程数不能过于多。例如手机并不像电脑那样的性能。

时间片:CPU在多个任务切换的时间间隔。

线程生命周期

就是:1->2->3->4->2。差不多是这样看下面概念

  1. 线程创建:例如 NSThread *t = [[NSThread alloc] init….]

  2. 线程调用start:[t start] 代表了线程开始了要准备就绪了,那么这条线成会进入Runnable状态,在这个状态下等待CPU调度,就是加入到可调度线程池中。(注意在这个状态下CPU并不一定会立马执行只是等待调度)

  3. CPU调度线程:当线程正常被CPU调度执行的话就会在执行完成后dead(线程销毁)。如果说线程被CPU调度执行后发生堵塞进入4

  4. 线程阻塞Blocked:当线程内调用sleep或者等待同步锁等等,CPU就会将线程从可调度线程池中移出,而线程在sleep到时或者获取同步锁后则会重新添加回可调度线程池。并且回到2状态(注意我说的是runnable等待调度)

补充:我们看到objc底层代码的时候就会有很多加锁的处理就是为了防止多线程访问

线程池

我们经常看到线程的创建的时候运行出来的结果并不是按顺序的,而是乱序的。例如:当线程1正在执行,可能下来不是线程2去安排执行,而是变为线程15,当又安排任务执行的时候会可能是线程8。也就是说:1,15,8,2,17这样的乱来的。那么这是为什么呢?

线程池工作原理:会有一个阈值(判断线程池满不满)。如果没达到的话创建一条。这是错误的不完善的,请看下面

  1. 先判断核心线程是否都正在执行任务?如果说有可用的空闲的就会被CPU从可调度线程池中拿到去执行。否则2

  2. 再看线程池的工作队列是否饱和,也就是阈值判断?如果说不饱和的话就将任务添加在工作队列,安排线程执行。否则3

  3. 看线程池中的线程是否都在执行状态?如果说没有,则安排线程去执行任务。否则4

  4. 交给饱和策略处理

饱和策略

当前任务还在,但是任务没有被安排到位,就只能来这里了。

  1. AbortPolicy 抛出异常(RejectedExecutionException)阻止系统正常运行。例如:假如上面的阈值是100,但是这个任务来了变成101了,那么已经超出CPU的能力了超负载了。

  2. CallerRunsPolicy 将任务回退给调度者。例如:任务A我执行不了,回退给你,你看着办

  3. DisOldestPolicy 丢掉等待最久的任务。例如:一个任务久久不能执行,CPU会一直给其提升优先级,但是一直提升你还没法执行,CPU也救不活你,扔了算了

  4. DisCardPolicy 直接丢弃任务。

线程与runloop

关于runloop以后会写一篇文章探究

  1. runloop与线程是一一对应的,一个runloop对应一个核心线程。为什么说核心线程?因为runloop是可以嵌套的,但是核心的只有一个,他们的关系保存在一个全局字典中。

  2. runloop是来管理线程的,当线程的runloop被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务

  3. runloop在第一次获取时被创建,在线程结束时被销毁

  4. 对于主线程来说,runloop在程序一启动就默认创建好了

  5. 对于子线程来说,runloop是懒加载的,只有在我们使用的时候才会创建,所以在子线程使用定时器要注意:确保子线程的runloop被创建,不然定时器不会回调

关于多线程的问题

任务的执行速度影响因素有哪些

1。CPU的调度情况(例如CPU当前的负荷),2。 任务的复杂度,3。 任务的优先级,4。 线程的状态(线程的数量,并发数等等)

例如:去银行存取钱,银行有3个窗口(3条线程1,2,3),老王要来取钱到窗口1(线程1),要取1000W(任务复杂度)所以窗口1要执行很久。而老李来存钱,要存100亿(任务复杂度),大堂经理一看是VIP中P(任务优先级)就赶紧迎上来将线程3正在执行的人执行完后(线程状态)给其他人说去别的窗口让线程3单独给老李办理(CPU调度)。

优先级翻转

有不成文规定:IO会比CPU更容易得到优先级提升。IO密集型的肯定会比CPU密集型的执行速度慢,而IO型的会频繁等待所以有可能这个任务会被舍弃掉“饿死”。所以CPU开始进行调度给这个线程进行优先级提升,如果一直没执行就会一直提升。但是如果一直没执行的话就会DisOldestPolicy

IO密集型:代表频繁等待线程

CPU密集型:很少等待线程

优先级影响因素

  1. 用户指定的优先级,例如iOS的服务质量NSQualityOfService
  2. 根据进入等待状态的频繁程度或者降低
  3. 长时间不执行,也会提升优先级。这点其实属于第二点

多线程的资源共享(资源抢夺)

atomic,可以看objc源码方法就是在setter,getter方法中加了锁spinlock_t。其实自旋锁是根据互斥锁封装的。我们平时开发用的最多的就是互斥锁。关于对锁的探究以后会有

image.png

iOS 经常用的多线程

后续会直接研究GCD的原理,iOS的多线程我们掌握GCD就知道全部了,其使用方法看API或者自行百度均能实现。关于多线程其实还要知道线程间的通讯问题,这些都可以找到相应api
image.png

补充 C与OC的桥接

对于C和OC的桥接,以及C与swift的桥接,OC与swift的桥接,以及方法函数名称冲突的解决其实是均可以的。但是这里只介绍第一种

C与OC的桥接,你可以类比是CFFoundation与Foundation。

  1. __bridge只做类型转换,但是不修改对象(内存)管理权
  2. __bridge_retainedCFBridgingRetain将OC对象转换为Core Foundation对象,同时将对象(内存)管理权交给我们,后续释放需要使用CFRelease或者相关方法释放对象
  3. __bridge_transferCFBridgingRelease将Core Foundation对象转换为OC对象,同时将对象(内存)的管理权交给ARC
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享