22.iOS底层学习之多线程原理

2021-12-30  本文已影响0人  牛牛大王奥利给

本篇提纲:
1、线程与进程
2、多线程
3、多线程相关面试题
4、线程安全问题
5、线程与runloop的关系

线程与进程

他们之间的关系可以用不同的公司和生产线来比喻,比如我们现在有很多正在运作的公司,每个公司我们可以理解为一个进程;而每个公司的内部有很多部门,比如产品部、市场部、技术部去生产不同的东西,可以理解为公司内部的三条线程,各自有各自的任务,但是整个公司的空间和资源是这三个线程可以共享的。

多进程要比多线程更加健壮,一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃了会导致整个进程都崩溃掉。

我看到了一篇解释进程和线程关系的比较清楚易懂的文章,还有疑问可以看下进程和线程关系的简单解释

多线程

多线程的实现原理:
iOS中的多线程,由CPU在多个任务之间进行快速切换,CPU调度线程的时间足够快,就造成了多线程的“同时”执行的效果。

时间片:CPU在多个任务之间进行快速的切换,这个时间间隔称为时间片

如果开启的线程太多会导致的问题:
1、CPU会在N个线程之间来回切换,会消耗大量的CPU资源。
2、每个线程的调度次数会被降低,线程的执行效率会很低。

多线程的好处:
1、能适当提高程序的执行效率。
2、能适当的提高CPU、内存等资源的利用率。

多线程会带来的问题:
1、开启线程需要占用内存,默认情况下,每一个线程都占512KB。
2、会让程序设计更加复杂,比如线程之间的通信,多线程的资源共享问题。

iOS多线程技术方案:

方案 简介 语言 线程生命周期 使用频率
pthread - 一套通用的多线程API
- 适用于Unix/Linux/Windows等系统
- 跨平台、可移植
- 使用难度大
C语言 程序员管理 几乎不用
NSThread - 面向对象
- 简单易用,可直接操作线程对象
OC语言 程序员管理 偶尔使用
GCD - 旨在替代NSThread等线程技术
- 充分利用设备多核
C语言 自动管理 经常使用
NSOperation - 基于GCD(底层是GCD)
- 比GCD多了一些更简单实用的功能
-使用更加面向对象
OC语言 自动管理 经常使用

多线程生命周期:
线程的生命周期主要分为五个阶段:

线程生命周期图.jpg

线程池原理:
1、先去判断核心线程池是否都在执行任务

2、判断线程池工作队列是否饱满

3、判断线程池中的线程是否都处于执行状态

4、交给饱和策略去执行,分为以下四种拒绝策略:

四种拒绝策略均实现的RejectedExecutionHandler接口

多线程相关面试题

IO密集型比CPU密集型更容易得到线程优先级的提升

IO操作的速度很慢,并且需要频繁的等待,如果它的优先级又低很容易被饱和策略所淘汰。为了避免这种情况,当CPU发现一个频繁等待的线程,会提升它的优先级,从而提升线程被执行的可能性。

线程安全问题

使用注意事项:
互斥锁的锁定范围,应该尽量小,锁定范围越大,效率越差。
能够加锁的任意NSObject对象
锁对象一定要保证所有的线程都能够访问

自旋锁的使用场景:锁持有的时间短,或者线程不希望在重新调度上花太多成本时,就可以使用自旋锁。

使用自旋锁,当新的线程访问代码时,如果发现有其他线程已经锁定了代码,新线程会用一种死循环的方法,一直等待锁定的代码执行完成,即不停的尝试执行代码,比较消耗性能。

附一张网上查到的自旋锁和非自旋锁的流程图:


自旋锁和非自旋.png

自旋锁的好处:
自旋锁用循环去不停地尝试获取锁,让线程始终处于 Runnable 状态,节省了线程状态切换带来的开销。

因为阻塞和唤醒线程都是需要高昂的开销的,如果同步代码块中的内容不复杂,那么可能切换线程带来的开销比实际业务代码执行的开销还要大。

在很多场景下,可能我们的同步代码块的内容并不多,所以需要的执行时间也很短,如果我们仅仅为了这点时间就去切换线程状态,那么其实不如让线程不切换状态,而是让它自旋地尝试获取锁,等待其他线程释放锁,有时我只需要稍等一下,就可以避免上下文切换等开销,提高了效率。

关于atomic的源码分析,来看下内部的锁:


image.png

它的内部实现是添加的这种spinlock_t锁,而spinlock_t是:

using spinlock_t = mutex_tt<LOCKDEBUG>;
class mutex_tt : nocopy_t {
    os_unfair_lock mLock;
 public:
   .......
}

OS_UNFAIR_LOCK_AVAILABILITY
typedef struct os_unfair_lock_s {
    uint32_t _os_unfair_lock_opaque;
} os_unfair_lock, *os_unfair_lock_t;

根据注释部分的描述,可以了解到os_unfair_lock加完锁之后,会让另外一个线程进入休眠状态,而不是忙等,所以其实是互斥锁,这个改变是在iOS10之后,在iOS10之前使用的是OSSpinLock,之所以被os_unfair_lock替代也是出于线程饿死的问题。

image.png
线程与RunLoop的关系

1、RunLoop与线程是一一对应的,一个Runloop对应一个核心线程。为什么说是核心的,因为RunLoop里可以进行嵌套,但是核心只能有一个,他们的关系保存在一个全局字典里。

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

3、RunLoop在第一次获取时被创建,在线程结束时被销毁。

4、主线程的RunLoop,在程序启动的时候默认创建。

5、对于子线程来说,RunLoop是懒加载的,只有当我们使用的时候才会去创建。所以,当子线程使用定时器时,要确保子线程的RunLoop被创建,不然定时器无法进行回调。

上一篇 下一篇

猜你喜欢

热点阅读