iOS多线程浅汇-原理篇
一、前言
多线程这个词对于大家来说并不陌生,但是真正能够熟知多线程的坑点只在少数,iOS中并行也一直被认为是恶魔,有时候会出现很多莫名其妙的bug。
大家都知道:
新开一条线程,提高程序运行效率,避免阻塞主线程
但是有时候,多开一条线程只意味着多一条线程的性能损耗。
除此之外熟练掌握原理,特别是对于几个常见的概念(同步,异步,串行,并行)理解得十分透彻的确实不多,可能有许多人和我一样处于似懂非懂的状态。带着这个困惑,翻看了许多博文还有apple的官方文档,整理了下有关多线程编程的原理部分内容,多线程实践篇请移步占位,添加实践链接。
二、基本概念
进程:
一个具有一定独立功能的程序关于某个数据集合的一次运行活动
文档中关于进程的描述:
The term process is used to refer to a running executable, which can encompass multiple threads.
系统中正在运行的一个应用程序就是一个进程,每个进程都运行在自己独有的受保护的内存中。在iPhone中,每一个APP都是一个进程。
线程:
The term thread is used to refer to a separate path of execution for code.
线程是指可独立执行的代码路径,它程序执行流的最小单元,是进程中的一个实体,是执行程序最基本的单元,有自己栈和寄存器。
一个进程的所有任务都在线程中进行,因此每个进程都至少有一个线程,这个线程被称为主线程。
多线程:
一个进程可以开启多条线程,让所有任务异步执行。
同步:
只能在当前线程下按按照FIFO(先进先出)的规则顺序执行,当前任务会阻塞下一个任务。如果在同步线程下开了更多的线程,只会造成资源的浪费。比如开了三条线程,每个任务都需要等待上一个任务结束才能被处理,这种情况下系统就必须在三个线程之间来回切换。线程有自己的栈和寄存器,三个线程就有三组栈和寄存器中的值需要不断地被备份、替换。这相对于只有一组栈和寄存器(只有一条线程)来说,效率肯定大大降低了。当开发者使用GCD和NSOperation的时候,系统会默认只在一条线程,避免造成资源浪费。
异步:
an asynchronous function does some work behind the scenes to start a task running but returns before that task might actually be complete. Typically, this work involves acquiring a background thread, starting the desired task on that thread, and then sending a notification to the caller (usually through a callback function) when the task is done.
异步方法在执行的时候,不会等到其内部代码全部执行完毕才返回,而是立即返回往下执行,在另一条线程中执行内部代码。
异步与同步的区别在于当前任务不会阻塞下一个任务的进行,当一条线程中的当前任务还未执行完毕,而另一条线程没有任务正在执行时,CUP就会将下一个任务分配给空闲的线程,由于CUP的速度够快,所以看起来每个任务都是同时进行的。
并发(concurrency):
Concurrency is the notion of multiple things happening at the same time
并发形容的是许多任务在同一时间需要处理,侧重在于“发生”。
并行(parallel)
指的是有许多任务同时在运行,侧重在“运行”。
队列:
装载线程任务的队形结构
调度队列(dispatch queue)&操作队列(operation queue)
A dispatch queue executes tasks either serially or concurrently but always in a first-in, first-out order
iOS中的队列(准确的说应该是GCD和NSOperation才引入的概念)是用于管理任务的执行方式,dispatch queue是GCD下的方式,它是C底层机制的;operation queue是NSOperation的方式,但是和dispatch queue的用法和管理任务的机制几乎是一样的,NSOperation是在OC层面上对GCD的封装。
唯一的区别在于:
Whereas dispatch queues always execute tasks in first-in, first-out order, operation queues take other factors into account when determining the execution order of tasks
operation queue在决定任务的执行顺序的时候会考虑其他因素,比如是否添加了依赖等
队列又分为串行队列和并行队列:
串行队列:
Serial queues (also known as private dispatch queues) execute one task at a time in the order in which they are added to the queue. The currently executing task runs on a distinct thread (which can vary from task to task) that is managed by the dispatch queue
串行队列(也称私人队列)在一个时间内只会执行一个任务。线程里的任务按照顺序执行,同步执行:只有一个线程,且每个任务按照顺序依次执行;异步执行:有多个线程,每个线程中的任务按照顺序执行。
串行队列的每个队列中一次只能执行一个任务,但是系统并没有限制开发者只能有一个队列,开发者同样可以通过串行队列实现并发任务的处理。
if you create four serial queues, each queue executes only one task at a time but up to four tasks could still execute concurrently, one from each queue.
如果创建了四个串行队列,每个队列中的一次只执行一个任务,这样就能做到在同一时间执行四个任务。
并行队列:
Concurrent queues (also known as a type of global dispatch queue) execute one or more tasks concurrently, but tasks are still started in the order in which they were added to the queue. The currently executing tasks run on distinct threads that are managed by the dispatch queue. The exact number of tasks executing at any given point is variable and depends on system conditions.
并行队列(全局队列)可以同时执行多个任务,任务也是按照添加到队列的顺序执行的,当期运行的任务在后台线程执行,由dispatch queue负责调度,系统会根据系统状况决定一次能执行的最多任务数。
In iOS 5 and later, you can create concurrent dispatch queues yourself by specifying DISPATCH_QUEUE_CONCURRENT as the queue type.In addition, there are four predefined global concurrent queues for your application to use
iOS5以后,apple允许开发者自己创建并行队列,同时,apple还提供了四个已经创建好的全局队列供开发者使用,只需要获取就行。
note:同步执行:只有一个线程,所以没办法多个任务同时执行;异步执行:有多个线程,多个线程中的多个任务可以同时进行。
三、举个栗子
用一个例子说明同步异步,串行并行之间的主要功能。
现在你有三个耗时任务task1,task2,task3,分别用时为2s,1s,1s。
这里需要清楚一个概念:线程是“一个CPU执行的一条无分叉的命令列”,单核CPU上,同一时刻只能有一条线程运行。
对于串行并行来说,是执行速度上的概念,如果有三个CUP同时执行,三个任务的执行时间为2s,因为三个任务可以同时执行,
对于同步异步来说,是执行顺序上的概念,如果加入task1为下载图片的任务,而task2和task3是UI相关的任务,使用异步执行task1,减少了2s的卡顿时间,在这个期间用户仍然可以触摸滚动。
如果使用GCD和NSOperation,由于系统会帮开发者管理线程的生命周期:
同步串行:任务一个接着一个执行;
func syncSerial() {NSLog("=======>>>syncSerial<<<========") let q1 = dispatch_queue_create("q1",DISPATCH_QUEUE_SERIAL)dispatch_sync(q1) { () -> VoidinNSLog("1-%@",[NSThread.currentThread()]); }dispatch_sync(q1) { () -> VoidinNSLog("2-%@",[NSThread.currentThread()]); }dispatch_sync(q1) { () -> VoidinNSLog("3-%@",[NSThread.currentThread()]); }dispatch_sync(q1) { () -> VoidinNSLog("4-%@",[NSThread.currentThread()]); }dispatch_sync(q1) { () -> VoidinNSLog("5-%@",[NSThread.currentThread()]); }NSLog("=======>>>syncSerial<<<========") }
打印结果:
2016-03-23 13:38:29.521 Mutiple[8199:2165063] =======>>>syncSerial<<<========
2016-03-23 13:38:29.522 Mutiple[8199:2165063] 1-(
"{number = 1, name = main}"
)
2016-03-23 13:38:29.522 Mutiple[8199:2165063] 2-(
"{number = 1, name = main}"
)
2016-03-23 13:38:29.522 Mutiple[8199:2165063] 3-(
"{number = 1, name = main}"
)
2016-03-23 13:38:29.523 Mutiple[8199:2165063] 4-(
"{number = 1, name = main}"
)
2016-03-23 13:38:29.523 Mutiple[8199:2165063] 5-(
"{number = 1, name = main}"
)
2016-03-23 13:38:29.523 Mutiple[8199:2165063] =======>>>syncSerial<<<========
同步并行:任务一个接着一个执行;
func syncConcurrency() {NSLog("=======>>>syncConcurrency<<<========") let q1 = dispatch_queue_create("q1",DISPATCH_QUEUE_CONCURRENT)dispatch_sync(q1) { () -> VoidinNSLog("1-%@",[NSThread.currentThread()]); }dispatch_sync(q1) { () -> VoidinNSLog("2-%@",[NSThread.currentThread()]); }dispatch_sync(q1) { () -> VoidinNSLog("3-%@",[NSThread.currentThread()]); }dispatch_sync(q1) { () -> VoidinNSLog("4-%@",[NSThread.currentThread()]); }dispatch_sync(q1) { () -> VoidinNSLog("5-%@",[NSThread.currentThread()]); }NSLog("=======>>>syncConcurrency<<<========") }
打印结果:
2016-03-23 13:40:16.968 Mutiple[8256:2181632] =======>>>syncConcurrency<<<========
2016-03-23 13:40:16.969 Mutiple[8256:2181632] 1-(
"{number = 1, name = main}"
)
2016-03-23 13:40:16.970 Mutiple[8256:2181632] 2-(
"{number = 1, name = main}"
)
2016-03-23 13:40:16.970 Mutiple[8256:2181632] 3-(
"{number = 1, name = main}"
)
2016-03-23 13:40:16.970 Mutiple[8256:2181632] 4-(
"{number = 1, name = main}"
)
2016-03-23 13:40:16.970 Mutiple[8256:2181632] 5-(
"{number = 1, name = main}"
)
2016-03-23 13:40:16.970 Mutiple[8256:2181632] =======>>>syncConcurrency<<<========
异步串行:任务一个接着一个执行;
func asyncSerail() {NSLog("=======>>>asyncSerail<<<========") let q1 = dispatch_queue_create("q1",DISPATCH_QUEUE_SERIAL)dispatch_async(q1) { () -> VoidinNSLog("1-%@",[NSThread.currentThread()]); }dispatch_async(q1) { () -> VoidinNSLog("2-%@",[NSThread.currentThread()]); }dispatch_async(q1) { () -> VoidinNSLog("3-%@",[NSThread.currentThread()]); }dispatch_async(q1) { () -> VoidinNSLog("4-%@",[NSThread.currentThread()]); }dispatch_async(q1) { () -> VoidinNSLog("5-%@",[NSThread.currentThread()]); }NSLog("=======>>>asyncSerail<<<========") }
打印结果:
2016-03-23 13:04:26.713 Mutiple[7294:1959223] =======>>>asyncSerail<<<========
2016-03-23 13:04:26.714 Mutiple[7294:1959223] =======>>>asyncSerail<<<========
2016-03-23 13:04:26.715 Mutiple[7294:1959473] 1-(
"{number = 3, name = (null)}"
)
2016-03-23 13:04:26.716 Mutiple[7294:1959473] 2-(
"{number = 3, name = (null)}"
)
2016-03-23 13:04:26.716 Mutiple[7294:1959473] 3-(
"{number = 3, name = (null)}"
)
2016-03-23 13:04:26.716 Mutiple[7294:1959473] 4-(
"{number = 3, name = (null)}"
)
2016-03-23 13:04:26.716 Mutiple[7294:1959473] 5-(
"{number = 3, name = (null)}"
)
异步并行:多个任务一起执行,顺序不固定,但是可以通过dispatch_group和NSOperation添加依赖控制任务执行顺序。
func asyncConcurrency() {NSLog("=======>>>asyncConcurrency<<<========") let q1 = dispatch_queue_create("q1",DISPATCH_QUEUE_CONCURRENT)dispatch_async(q1) { () -> VoidinNSLog("1-%@",[NSThread.currentThread()]); }dispatch_async(q1) { () -> VoidinNSLog("2-%@",[NSThread.currentThread()]); }dispatch_async(q1) { () -> VoidinNSLog("3-%@",[NSThread.currentThread()]); }dispatch_async(q1) { () -> VoidinNSLog("4-%@",[NSThread.currentThread()]); }dispatch_async(q1) { () -> VoidinNSLog("5-%@",[NSThread.currentThread()]); }NSLog("=======>>>asyncConcurrency<<<========") }
打印结果:
2016-03-23 13:30:20.987 Mutiple[8107:2109033] =======>>>asyncConcurrency<<<========
2016-03-23 13:30:20.988 Mutiple[8107:2109033] =======>>>asyncConcurrency<<<========
2016-03-23 13:30:20.989 Mutiple[8107:2109139] 5-(
"{number = 7, name = (null)}"
)
2016-03-23 13:30:20.989 Mutiple[8107:2109119] 3-(
"{number = 4, name = (null)}"
)
2016-03-23 13:30:20.989 Mutiple[8107:2109093] 1-(
"{number = 3, name = (null)}"
)
2016-03-23 13:30:20.989 Mutiple[8107:2109118] 2-(
"{number = 5, name = (null)}"
)
2016-03-23 13:30:20.990 Mutiple[8107:2109138] 4-(
"{number = 6, name = (null)}"
)
异步下创建多个串行队列实现并行效果
func multiAsyncSerail() {NSLog("=======>>>asyncSerail<<<========") let q1 = dispatch_queue_create("q1",DISPATCH_QUEUE_SERIAL)dispatch_async(q1) { () -> VoidinNSLog("1-%@",[NSThread.currentThread()]); } let q2 = dispatch_queue_create("q2",DISPATCH_QUEUE_SERIAL)dispatch_async(q2) { () -> VoidinNSLog("2-%@",[NSThread.currentThread()]); } let q3 = dispatch_queue_create("q3",DISPATCH_QUEUE_SERIAL)dispatch_async(q3) { () -> VoidinNSLog("3-%@",[NSThread.currentThread()]); } let q4 = dispatch_queue_create("q4",DISPATCH_QUEUE_SERIAL)dispatch_async(q4) { () -> VoidinNSLog("4-%@",[NSThread.currentThread()]); } let q5 = dispatch_queue_create("q5",DISPATCH_QUEUE_SERIAL)dispatch_async(q5) { () -> VoidinNSLog("5-%@",[NSThread.currentThread()]); }NSLog("=======>>>asyncSerail<<<========") }
打印结果:
2016-03-23 13:33:21.412 Mutiple[8150:2128078] =======>>>asyncSerail<<<========
2016-03-23 13:33:21.413 Mutiple[8150:2128078] =======>>>asyncSerail<<<========
2016-03-23 13:33:21.414 Mutiple[8150:2128320] 4-(
"{number = 7, name = (null)}"
)
2016-03-23 13:33:21.414 Mutiple[8150:2128308] 1-(
"{number = 3, name = (null)}"
)
2016-03-23 13:33:21.414 Mutiple[8150:2128312] 3-(
"{number = 5, name = (null)}"
)
2016-03-23 13:33:21.414 Mutiple[8150:2128321] 5-(
"{number = 6, name = (null)}"
)
2016-03-23 13:33:21.414 Mutiple[8150:2128311] 2-(
"{number = 4, name = (null)}"
)
小结:
使用GCD实现多线程时:
同步串行和同步并行:都会按照顺序依次执行任务,且不会开启新的线程。
异步串行:会开启新的线程,但是系统默认在同一线程内按照任务顺序依次执行,因为这个时候开了多个线程也要顺序执行任务,开线程只会造成资源浪费
异步并行:开启多个线程,有多个任务同时执行,系统会根据每条线程的情况分配,执行顺序不固定。
在异步串行情况下,可以通过创建多个串行队列,实现异步并行的功能,让多个任务同时进行。