iOS 多线程原理(1)

215 阅读9分钟

学习重点

进程与线程的定义

多线程及其原理

线程的生命周期

可调度线程池以及饱和策略

自旋锁与互斥锁

1. 进程以及线程的定义

1.1 进程

  进程是指在系统中正在运行的一个应用程序,每个进程之间是独立的,每个进程均运行在其专用的且受保护的内存空间内,通过”活动监视器”可以查看Mac系统中开启的进程。

  如下图所示:

image.png

1.2 线程

  线程是进程的基本执行单元,一个进程的所有任务都在线程中执行,进程想要执行任务,必须得有线程,进程至少要有一条线程,程序启动默认会开启一条线程,这条线程被称为主线程或UI线程。

1.3 进程与线程的关系

  地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。

  资源拥有:同一进程内的线程共享本进程的资源,如内存、I/OCPU等,但是进程之间的资源是独立的。

  1. 一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都会死掉。所以多进程要比多线程健壮。
  2. 进程切换时,消耗的资源大,效率高。所以涉及到频繁的切换时,使用线程要好于进程。同样如果要求同时进行并且又要共享某些变量的并发操作,只能用线程而不能用进程。
  3. 执行过程:每个独立的进程有一个程序运行的入口,顺序执行序列和程序入口。但是线程不能独立执行,必须依存在应用程序中,而应用程序提供多个线程执行控制。
  4. 线程是处理器调度的基本单位,而进程不是。
  5. 线程没有地址空间,线程包含在进程地址空间中。

2. 多线程

2.1 为何使用多线程

  在iOS开发中,我们不可能使用单线程来开发,因为我们在开发过程中,经常需要编写一些耗时操作的代码(例如:网络请求、文件上传下载、蓝牙的操作等),如果仅是单线程开发(仅有主线程),那么必将发生UI上的卡顿,将会极大的影响用户体验,例如在以下界面中编写如下代码:

image.png

- (IBAction)networkOperation:(id)sender {
    
    NSLog(@"开始耗时操作");
    
    for (int i = 0; i < 100 * 1000; i++) {
        int a = i * i;
        
        NSString *str = @"----";
        
        NSLog(@"正在进行耗时操作:%@ %d", str, a);
    }
    
    NSLog(@"耗时操作执行完毕");
}

  当按钮点击时,将执行其中的耗时操作,你会发现,在控制台打印输出信息的过程中(有将近20秒的时长),你是无法做任何其他操作的(例如:滑动textView文本),因此单线程是无法满足我们的需求的。

2.2 多线程的优缺点

2.2.1 多线程的优点

  • 能适当提高程序的执行效率。

  • 能适当提高资源的利用率(CPU,内存)。

  • 线程上的任务执行完成后,线程会自动销毁。

2.2.2 多线程的优点

  • 开启线程需要占用一定的内存(默认情况下,每一个线程都占512KB)。

  • 如果开启大量的线程,会占用大量的内存空间,降低程序的性能。

  • 线程越多,CPU在调用线程上的开销就越大。

  • 程序设计更加复杂,比如线程间的通信,多线程的数据共享。

2.3 多线程的原理

  任务的执行依赖于线程,那么多线程则表示多个任务同时执行,而CPU同一时间只能执行一个任务,那么多线程是怎么来的呢?   CPU在执行任务的时候,实际上是多个线程之间快速切换执行,这个时间间隔是很小的,小到我们感觉多个线程是同时执行的。   所以单核CPU的多线程就是快速的在多个线程中不断的切换调用,已达到所有线程都在同时进行的效果,但是现在很多设备都是双核,四核或者八核的,相当于多个CPU同时处理,更能加快系统的运行处理速度,这才是真正意义上的多线程。

  上述所说的那段很短的时间也称之为时间片(通常在10-100毫秒左右),其概念就是CPU在多个任务之间进行快速的切换,这个时间间隔就是时间片。如果线程数非常多,CPU会在N个线程之间切换,消耗大量的CPU资源,每个线程被调度的次数会降低,线程的执行效率降低。

3. 线程的声明周期、可调度线程池以及饱和策略

3.1 线程的声明周期

  线程的生命周期一共分为五个部分分别是:新建,就绪,运行,阻塞以及死亡。由于CPU需要在多条线程中切换,因此线程状态也会在多次运行和阻塞之间切换,如下图所示:

image.png

  • 新建状态:被创建,在内存中,但不在可调度线程池中。

  • 就绪状态:调用线程的start方法后,在可调度池中,可以执行任务。

  • 运行状态:在可调度线程池中,正在执行任务。

  • 阻塞状态:被移除可调度线程池,在内存中,不能执行任务。

  • 死亡状态:被释放。

3.2 可调度线程池

  线程的运行策略,如下图所示:

image.png

  • 首先,先判断线程池中线程的数量是否超过核心线程数(corePoolSize),如果没有超过核心线程数,就创建新的线程去执行任务;

  • 否则,判断任务队列是否已满,如果没有满,就将任务添加到任务队列中;

  • 否则,再判断如果创建一个线程后,线程数是否会超过最大线程数(maximumPoolSize),没有超过最大线程数,就创建一个新的线程来执行任务;

  • 否则,执行饱和策略

3.3 饱和策略

  饱和策略共有下面四种,这四种策略均实现了RejectedExecutionHandler接口。

  • AbortPolicy直接抛出RejectedExecutionExeception异常来阻止系统正常运行。

  • CallerRunsPolicy将任务回退到调用者。

  • DisOldestPolicy丢掉等待最久的任务。

  • DisCardPolicy直接丢弃任务。

4. 锁

  在计算机科学中,锁是一种同步机制,用于在存在多线程的环境中实施对资源的访问限制。你可以理解成它用于排除并发的一种策略!你可以理解为为了防止多线程访问下资源的抢夺,保持线程同步的方式。

  使用任何锁都需要消耗系统资源(内存资源和CPU时间),这种资源消耗可以分为两类:

  1. 建立锁所需要的资源

  2. 当线程被阻塞时所需要的资源

4.1自旋锁

  自旋锁是一种用于保护多线程共享资源的锁,与一般互斥锁(mutex)不同之处在于当自旋锁尝试获取锁时以忙等待(busy waiting)的形式不断地循环检查锁是否可用。当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会一直等待(不会睡眠),当上一个线程的任务执行完毕,下一个线程会立即执行。

  在多CPU的环境中,对持有锁较短的程序来说,使用自旋锁代替一般的互斥锁往往能够提高程序的性能。

  • 优点:自旋锁的优点在于,因为自旋锁不会引起调用栈睡眠,所以不会进行线程调度,CPU时间片轮转等耗时操作,所以如果能在很短的时间内获得锁,自旋锁的效率远高于互斥锁

  • 缺点:自旋锁一直占用CPU,在未获得锁的情况下,一直运行(自旋),所以占用CPU,如果不能在短的时间内获得锁,这无疑会使CPU效率降低,自旋锁不能实现递归调用。

  常用自旋锁:atomicOSSpinLockdispatch_semaphore_t

4.2 互斥锁

  当上一个线程的任务没有执行完毕的时候(被锁住),那么下一个线程会进入睡眠状态等待任务执行完毕,当上一个线程的任务执行完毕,下一个线程会自动唤醒然后执行任务。

  互斥锁保证了锁内的代码,同一时间,只有一条线程能够执行,使用互斥锁时应注意锁定范围应该尽量小,锁定范围越大,其效率越差。

  常用互斥锁:pthread_mutex@ synchronizedNSLockNSConditionLockNSConditionNSRecursiveLock

4.3 atomic与nonatomic

4.3.1 两者的定义及区别

  我们在使用OC开发iOS应用程序时,定义OC类的属性时可以选择atomic或者nonatomic,其中:

  • atomic: 原子属性,为setter方法加自旋锁(即为单写多读)。

  • nonatomic: 非原子属性,不为setter方法加锁。

  这两种方式的区别如下:

  • atomic: 线程安全,需要消耗大量的资源。

  • nontomic: 非线程安全,适合内存小的移动设备。

4.3.2 使用建议

  在iOS开发中,我们一般都遵循以下的原则进行程序代码的编写:

  • 如非需抢占资源的属性(如购票,充值等),所有的属性都声明为nonatomic

  • 尽量避免多线程抢夺同一块资源。

  • 尽量将加锁、资源抢夺的业务逻辑交给服务器端处理,以减小移动客户端的压力。

4.3.3 atomic原理探究

  首先我们都知道在属性的setter方法底层,实际上调用了objc_setProperty函数,其代码如下所示:

image.png

  在这个函数中调用了reallySetProperty函数,其代码如下所示:

image.png

  在这个函数中,可以发现实际上atomic实际上只是一个标识符,而添加自旋锁的功能实际上是由spinlock_t类型变量slotlock所完成的,其定义如下所示:

image.png

  spinlock就是系统所提供的一种自旋锁。

5. iOS技术方案

image.png