GCD了解和日常使用

进程:也就是一个正在运行的应用程序。

线程:进程中的某一条完整的执行路径。一个进程可以有多个线程,至少有一个线程,即主线程。在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. 异步函数会立即返回,预定的任务会完成,但不会等它完成。任务可以先绕过不执行,回头再来执行。因此,一个异步函数不会阻塞当前线程去执行下一个函数。

并发与并行

单核设备可以通过快速的切换上下文,从一个线程,切换到另一个线程进行执行。 并发是逻辑上的同时发生,指一个处理器同时处理多个任务。多个事件在同一时间交替发生。

并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。是物理上的同时发生,真的可以实现。多核设备通过并行来同时执行多个线程;

并发和并行


GCD公开有5个不同的队列(Dispatch Queue)

1. 运行在主线程中的main queue(主队列)

2. 3个不同优先级的后台队列(优先级队列)

3. 以及一个优先级更低的后台队列(用于I/O)。

队列分为2种:串行队列并行队列

两者的主要区别是:执行顺序不同,以及开启线程数不同。

串行队列(Serial Dispatch Queue):

每次只有一个任务被执行。让任务一个接着一个地执行。

并行队列(Concurrent Dispatch Queue)

可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

注意:并发队列的并发功能只有在异步(dispatch_async)函数下才有效

5种类型队列


队列的创建方法/获取方法

可以使用dispatch_queue_create来创建队列,需要传入两个参数,第一个参数表示队列的唯一标识符,用于 DEBUG,可为空,Dispatch Queue 的名称推荐使用应用程序 ID 这种逆序全程域名;第二个参数用来识别是串行队列还是并发队列。

1. DISPATCH_QUEUE_SERIAL表示串行队列

2. DISPATCH_QUEUE_CONCURRENT表示并发队列。

队列的创建方式

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。

任务的创建方式

1. 同步执行 + 并发队列:所有任务都是在当前线程中执行的,没有开启新的线程

任务按顺序执行的。虽然并发队列可以开启多个线程,并且同时执行多个任务。但是因为本身不能创建新线程,只有当前线程这一个线程(同步任务不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后,才能继续接着执行下面的操作(同步执行后续任务会等待--当前执行的任务结束之后才开启)。所以任务只能一个接一个按顺序执行,不能同时被执行。

2. 异步执行 + 并发队列

除了当前线程(主线程),系统开启了新的线程,并且任务是交替/同时执行的。当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(异步执行不做等待,可以继续执行任务)。

3. 异步执行 + 串行队列

开启了一条新线程,异步执行不做任何等待,可以继续执行任务。任务是按顺序执行的、

 4. 同步执行 + 主队列会出现死锁

队列+执行


dispatch_barrier使用场景

GCD 栅栏方法:dispatch_barrier 

只对通过  dispatch_queue_create()  创建出来的  DISPATCH_QUEUE_CONCURRENT   并发队列有效。

如果是其它的队列,比如全局队列(global queue)或者 不是通过dispatch_queue_create()   创建出来的  DISPATCH_QUEUE_CONCURRENT并发队列,

dispatch_barrier_async / dispatch_barrier_sync   就会变成跟dispatch_async / dispatch_sync一样,失去其特殊性,dispatch_barrier_async没有“栅栏”的作用了。


有时需要异步执行两组操作,第一组作执行完之后,才能执行第二组操作。需要一个相当于栅栏一样的方法将两组异步执行的操作组给隔离开,每一个操作组里可以包含一个或多个任务。用 dispatch_barrier_async 方法在两个操作组间形成栅栏。

dispatch_barrier 函数相当于一个停顿,在这个函数之前加入队列的任务先执行,执行完毕后会执行栅栏函数中的任务(栅栏中的任务有互斥性),栅栏中任务执行结束后 --再执行栅栏函数之后的任务。(队列内的任务会等待,线程内 未加入队列的任务也要等待)

注意:

dispatch_barrier_sync :在当前线程同步执行,需要等待自己的任务(barrier 中的任务)结束之后,才会继续添加并执行写在barrier后面的任务。会阻塞当前线程。

dispatch_barrier_async: 在线程中执行,将自己的任务(barrier中的任务)插入到队列之后,不会等待自己的任务结束,会继续执行线程中后面的任务。不会阻塞线程,但是栅栏后加入队列的任务会等着栅栏内加入的任务执行完之后再执行。(队列内的任务会等待,但是线程内 未加入队列的任务不等待)


GCD 延时执行方法:dispatch_after:在指定的延迟时间点才将任务追加到主队列中

需求:在指定时间(例如3秒)之后执行某个任务。

需要注意的是:dispatch_after 函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。这个时间并不是绝对准确的,要大致延迟执行任务,dispatch_after函数是很有效的。时间准确性受 Dispatch Queue内是否有 大量需要处理追加的block 任务 或 当前线程某个正在处理的任务本身有延迟等。 

dispatch_after


GCD 一次性代码(只执行一次):dispatch_once

创建单例、或者在整个程序运行过程中只执行一次的代码时,可用 GCD 的dispatch_once函数

dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,在多线程下,可以保证线程安全。

onceToken ,未执行时 是0 ,执行中是一个其他值,其他进来的线程都会被阻塞( 类似于信号量的控制 )。执行结束,值变为0。 其他进来的线程会直接返回,不再处理。

dispatch_once


GCD 快速遍历方法:dispatch_apply

dispatch_apply按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束后返回

1. 串行队列中就和 for 循环一样,按顺序同步执行。每次取出一个元素,逐个遍历。

2. 并行队列进行异步执行。dispatch_apply可以 在多个线程中同时遍历多个数字。效率一般快于for循环的类串行机制(在for循环中一次处理任务很多时差距比较大)。因为是在并发队列中异步执行任务,所以各个任务的执行时间长短不定,最后结束顺序也不确定

使用场景:

1. 在拉取网络数据后提前算出各个控件的大小高度,防止绘制时计算耗时,提高表单滑动流畅性,每个表单的数据没有依赖关系。如果用for循环,耗时较多,用dispatch_apply比较好。

2. 字典数组的快速解析

dispatch_apply



GCD 队列组:dispatch_group

需求:分别异步执行2个耗时任务,当2个耗时任务都执行完毕后再回到主线程执行任务。

1.1 使用情况1:调用队列组的 dispatch_group_async 先把任务放到队列中,然后将队列放入group 

注意:dispatch_group_async = dispatch_group_enter +  dispatch_group_leave + dispatch_async;

1.2 使用情况2:dispatch_group_enter表示进入group,dispatch_group_leave表示一个任务执行完毕,离开group。

dispatch_group_enter :一个任务追加到 group,相当于 group 中未执行完毕任务数+1

dispatch_group_leave :一个任务离开了 group,相当于 group 中未执行完毕任务数-1。

dispatch_group_enter ,dispatch_group_leave 和  dispatch_async 配合使用。 

当 group 中未执行完毕任务数为0的时候,才会使dispatch_group_wait解除阻塞,以及执行追加到dispatch_group_notify中的任务。

2:等group 中添加的所有任务完成后再执行其他操作:调用  dispatch_group_wait 或者 dispatch_group_notify 继续执行其他任务,二者哪种方式都可以实现。

2.1  dispatch_group_notify: 监听 group 中任务的完成状态

当group 所有的任务都执行完成后,调用 dispatch_group_notify--block 中的任务。( 去指定队列执行block任务)。

不会阻塞当前线程,线程内的其他未加入group 的任务会被执行(执行可能会早于放入group 中的任务)。

2.2  dispatch_group_wait 回到当前线程继续向下执行(会阻塞当前线程)。 暂停当前线程,等待指定的 group 中的任务执行完成后,dispatch_group_wait 后的代码 才会往下继续执行。


GCD 信号量:dispatch_semaphore   可用于控制线程的并发量

Dispatch Semaphore,是持有计数的信号。计数 <=0 时等待,不可通过。计数 >= 1 时,通过。 

Dispatch Semaphore提供了三个函数。

1.  dispatch_semaphore_create(long value):创建一个 Semaphore 并初始化信号的总量

 注意:传入的参数value 必须大于或等于1,否则会返回NULL。

2. dispatch_semaphore_signal:发送一个信号,让信号总量加 1。返回值为long型。

2.1  :返回值为0:表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。

2.2:返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。

3. dispatch_semaphore_wait :返回值为long型。

如果dsema信号量的值大于1,该函数所处线程就继续执行下面的语句,并且将信号量的值减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  持续等待

使用场景:保持线程同步,将异步执行任务转换为同步执行任务。保证线程安全,为线程加锁。

dispatch_semaphore使用


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 - 知乎

iOS-GCD使用详解 - Allence - 博客园

iOS - GCD - 简书

iOS GCD中级篇 - dispatch_semaphore(信号量)的理解及使用 - 那一抹风情 - 博客园  使用信号量来控制同一时间开辟的线程数量,提高性能。

Dispatch Source Timer 的使用以及注意事项_csdndimo的专栏-CSDN博客_dispatch_source_set_timer  GCD定时器dispatch_source_set_timer的详细介绍。

iOS中NSOperation详解 - 简书 NSOperation 的使用详解

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,122评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,070评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,491评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,636评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,676评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,541评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,292评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,211评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,655评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,846评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,965评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,684评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,295评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,894评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,012评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,126评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,914评论 2 355

推荐阅读更多精彩内容