进程:也就是一个正在运行的应用程序。
线程:进程中的某一条完整的执行路径。一个进程可以有多个线程,至少有一个线程,即主线程。在iOS开发中,所有涉及UI界面的,必须在主线程中更新。
在iOS开发中,所有的UI 操作都是在主线程中进行的,如果耗时操作在主线程,会造成页面卡顿。应该将耗时操作放在子线程中执行,GCD 是iOS多线程当中比较常用的方法
GCD 可用于多核的并行运算,会自动利用更多的 CPU 内核(比如双核、四核);
GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码。
GCD 中两个核心概念:任务和队列。
(1)任务:执行什么操作(2)队列:用来存放任务
将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行
提示:任务的取出遵循队列的FIFO原则:先进先出,后进后出
GCD中有2个用来执行任务的函数:
1. 同步执行(sync):dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
2. 异步执行(async):dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
两个函数相同点:
1. 队列中的任务是按照先来先执行的顺序开始执行
2. 把一个block任务加入到指定的队列中
同步函数 dispatch_sync 函数:要求加入到队列的block立即执行,block 执行结束前阻塞当前线程,会一直等待不能进行其他操作。直到当前任务完成之后才能执行下一个任务
异步函数 dispatch_async 函数:把block任务加入队列后,不等block执行完成就返回了,任务不会立即执行,线程不做等待,可以继续执行其他任务。任务执行结束时间是不确定的,取决于每个任务的耗时。对于n个并发队列,GCD不会创建对应的n个线程而是进行适当的优化。
差异点:
1. 同步函数只在完成了它预定的任务后才返回。
2. 异步函数会立即返回,预定的任务会完成,但不会等它完成。任务可以先绕过不执行,回头再来执行。因此,一个异步函数不会阻塞当前线程去执行下一个函数。
并发与并行
单核设备可以通过快速的切换上下文,从一个线程,切换到另一个线程进行执行。 并发是逻辑上的同时发生,指一个处理器同时处理多个任务。多个事件在同一时间交替发生。
并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。是物理上的同时发生,真的可以实现。多核设备通过并行来同时执行多个线程;
![](https://img.haomeiwen.com/i14367363/4004e4c2f528a441.png)
GCD公开有5个不同的队列(Dispatch Queue)
1. 运行在主线程中的main queue(主队列)
2. 3个不同优先级的后台队列(优先级队列)
3. 以及一个优先级更低的后台队列(用于I/O)。
队列分为2种:串行队列和并行队列。
两者的主要区别是:执行顺序不同,以及开启线程数不同。
串行队列(Serial Dispatch Queue):
每次只有一个任务被执行。让任务一个接着一个地执行。
并行队列(Concurrent Dispatch Queue):
可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)
注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效
![](https://img.haomeiwen.com/i14367363/da65c38b72d073cf.png)
队列的创建方法/获取方法
可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。
1. DISPATCH_QUEUE_SERIAL表示串行队列
2. DISPATCH_QUEUE_CONCURRENT表示并发队列。
![](https://img.haomeiwen.com/i14367363/1eac66d75d105625.png)
1. GCD 提供了的一种特殊的串行队列:主队列(Main Dispatch Queue)--串行队列
所有放在主队列中的任务,都会放到主线程中执行,使用dispatch_get_main_queue()获得主队列。
主队列中的任务必须在主线程中执行,不允许在子线程中执行
2. GCD 默认提供了全局并发队列(Global Dispatch Queue)
用dispatch_get_global_queue 来获取。
两个参数:
1. 表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。
DISPATCH_QUEUE_PRIORITY_HIGH: QOS_CLASS_USER_INITIATED (2:高)
DISPATCH_QUEUE_PRIORITY_DEFAULT: QOS_CLASS_DEFAULT (0:默认)
DISPATCH_QUEUE_PRIORITY_LOW: QOS_CLASS_UTILITY (-2:低)
DISPATCH_QUEUE_PRIORITY_BACKGROUND: QOS_CLASS_BACKGROUND
2. flags:保留参数,以便以后使用,一般传0即可。
任务的创建方法
GCD 提供了同步执行任务的创建方法dispatch_sync和异步执行任务创建方法dispatch_async。
![](https://img.haomeiwen.com/i14367363/c1f523de6f249ebf.png)
1. 同步执行 + 并发队列:所有任务都是在当前线程中执行的,没有开启新的线程
任务按顺序执行的。虽然并发队列可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步任务需要等待队列的任务执行结束)。所以任务只能一个接一个按顺序执行,不能同时被执行。
2. 异步执行 + 并发队列
除了当前线程(主线程),系统开启了新的线程,并且任务是交替/同时执行的。当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)。
3. 异步执行 + 串行队列
开启了一条新线程,异步执行不做任何等待,可以继续执行任务。任务是按顺序执行的、
4. 同步执行 + 主队列会出现死锁
![](https://img.haomeiwen.com/i14367363/15dc325c9c095e2c.png)
![](https://img.haomeiwen.com/i14367363/6d8d23b8e6f5c636.png)
GCD 栅栏方法:dispatch_barrier
有时需要异步执行两组操作,第一组作执行完之后,才能执行第二组操作。需要一个相当于栅栏一样的一个方法将两组异步执行的操作组给隔离开,每一个操作组里可以包含一个或多个任务。可以用 dispatch_barrier_async 方法在两个操作组间形成栅栏。
dispatch_barrier 函数相当于一个停顿,在这个函数之前加入队列的任务先执行,执行完毕后执行栅栏函数中的任务,然后再执行栅栏函数之后的任务
注意:
dispatch_barrier_sync :在当前线程同步执行,需要等待自己的任务(barrier)结束之后,才会继续添加并执行写在barrier后面的任务,然后执行后面的任务。会阻塞后续代码的运行。
dispatch_barrier_async: 在子线程中执行,将自己的任务(barrier)插入到队列之后,不会等待自己的任务结束,会继续把后面的任务插入到队列,然后执行任务。不会阻塞后续代码的运行
GCD 延时执行方法:dispatch_after:在指定的延迟时间点才将任务追加到主队列中
需求:在指定时间(例如3秒)之后执行某个任务。
需要注意的是:dispatch_after 函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。这个时间并不是绝对准确的,要大致延迟执行任务,dispatch_after函数是很有效的。
![](https://img.haomeiwen.com/i14367363/13605aa2fd84f19a.png)
GCD 一次性代码(只执行一次):dispatch_once
创建单例、或者有整个程序运行过程中只执行一次的代码时,可以用 GCD 的dispatch_once函数
dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,在多线程下,可以保证线程安全。
![](https://img.haomeiwen.com/i14367363/447232c177574310.png)
GCD 快速遍历方法:dispatch_apply
dispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束后返回
1. 串行队列中就和 for 循环一样,按顺序同步执行。每次取出一个元素,逐个遍历。
2. 并行队列进行异步执行。dispatch_apply可以 在多个线程中同时遍历多个数字。效率一般快于for循环的类串行机制(在for循环中一次处理任务很多时差距比较大)。因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不确定
使用场景:
1. 在拉取网络数据后提前算出各个控件的大小,防止绘制时计算耗时,提高表单滑动流畅性,每个表单的数据没有依赖关系。如果用for循环,耗时较多,用dispatch_apply比较好。
2. 字典数组的快速解析
![](https://img.haomeiwen.com/i14367363/ba900df2979c5b88.png)
GCD 队列组:dispatch_group
需求:分别异步执行2个耗时任务,当2个耗时任务都执行完毕后再回到主线程执行任务。
1.1 使用情况1:调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入group
注意:dispatch_group_async = dispatch_group_enter + dispatch_group_leave;
1.2 使用情况2:dispatch_group_enter表示进入group,dispatch_group_leave表示一个任务执行完毕,离开group。
dispatch_group_enter :一个任务追加到 group,相当于 group 中未执行完毕任务数+1
dispatch_group_leave :一个任务离开了 group,相当于 group 中未执行完毕任务数-1。
当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。
2:等group 中添加的所有任务完成后。调用 dispatch_group_wait 或者 dispatch_group_notify 继续执行其他任务,二者哪种方式都可以实现。
2.1 dispatch_group_notify: 监听 group 中任务的完成状态,当所有的任务都执行完成后,调用 dispatch_group_notify 回到指定线程继续执行任务。
2.2 dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。 暂停当前线程,等待指定的 group 中的任务执行完成后,才会往下继续执行。
GCD 信号量:dispatch_semaphore
Dispatch Semaphore,是持有计数的信号。使用计数来完成这个功能,计数小于 0 时等待,不可通过。计数为 0 或大于 0 时,计数减 1 且不等待,可通过。
Dispatch Semaphore提供了三个函数。
1. dispatch_semaphore_create(long value):创建一个 Semaphore 并初始化信号的总量
注意:传入的参数value 必须大于或等于0,否则会返回NULL。
2. dispatch_semaphore_signal:发送一个信号,让信号总量加 1。返回值为long型。
2.1 :返回值为0:表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。
2.2:返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
3. dispatch_semaphore_wait :返回值为long型。
如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t。不能直接传入整形或float型数,
3.1: 返回值为0,表示在timeout之前,该函数所处的线程被成功唤醒。
3.2: 返回不为0时,表示timeout发生。
3.3:等待期间desema的值被dispatch_semaphore_signal函数加1,且该函数所处线程获得了信号量,继续向下执行并将信号量减1。
3.4: 等待期间没有获取到信号量或者信号量的值一直为0,等到timeout--所处线程自动执行后面的语句。
4: timeout参数
4.1 :DISPATCH_TIME_NOW 表示当前(立刻超时)
4.2:DISPATCH_TIME_FOREVER 表示遥远的未来(永不超时)
使用场景:保持线程同步,将异步执行任务转换为同步执行任务。保证线程安全,为线程加锁。
![](https://img.haomeiwen.com/i14367363/9b1ad304b9b0d666.png)
GCD定时器 dispatch_source_set_timer
1. NSTimer并不准确。GCD定时更为准确。NSTimer是在RunLoop的基础上执行的,GCD 不需要考虑线程切换时涉及到 runloop 的mode问题
2. 停止 Dispatch Timer 有两种方法,一种是使用 dispatch_suspend,另外一种是使用 dispatch_source_cancel。
dispatch_source_cancel 则是真正意义上的取消 Timer。被取消之后如果想再次执行 Timer,只能重新创建新的 Timer。类似于对 NSTimer 执行 invalidate。
dispatch_suspend 并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。
3. GCD定时器默认是暂停的,需要手动启动: dispatch_resume(_timer);
dispatch_suspend 严格上只是把 Timer 暂时挂起,它和 dispatch_resume 是一个平衡调用,两者分别会减少和增加 dispatch 对象的挂起计数。当这个计数大于 0 的时候,Timer 就会执行。在挂起期间,产生的事件会积累起来,等到 resume 的时候会融合为一个事件发送。
4. GCD 定时器可以设置时间偏差值,通过参数leeway 来设置
dispatch_source_set_timer的第四个参数leeway指的是一个期望的容忍时间,将它设置为 1 秒,意味着系统有可能在定时器时间到达的前 1 秒或者后 1 秒才真正触发定时器。在调用时推荐设置一个合理的 leeway 值。需要注意,就算指定 leeway 值为 0,系统也无法保证完全精确的触发时间,只是会尽可能满足这个需求。
5. dispatch_source_set_timer中第二个参数,当我们使用dispatch_time或者DISPATCH_TIME_NOW时,系统会使用默认时钟来进行计时。然而当系统休眠的时候,默认时钟是不走的,也就会导致计时器停止。使用dispatch_walltime可以让计时器按照真实时间间隔进行计时。
参考地址:
iOS 多线程:『GCD』详尽总结 - 简书 详细&每个方法都有列子介绍
GCD 常见面试点 - 简书 怎么用GCD控制创建的线程数量 (semaphore功能)
iOS多线程详解 - 简书 NSOperation的用法
iOS基础:多线程-深入理解GCD - 简书 例子清晰易懂
iOS GCD中级篇 - dispatch_semaphore(信号量)的理解及使用 - 那一抹风情 - 博客园 使用信号量来控制同一时间开辟的线程数量,提高性能。
Dispatch Source Timer 的使用以及注意事项_csdndimo的专栏-CSDN博客_dispatch_source_set_timer GCD定时器dispatch_source_set_timer的详细介绍。
iOS中NSOperation详解 - 简书 NSOperation 的使用详解
网友评论