- 进程与线程
- 进程概念: 进程是程序在计算机上的一次执行活动,打开一个app,就开启了一个进程,可包含多个线程。
- 线程概念: 独立执行的代码段,一个线程同时间只能执行一个任务,反之多线程并发就可以在同一时间执行多个任务。
- iOS程序中,主线程(又叫作UI线程)主要任务是处理UI事件,显示和刷新UI,(只有主线程有直接修改UI的能力)耗时的操作放在子线程(又叫作后台线程、异步线程)。在iOS中开子线程去处理耗时的操作,可以有效提高程序的执行效率,提高资源利用率。但是开启线程会占用一定的内存,(主线程的堆栈大小是1M,第二个线程开始都是512KB,并且该值不能通过编译器开关或线程API函数来更改)降低程序的性能。所以一般不要同时开很多线程。
- 线程相关
- 同步线程:同步线程会阻塞当前线程去执行线程内的任务,执行完之后才会反回当前线程。
- 异步线程:异步线程不会阻塞当前线程,会开启其他线程去执行线程内的任务。
- 串行队列:线程任务按先后顺序逐个执行(需要等待队列里面前面的任务执行完之后再执行新的任务)。
- 并发队列:多个任务按添加顺序一起开始执行(不用等待前面的任务执行完再执行新的任务),但是添加间隔往往忽略不计,所以看着像是一起执行的。
- 并发VS并行:并行是基于多核设备的,并行一定是并发,并发不一定是并行。
- 多线程中会出现的问题
- Critical Section(临界代码段) 指的是不能同时被两个线程访问的代码段,比如一个变量,被并发进程访问后可能会改变变量值,造成数据污染(数据共享问题)。
- Race Condition (竞态条件) 当多个线程同时访问共享的数据时,会发生争用情形,第一个线程读取改变了一个变量的值,第二个线程也读取改变了这个变量的值,两个线程同时操作了该变量,此时他们会发生竞争来看哪个线程会最后写入这个变量,最后被写入的值将会被保留下来。
- Deadlock (死锁) 两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。
- Thread Safe(线程安全) 一段线程安全的代码(对象),可以同时被多个线程或并发的任务调度,不会产生问题,非线程安全的只能按次序被访问。
- 所有Mutable对象都是非线程安全的,所有Immutable对象都是线程安全的,使用Mutable对象,一定要用同步锁来同步访问(@synchronized)。
- 互斥锁:能够防止多线程抢夺造成的数据安全问题,但是需要消耗大量的资源
- 原子属性(atomic)加锁
- atomic: 原子属性,为setter方法加锁,将属性以atomic的形式来声明,该属性变量就能支持互斥锁了。
- nonatomic: 非原子属性,不会为setter方法加锁,声明为该属性的变量,客户端应尽量避免多线程争夺同一资源。
- Context Switch (上下文切换) 当一个进程中有多个线程来回切换时,context switch用来记录执行状态,这样的进程和一般的多线程进程没有太大差别,但会产生一些额外的开销。
前言
对初学者来说,GCD似乎是一道迈不过去的坎,很多人在同步、异步、串行、并行和死锁这几个名词的漩涡中渐渐放弃治疗。本文将使用图文表并茂的方式给大家形象地解释其中的原理和规律。
线程、任务和队列的概念
异步、同步 & 并行、串行的特点
一条重要的准则
一般来说,我们使用GCD的最大目的是在新的线程中同时执行多个任务,这意味着我们需要两项条件:
- 能开启新的线程
- 任务可以同时执行
- 结合以上两个条件,也就等价“开启新线程的能力 + 任务同步执行的权利”,只有在满足能力与权利这两个条件的前提下,我们才可以在同时执行多个任务。
所有组合的特点
(一)异步执行 + 并行队列
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
|
打印结果:
1 2 3 4 5 |
|
解释
- 异步执行意味着
- 可以开启新的线程
- 任务可以先绕过不执行,回头再来执行
- 并行队列意味着
- 任务之间不需要排队,且具有同时被执行的“权利”
- 两者组合后的结果
- 开了三个新线程
- 函数在执行时,先打印了start和end,再回头执行这三个任务
- 这三个任务是同时执行的,没有先后,所以打印结果是“任务3-->任务2-->任务1”
步骤图
(二)异步执行 + 串行队列
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
打印结果:
1 2 3 4 5 |
|
解释
- 异步执行意味着
- 可以开启新的线程
- 任务可以先绕过不执行,回头再来执行
- 串行队列意味着
- 任务必须按添加进队列的顺序挨个执行
- 两者组合后的结果
- 开了一个新的子线程
- 函数在执行时,先打印了start和end,再回头执行这三个任务
- 这三个任务是按顺序执行的,所以打印结果是“任务1-->任务2-->任务3”
步骤图
(三)同步执行 + 并行队列
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
打印结果:
1 2 3 4 5 |
|
解释
- 同步执行执行意味着
- 不能开启新的线程
- 任务创建后必须执行完才能往下走
- 并行队列意味着
- 任务必须按添加进队列的顺序挨个执行
- 两者组合后的结果
- 所有任务都只能在主线程中执行
- 函数在执行时,必须按照代码的书写顺序一行一行地执行完才能继续
- 注意事项
- 在这里即便是并行队列,任务可以同时执行,但是由于只存在一个主线程,所以没法把任务分发到不同的线程去同步处理,其结果就是只能在主线程里按顺序挨个挨个执行了
步骤图
(四)同步执行+ 串行队列
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
打印结果:
1 2 3 4 5 |
|
解释
- 这里的执行原理和步骤图跟“同步执行+并发队列”是一样的,只要是同步执行就没法开启新的线程,所以多个任务之间也一样只能按顺序来执行,
(五)异步执行+主队列
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
打印结果:
1 2 3 4 5 |
|
解释
- 异步执行意味着
- 可以开启新的线程
- 任务可以先绕过不执行,回头再来执行
- 主队列跟串行队列的区别
- 队列中的任务一样要按顺序执行
- 主队列中的任务必须在主线程中执行,不允许在子线程中执行
- 以上条件组合后得出结果:
- 所有任务都可以先跳过,之后再来“按顺序”执行
步骤图
(六)同步执行+主队列(死锁)
实现代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
打印结果:
1 |
|
解释
- 主队列中的任务必须按顺序挨个执行
- 任务1要等主线程有空的时候(即主队列中的所有任务执行完)才能执行
- 主线程要执行完“打印end”的任务后才有空
- “任务1”和“打印end”两个任务互相等待,造成死锁
步骤图