在进行iOS开发过程中,我们常会遇到网络请求、复杂计算、数据存取等比较耗时的操作,如果处理不合理,将对APP的流畅度产生较大影响。除了优化APP架构,并发(concurrency)是一个常用且较好的解决方法,但并发涉及串行、并发、并行、同步、异步、多线程、GCD、NSOperation和NSOperationQueue等诸多容易混淆的概念,为求概念清晰明了,还请茗茶静坐,听我徐徐道来。
一、线程和任务
线程(thread) 和任务(task)是其他并发概念的基础,因此也是首要需理清的概念,以下是其要点,详细可参考Thread (computing)和Task (computing))。
1)任务(task)
a)任务(task)是从程序中划分出来,可以独立执行的代码片段;
b)任务间可以添加依赖关系,如B任务依赖A任务,taskB.addDependency(taskA)
,这意味着B任务的执行以A任务完成为前提。
需要注意的是一个任务是否可以添加依赖,完全取决于任务封装类和其相关管理类的具体实现,GCD不支持任务依赖,NSOperationQueue
就支持任务依赖。
下面的代码是对一个任务的简单封装,并支持任务间的依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//Task是一个任务的简单封装类 class Task { let taskBlock: () -> () var dependencies = [Task]() init(block: () -> ()) { taskBlock = block } func addDependency(task: Task) { dependencies.append(task) } } //初始化两个自定义任务 var taskA = Task(){ //自定义任务A,自定义需要执行的代码 ... } var taskB = Task(){ //自定义任务B,自定义需要执行的代码 ... } //添加依赖关系 taskB.addDependency(taskA) |
2)线程(thread)
a)线程(thread)是代码执行的独立路径,一条线程只能同时执行一行代码(一行代码,其实就是一条处理器命令)。
b)线程中代码管理是以任务(task)为单位,一条线程逐行执行一个任务中的代码(任务可以取消),完成后再逐行执行下一个任务中的代码。
c)一条线程跳出一个任务的执行,即意味着这个任务的完成。因此,一条线程不能执行taskA一段时间后,还未完成就开始执行taskB,然后又返回执行taskA(这其实是单线程内的并发,与单核处理器的并发概念相同,具体实践中不存在线程内并发)。
二、概念释疑
1)并行(parallelism)和并发(concurrency)
并发和并行都是指多个任务可以同时执行,都属于多线程编程概念,因此二者必然十分相近,容易混淆。二者区别只有一点,即是否多任务执行于严格的同一时刻。并发不是,并行是。
单核处理器时代(一个处理器同一时刻只能执行一条命令),为了实现多任务的同时执行,系统利用时间分片(time-slicing)技术,将处理器的执行时间切分为多个小片段,一会执行threadA,一会执行threadB,一会再执行threadA,即在多个线程(任务是在线程上执行的)之间来回跳动执行。虽不是真的多线程多任务同时执行,但由于处理器的处理速度非常快,在用户看来,仍然是同时执行的。这种伪多线程就是并发。
多核处理器时代(不同处理器相互独立,可以同时执行各自的命令),多条线程完全可以严格同一时刻执行,这种真多线程就是并行。
1 2 3 4 5 |
//三个线程的并发 thread1 -> |---A---| ->|---A---| \ / \ thread2 ------> ->|------B----| \ thread3 ------------------------------------> |------C------| |
上述代码是三个线程的并发执行,可以看出thread1、thread2和thread3不可能严格同一时刻执行,但也都获得了处理器的一小段执行时间。
1 2 3 4 |
//三个线程的并行 thread1 -> |-----A-----| thread2 -> |------B----| thread3 -> |------C------| |
上述代码是三个线程的并行执行,可以看出thread1、thread2和thread3有一段时间同时执行。
现在的终端设备无论是手机还是PC的处理器,大多都已是多核处理器,可以实现并行计算,但为了最大化的利用处理器的性能,现代处理器还是融合了time-slicing技术和多核技术,因此实际运行中,有时并发,有时并行。但相对来说,并发是个更广泛的概念,因此Apple的多线程编程叫做concurrency programming
并发编程。汉语中,并发和并行的区别其实没那么清晰,可以互用,而且有时用并行更加明确,如串并行比串行、并发针对性更强。(为概念清晰期间,下文中有时会用并行,其实即是并发。)
2)串并行与线程
串行(serial)和并行
串行和并行主要区别在于一个任务的执行是否以上一个任务的完成为前提。串行中,一个任务的执行必须以上一个任务执行结束为前提,并行中,一个任务的执行与上一个任务的执行状态无关。以排队买票为例,串行像单个买票队伍,单个卖票窗口,必须一个一个来,串行像单个买票队伍,多个卖票窗口,多个人可以同时买票。
1 2 |
//三个串行任务 |-----A-----||------B--------||----C--| |
上文为三个串行任务,任务A完成后,才执行任务B,B结束后,才最后执行任务C。
1 2 3 4 |
//三个并发任务 |-----A-----| |------B----| |--C---| |
上文为三个并行任务,任务A早于任务C开始,却晚于任务C结束。
串并行与线程
串并行主要关注多个任务之间的相互依赖关系,与线程无关。但实际中,任务是在线程中执行的,是否串行一定在单线程上执行,并行一定在多个线程中执行呢?并非如此。
单线程既可以实现串行,也可以实现并行。
一、线程和任务
线程(thread) 和任务(task)是其他并发概念的基础,因此也是首要需理清的概念,以下是其要点,详细可参考Thread (computing)和Task (computing))。
1)任务(task)
a)任务(task)是从程序中划分出来,可以独立执行的代码片段;
b)任务间可以添加依赖关系,如B任务依赖A任务,taskB.addDependency(taskA)
,这意味着B任务的执行以A任务完成为前提。
需要注意的是一个任务是否可以添加依赖,完全取决于任务封装类和其相关管理类的具体实现,GCD不支持任务依赖,NSOperationQueue
就支持任务依赖。
下面的代码是对一个任务的简单封装,并支持任务间的依赖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//Task是一个任务的简单封装类 class Task { let taskBlock: () -> () var dependencies = [Task]() init(block: () -> ()) { taskBlock = block } func addDependency(task: Task) { dependencies.append(task) } } //初始化两个自定义任务 var taskA = Task(){ //自定义任务A,自定义需要执行的代码 ... } var taskB = Task(){ //自定义任务B,自定义需要执行的代码 ... } //添加依赖关系 taskB.addDependency(taskA) |
2)线程(thread)
a)线程(thread)是代码执行的独立路径,一条线程只能同时执行一行代码(一行代码,其实就是一条处理器命令)。
b)线程中代码管理是以任务(task)为单位,一条线程逐行执行一个任务中的代码(任务可以取消),完成后再逐行执行下一个任务中的代码。
c)一条线程跳出一个任务的执行,即意味着这个任务的完成。因此,一条线程不能执行taskA一段时间后,还未完成就开始执行taskB,然后又返回执行taskA(这其实是单线程内的并发,与单核处理器的并发概念相同,具体实践中不存在线程内并发)。
二、概念释疑
1)并行(parallelism)和并发(concurrency)
并发和并行都是指多个任务可以同时执行,都属于多线程编程概念,因此二者必然十分相近,容易混淆。二者区别只有一点,即是否多任务执行于严格的同一时刻。并发不是,并行是。
单核处理器时代(一个处理器同一时刻只能执行一条命令),为了实现多任务的同时执行,系统利用时间分片(time-slicing)技术,将处理器的执行时间切分为多个小片段,一会执行threadA,一会执行threadB,一会再执行threadA,即在多个线程(任务是在线程上执行的)之间来回跳动执行。虽不是真的多线程多任务同时执行,但由于处理器的处理速度非常快,在用户看来,仍然是同时执行的。这种伪多线程就是并发。
多核处理器时代(不同处理器相互独立,可以同时执行各自的命令),多条线程完全可以严格同一时刻执行,这种真多线程就是并行。
1 2 3 4 5 |
//三个线程的并发 thread1 -> |---A---| ->|---A---| \ / \ thread2 ------> ->|------B----| \ thread3 ------------------------------------> |------C------| |
上述代码是三个线程的并发执行,可以看出thread1、thread2和thread3不可能严格同一时刻执行,但也都获得了处理器的一小段执行时间。
1 2 3 4 |
//三个线程的并行 thread1 -> |-----A-----| thread2 -> |------B----| thread3 -> |------C------| |
上述代码是三个线程的并行执行,可以看出thread1、thread2和thread3有一段时间同时执行。
现在的终端设备无论是手机还是PC的处理器,大多都已是多核处理器,可以实现并行计算,但为了最大化的利用处理器的性能,现代处理器还是融合了time-slicing技术和多核技术,因此实际运行中,有时并发,有时并行。但相对来说,并发是个更广泛的概念,因此Apple的多线程编程叫做concurrency programming
并发编程。汉语中,并发和并行的区别其实没那么清晰,可以互用,而且有时用并行更加明确,如串并行比串行、并发针对性更强。(为概念清晰期间,下文中有时会用并行,其实即是并发。)
2)串并行与线程
串行(serial)和并行
串行和并行主要区别在于一个任务的执行是否以上一个任务的完成为前提。串行中,一个任务的执行必须以上一个任务执行结束为前提,并行中,一个任务的执行与上一个任务的执行状态无关。以排队买票为例,串行像单个买票队伍,单个卖票窗口,必须一个一个来,串行像单个买票队伍,多个卖票窗口,多个人可以同时买票。
1 2 |
//三个串行任务 |-----A-----||------B--------||----C--| |
上文为三个串行任务,任务A完成后,才执行任务B,B结束后,才最后执行任务C。
1 2 3 4 |
//三个并发任务 |-----A-----| |------B----| |--C---| |
上文为三个并行任务,任务A早于任务C开始,却晚于任务C结束。
串并行与线程
串并行主要关注多个任务之间的相互依赖关系,与线程无关。但实际中,任务是在线程中执行的,是否串行一定在单线程上执行,并行一定在多个线程中执行呢?并非如此。
单线程既可以实现串行,也可以实现并行。