GCD学习

Objective-C高级编程-GCD部分

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。

GCD的简单使用

dispatch_async(queue, ^{
    
    /*
     * 想在后台处理的事务,如数据库访问,大量的计算等
     */
     
     dispatch_async(dispatch_get_main_queue(), ^{
         
         /*
          * 在主线程执行的事务,如界面更新
          */
     });
});

这里dispatch_async的作用就是讲开发者定义的想执行的任务追加到适当的Dispatch Queue中。

dispatch_async(queue, ^{
    //想执行的任务
});

其中需要传入两个参数,第一个是Dispatch Queue,第二个是Block变量。

这里dispatch_async将任务异步的追加到Dispatch Queue中,Dispatch Queue会按照FIFO的顺序执行处理。

这里Dispatch Queue有两种

Dispatch Queue的种类 说明
Serial Dispatch Queue 等待现在执行中处理结束
Concurrent Dispatch Queue 不等待现在执行中处理结束

简单来讲如果使用Serial Dispatch Queue来处理事务,事务的处理顺序会按照加入的顺序执行。

如果使用Concurrent Dispatch Queue来处理事务,则后面添加的事务不会等待前面的事务执行完成后才执行,也就是不保证事务的处理顺序是加入的顺序。加入的事务是并行处理,但并行处理的处理数量取决于当前系统的状态,即iOS和OS X基于Dispatch Queue中的处理数、CPU核数以及CPU负荷等当前系统的状态来决定Concurrent Dispatch Queue中并行执行的处理数。

获取Dispatch_Queue

1. dispatch_queue_create函数

dispatch_queue_t queue = dispatch_queue_create("MySerialDispatchQueue", NULL);

第一个参数是名称,可以方便调试。

第二个参数可以指定获取的是Serial Dispatch Queue还是Concurrent Dispatch Queue,参数为DISPATCH_QUEUE_CONCURRENT或者DISPATCH_QUEUE_SERIAL

通过dispatch_queue_create创建的Dispatch Queue在使用结束后必须通过dispatch_release函数释放。这里对应的还有dispatch_retain函数,即Dispatch Queue也像Objective-C的引用计数式内存管理一样,需要通过dispatch_retain函数和dispatch_release函数的引用计数来管理内存。

使用时可以在使用完dispatch_async函数之后立即使用dispatch_release函数进行释放,因为实际上传入dispatch_async函数的Block语法强持有了queue对象,所以不用担心在调用了dispatch_release函数后导致queue被废弃。

2. Main Dispatch Queue/Global Dispatch Queue

Main Dispatch Queue获取主线程执行的Dispatch Queue。

Global Dispatch Queue获取所有应用程序都能够使用的Concurrent Dispatch Queue,它有4个优先级,分别是高、默认、低和后台,执行优先级只是大致的判断,并不能保证实时性。

名称 Dispatch Queue的种类 说明
Main Dispatch Queue Serial Dispatch Queue 主线程执行
Global Dispatch Queue(High Priority) Concurrent Dispatch Queue 执行优先级:高
Global Dispatch Queue(Default Priority) Concurrent Dispatch Queue 执行优先级:默认
Global Dispatch Queue(Low Priority) Concurrent Dispatch Queue 执行优先级:低
Global Dispatch Queue(Background Priority) Concurrent Dispatch Queue 执行优先级:后台

各种Dispatch Queue获取方法如下:

//Main Dispatch Queue
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

//Global Dispatch Queue High Priority
dispatch_queue_t globalDispatchQueueHight = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

这里其他几个优先级对应的参数为DISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND

关于GCD中注意的问题

数据竞争

如多个线程要修改同一个变量或者文件,最好将这些操作放在同一个Serial Dispatch Queue来执行,但比如读取操作就可以并行执行。

可能会用到的API
1. Serial Dispatch Queue

使用该queue可以有效避免数据竞争问题。

2. Dispatch Group

该API会将追加到Dispatch Group中的多个处理全部执行完成后执行结束处理。

3. dispatch_barrier_async函数

在访问数据库或文件时,想在多个读取操作后,进行一次写入操作,然后再执行多个读取操作,可以使用该函数,简化代码的逻辑。

//获取一个Concurrent Dispatch Queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//并行执行多个读操作
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
//这一步会等待上面的所有并行任务执行完毕才会执行
dispatch_barrier_async(queue, blk_for_writing);
//写操作执行完毕后,继续并行执行剩下的读操作
dispatch_async(queue, blk4_for_reading);
dispatch_async(queue, blk5_for_reading);
dispatch_async(queue, blk6_for_reading);
dispatch_async(queue, blk7_for_reading);

死锁

使用dispatch_sync可能会导致死锁,如下代码是在主线程中运行的话就会导致死锁:

//在主线程中获取主线程的Dispatch Queue
dispatch_queue_t queue = disaptch_get_main_queue();
//向main dispatch queue中同步添加一个Block
dispatch_sync(queue, ^{
    NSLog(@"Heollo?");
});

这段代码导致死锁的原因是,dispatch_sync是同步追加的函数,意味着进入该函数后,需要将Block代码添加到Queue中并且执行完成后才会返回,而该函数没有返回之前,主线程就会等待在这一步,并不会执行加入到Main Dispatch Queue中的Block语句,最终导致死锁的产生。

不要使用太多线程

太多的线程会导致消耗大量的内存,在使用过程中,需要结合使用环境,当前需要处理的逻辑等来决定多线程的使用,不要滥用。

GCD的实现

这部分书中着墨不多,主要包括两个部分,一个是常用的Dispatch Queue,另一个是不太引人瞩目的Dispatch Source。

Dispatch Queue

我们可以想象,GCD的实现最基本的需要以下这些工具

  • 用于管理追加的Block的C语言层实现的FIFO队列
  • Atomic函数中实现的用于排他控制的轻量级信号
  • 用于管理线程的C语言层实现的一些容器

而苹果的官方说明中提到

通常,应用程序中编写的线程管理用的代码要在系统级实现。

实质上,GCD的实现是在iOS和OS X的核心XNU内核级上实现的。编程人员无论如何努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。

以下软件组件用于实现Dispatch Queue

组件名称 提供技术
libdispatch Dispatch Queue
Libc(pthreads) pthread_workqueue
XNU内核 workqueue

其中,我们使用GCD时使用到的API全部为包含在libdispatch库中的C语言函数。

libdispatch部分

Dispatch Queue通过结构体和链表,被实现为FIFO队列,这个队列管理的是使用dispatch_async等函数时传入的Block。该Block首先被包装成disptch_continuation_t类型的结构体中,该结构体存储Block以及Block所属的Dispatch Group和一些其他的信息(通常说的执行上下文)。

Dispatch Queue可通过dispatch_set_target_queue函数来设定,可以将多个Dispatch Queue连接起来,让本来并行执行的任务变为串行执行。但连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Seiral Dispatch Queue的各种优先级的Global Dispatch Queue。

其中有8种Global Dispatch Queue:

  • Global Dispatch Queue(High Priority)
  • Global Dispatch Queue(Default Priority)
  • Global Dispatch Queue(Low Priority)
  • Global Dispatch Queue(Background Priority)
  • Global Dispatch Queue(High Overcommit Priority)
  • Global Dispatch Queue(Default Overcommit Priority)
  • Global Dispatch Queue(Low Overcommit Priority)
  • Global Dispatch Queue(Background Overcommit Priority)

其中附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue,表示不管系统状态如何,都会强制生成线程的Dispatch Queue。

pthreads部分

上面8中Global Dispatch Queue各使用1个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_workqueue

pthread_workqueue包含在Libc提供的pthreads API中。其使用bsdthread_registerworkq_open系统函数,在初始化XNU内核的workqueue之后获取workqueue信息。

XNU部分

XNU内核持有4中workqueue。

  • WORKQUEUE_HIGH_PRIOQUEUE
  • WORKQUEUE_DEFAULT_PRIOQUEUE
  • WORKQUEUE_LOW_PRIOQUEUE
  • WORKQUEUE_BG_PRIOQUEUE

这4个优先级与Global Dispatch Queue的4中执行优先级相同。

Dispatch Queue执行Bloack的过程
  1. libdispatch从Global Dispatch Queue自身的FIFO队列中取出Dispatch Continuation,调用pthread_workqueue_additem_np函数。将该Global Dispatch Queue自身、符合其优先级的workqueue信息以及为执行Dispatch Continuation的回调函数等传递给参数。
GCD_DispatchQueue执行Block过程.png

XNU内核基于系统状态判断是否要生成线程。如果是Overcommit优先级的则始终生成线程。

workqueue生成的线程在实现用于workqueue的线程计划表中运行,所以与一般线程的上下文切换不同。这里也隐藏着使用GCD的原因。该线程虽然与iOS和OS X中通常使用的线程大致相同,但是有有一部分pthread API不能使用。可参考官方文档《并列编程指南》的”与POSIX线程的互换性“一节。

(翻译真的很烂,猜测是这样的线程生成效率高)

回调

workqueue的线程执行pthread_workqueue函数,该函数调用libdispatch的回调函数,在该回调函数中执行加入到Dispatch Continuation的Block。

Block执行结束后,进行通知Dispatch Group结束、释放Dispatch Continuation等处理,开始准备执行加入到Global Dispath Queue中的下一个Block。

小结

XNU内核系统级的实现是GCD高性能的最大的原因,由编程人员管理的线程中,想发挥出匹敌GCD的性能是不可能的。

总结

多线程编程是一种易发生各种问题的编程技术,虽然回避这些问题有许多方法,且程序都偏于复杂,但使用多线程编程的好处显而易见,保证应用程序的响应性能,而GCD大大简化了偏于复杂的多线程编程的源代码。

参考

《Objective-C高级编程 iOS与OS X多线程和内存管理》

并列编程指南

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

推荐阅读更多精彩内容

  • 文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。通过本文,您将了解到: 1. GCD 简介 2. G...
    晓_我想去环游世界阅读 1,142评论 2 8
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,371评论 8 265
  • 1.NSTimer不准时的原因:(1).RunLoop循环处理时间,每次循环是固定时间,只有在这段时间才会去查看N...
    稻春阅读 1,240评论 0 3
  • 起床的时候已经十一点多了,不能再这样了。 跟LJ聊了聊写作。我已经很久没有写东西了,除了要交作业的时候拼凑一些论文...
    Leezf阅读 105评论 0 1
  • 此刻,时间是散落在长椅上的树影 我想在这静谧的时光中睡去 好好歇歇 醒来以后 还有两天的时间回家 我一定精神抖擞 ...
    知了猴姑娘阅读 199评论 0 0