本篇文章作为多线程的一个开篇可能会偏向于概念性和基础性。因为我的计划是从这篇开始接下来几篇会去探究下GCD
的底层源码实现。所以有基础的同学可以略过这篇文章。
线程和进程的定义
线程:
进程:
线程和进程的关系
从上面我们知道线程没有独立的地址空间,但是我们又听说过有一个叫TLS
(线程的本地存储)的东西,那这个是什么呢?为什么叫线程的暂存空间呢?这里我摘选《程序员的自我修养》一书中的介绍:
* 线程局部存储(Thread Local Storage,TLS)。线程局部存储是某些操作系统为线程单独提供的私有空间,但通常只具有很有限的容量。
而且这个TLS
在后面我们对GCD
底层探究的时候会再次看到它,在同步锁中主要是对SyncData
进行一些存储。
多线程
多线程优缺点:
我们都知道iOS
的app
都是单进程
的(排除那些所谓的web进程
啥的)。那在这种情况下多线程的出现就更是解决了燃眉之急,因为若是没有多线程那么所有任务只能是一个接一个的去等待完成,效率会很低。这就涉及到多线程的意义或者说优缺点。如图:
在这里也把官方的多线程文档链接发一下,同时把网上经常能看到的一个关于内存的官网图片也发下:
多线程方案:
多线程原理:
同样也要明白一个问题就是,多线程并不等于并发。并不一定是所有任务一起执行。这就涉及到多线程的原理。如图:
时间片概念:
所以从上图我们可以知道多线程也不一定就是并发(所有任务一起执行)而是在CPU
的调度下在线程间不停切换。这样就能适当的避免被某个线程堵死进程,也能适当的节省资源提高效率。当然这里所说的CPU
都是以单核
为例的。如果是多核
那当然可以在同一时间调度处理多个
线程。这里就涉及到一个概念-时间片
如图:
多线程生命周期:
多线程之间的调度也是有生命周期的,因为CPU
在多个线程间频繁切换和调度的时候就涉及到每个线程都必须有各种状态
,比如从线程创建
->线程预备妥当
->线程被调度运行
->线程死亡
。在这过程中还有可能线程被睡眠
、加锁
等操作影响从而进入阻塞
状态。等待这一切操作结束这条线程又要进入到预备调度
和运行
的过程中。用一张图表示就如下:
多线程调度线程池和饱和策略:
线程调度池:
饱和策略:
任务的执行速度的影响因素:
我们了解了下调度线程池
和饱和策略
,那我们就不禁会思考我们任务执行速度会受哪些因素的影响呢?我们大概可以总结出以下几点:
1,CPU的情况
举个例子:我们去奶茶店买奶茶,如果这家店比较小只有一个工作人员和一个窗口来服务,那么相比隔壁一家有三个窗口五个服务员的比较大的奶茶店这家小的奶茶店在硬件上就比不上隔壁的,这也就是CPU
的影响。或者是同一家店你在买奶茶高峰期进店买和买奶茶人员稀少的时候进店也是不一样的。同样也是CPU
在不同状态下的影响。
2,任务的复杂度
举个例子:我们去奶茶店买奶茶,如果两个服务员同时给两个买奶茶的人制作奶茶,一个人要买十杯一个只需要一杯,那很明显两个人买完奶茶的任务的速度也是不一样的。这就是人物复杂度的影响。
3,任务优先级
举个例子:我们还是去奶茶店买奶茶,如果这家店有VIP服务
,两个人进店一个是SVIP
一个是普通的顾客。在接待上,很明显SVIP
会优先,而普通的则只能稍加等待。这就是优先级的影响。
4,线程状态
举个例子:我们还是去奶茶店买奶茶,如果这家店有两个窗口在卖奶茶,两个人分别同时到达各自的窗口点单。但是其中一个窗口的工作人员因为突然有急事需要离开两分钟,那么这个窗口就必须得暂停服务,另一个窗口的顾客就会先拿到奶茶离开。这就是线程状态的影响。
优先级翻转(IO
VS CPU
优先级提升):
当我们在执行任务的时候若是一切资源都比较充裕那么任务会很顺利执行,但是在遇到资源紧张的时候无论是来自硬件还是任务的条件都会大大影响任务执行的速度。甚至在某些情况下还会直接在饱和策略里被处理掉,或者直接被一直挂着。这种情况就会出现一个名词-饿死
在这种情况下就出现了另一个名词-优先级翻转
一般情况下,IO 密集型 (频繁等待)
的优先级是比 CPU 密集型 (很少等待)
低的。也就是会在某种情况下 因为优先级低的缘故或者其他缘故导致线程饿死 也得不到执行。这个时候就需要利用一些方法来解决这种情况。所以就需要CPU
来调度分配,将那些 频繁等待的任务提升优先级等,将资源倾斜一些给那些任务。但是并不是提升了优先级就一定会执行因为还有很多其他因素影响。
优先级影响因素:
一般我们会认为优先级的影响因素有三个:
1,用户指定
2,CPU
会根据进入执行的等待频繁度来提高和降低优先级
3,当有些任务一直不执行的时候也会得到优先级提升
以上的三点影响因素是《程序员自我修养》一书中有提到的。
多线程资源抢夺:
资源抢夺是我们在多线程里经常听到的一个名词,举个简单而经典的例子:
春运12306售票系统售票,成千上万个窗口同时售同一趟列车的票。但是票数是有限的比如10000张,当某个窗口售出去一张后,此时服务器里只剩下999张了,但是第二个窗口和这个窗口去查询票的时候得到的票数都是10000,只是第二个窗口出票比第一个窗口慢了一点,这个时候就造成了数据不同步,而造成资源抢夺。因为在某一个时刻多个窗口查询到的票数一样但是在出票的时候后台数据却因为某个窗口出票而有所改变,这个时候其他窗口并不知情,所以剩下的票可能就不足以出售这些窗口下的单。这个时候解决的办法就是加锁
。
今天我们简单了解下自旋锁和互斥锁的区别
自旋锁:
同步锁,在发现其他线程执行操作的时候,当前线程进入一个 忙等的状态,这个状态会一直主动询问,其他线程是否执行完毕,若是执行完毕则执行当前线程。自旋锁性能耗费比较高,适合用于比较短小简单的任务。
互斥锁:
同步锁,在发现其他线程执行操作的时候,当前线程进入 休眠状态,一直等待收到通知唤醒执行(需要主动操作)才会执行当前线程。互斥锁性能消耗比较小,适用于更多场景。
我们先看看自旋锁
其实在我们的开发过程中会常用到一个东西就是 属性的修饰符 atomic
。我们都知道atomic
是原子性 是为多线程开发准备的,是默认属性。但是在我们探究OC底层
的属性设置的时候会发现,atomic
其实就是一个标识符,这个标识符系统会自动添加一把自旋锁。而相对的非原子属性 的nonatomic
却没有。这也再次表明nonatomic
比atomic
性能高。我们看看部分代码:
本文到这里就差不多结束了,本文主要是针对以下概念的东西来简单分解,主要目的是为了后面我们用的非常多的GCD
篇章做铺垫。
遇事不决,可问春风。站在巨人的肩膀上学习,如有疏忽或者错误的地方还请多多指教。谢谢!