selector

iOS 多线程原理

2021-09-18  本文已影响0人  冼同学

前言

iOS开发过程中,线程的处理是我们不可绕开的技术话题。比如pthreadNSThreadGCDNSOperation,其中iOS开发中GCDNSOperation是我们最常用。在研究这些之前,我们先来了解一些多线程方面的概念。

1.线程和进程

1.1定义

1.2进程与线程的关系

1.3 多线程的意义

在开发过程中,多线程我们一直在面对着,但是我们明白多线程能给开发带来什么好处吗?通过以下的案例分析看看:

案例分析
上面的案例循环十万次创建内容,因为没有进行异步的处理,所以这个操作一直在主线程进行。这个过程一共耗时20秒左右,这样子给APP带来卡顿,极大地影响了用户体验。

为了解决以上的问题,我们需要对循环中的任务进行异步处理。如果一个事务很复杂,比较耗时,可以将一个大的事务拆分成多个小的事务进行并发处理,这样可以节省时间,并且不会影响用户的体验。

多线程的优缺点

1.4时间片概念

开启过多的线程也会导致性能的下降,这里涉及到时间片的概念。多线程的执行是CPU快速的在多个线程之间进行切换。线程数过多,CPU会在多个线程之间切换,销毁大量的CPU资源,反而导致执行效率的下降

2.iOS内存五大区

我们知道内存主要分为五大区,分别是:栈区堆区全局区常量区代码区,详细见下图:

内存五大区

2.1栈区(stack)

注意:传入函数的参数值、函数体内声明的局部变量等,由编译器自动分配释放,通常在函数执行结束后就释放了。(不包括static修饰的变量,static意味该变量存放在全局/静态区)。

2.2堆区(heap)

注意:当访问堆中内存时,一般需要先通过对象读取到栈区指针地址,然后通过指针地址访问堆区。因为现在iOS基本都使用ARC来管理对象,所以也不需要手动释放

2.3全局区(静态区)(BSS段)

2.4常量区(数据段)

2.5代码区(text段)

3.线程的生命周期

线程的生命周期包含5个阶段,包括:新建就绪运行阻塞销毁。见下图:

线程的生命周期

4.线程池的运行策略

线程池的运行策略,见下图:

线程池运行策略
由上图可知:队列满正在运行的线程数量小于最大线程数,则新进入的任务,会直接创建非核心线程工作。

线程池的饱和策略

如果线程池中的队列满了,并且正在运行的线程数量已经大于等于当前线程池的最大线程数,则进行饱和策略的处理。

5.自旋锁和互斥锁

5.1.自旋锁

一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。
注意:在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。

自旋锁:OSSpinLockdispatch_semaphore_t

5.2互斥锁

当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务,该任务也不会立刻执行,而是成为可执行状态就绪)。
互斥锁:pthread_mutex@synchronizedNSLockNSConditionLockNSConditionNSRecursiveLock

5.3自旋锁和互斥锁的特点

5.4原子属性和非原子属性

注意:

5.5原子属性和非原子属性的原理

在探索类的本质时,对于类的属性的setter方法,系统会有一层objc_setProperty的封装(libobjc.dylib源码)。见下图:

objc_setProperty方法
底层会调用reallySetProperty方法,在该方法的实现中,针对原子属性,添加了spinlock锁,见下图:
reallySetProperty
SpinlockLinux内核中提供的一种比较常见的锁机制,自旋锁是原地等待的方式解决资源冲突的,即,一个线程获取了一个自旋锁后,另外一个线程期望获取该自旋锁,获取不到,只能够原地打转(忙等待)。由于自旋锁的这个忙等待的特性,注定了它使用场景上的限制 —— 自旋锁不应该被长时间的持有(消耗CPU资源)。
注意:atomic只是原子属性,一个标识符,所以atomic并不是自旋锁,底层是通过Spinlock实现自旋锁。

6.iOS技术方案

iOS技术方案

补充:锁的详细剖析会在下一篇文章中

上一篇下一篇

猜你喜欢

热点阅读