多线程之GCD

什么是 GCD

GCD 是 libdispatch 的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。它具有以下优点:

  • GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
  • GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
  • GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。
术语

首先,我们先来了解一下在 iOS 并发编程中非常重要的三个术语,这是我们理解 iOS 并发编程的基础:

  • 进程(process),指的是一个正在运行中的可执行文件。每一个进程都拥有独立的虚拟内存空间和系统资源,包括端口权限等,且至少包含一个主线程和任意数量的辅助线程。另外,当一个进程的主线程退出时,这个进程就结束了;
  • 线程(thread),指的是一个独立的代码执行路径,也就是说线程是代码执行路径的最小分支。在 iOS 中,线程的底层实现是基于 POSIX threads API 的,也就是我们常说的 pthreads ;
  • 任务(task),指的是我们需要执行的工作,是一个抽象的概念,用通俗的话说,就是一段代码。
串行 vs. 并发

从本质上来说,串行和并发的主要区别在于允许同时执行的任务数量。串行,指的是一次只能执行一个任务,必须等一个任务执行完成后才能执行下一个任务;并发,则指的是允许多个任务同时执行。

同步 vs. 异步

同样的,同步和异步操作的主要区别在于是否等待操作执行完成,亦即是否阻塞当前线程。同步操作会等待操作执行完成后再继续执行接下来的代码,而异步操作则恰好相反,它会在调用后立即返回,不会等待操作的执行结果。

队列 vs. 线程

在 iOS 中,有两种不同类型的队列,分别是串行队列和并发队列。正如我们上面所说的,串行队列一次只能执行一个任务,而并发队列则可以允许多个任务同时执行。iOS 系统就是使用这些队列来进行任务调度的,它会根据调度任务的需要和系统当前的负载情况动态地创建和销毁线程,而不需要我们手动地管理。

Operation Queues vs. Grand Central Dispatch (GCD)

简单来说,GCD 是苹果基于 C 语言开发的,一个用于多核编程的解决方案,主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。而 Operation Queues 则是一个建立在 GCD 的基础之上的,面向对象的解决方案。它使用起来比 GCD 更加灵活,功能也更加强大。下面简单地介绍了 Operation Queues 和 GCD 各自的使用场景:

  • Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等;

  • GCD :则是一种更轻量级的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手。

GCD和RunLoop的关系

在每个RunLoop周期醒来之后,会获取GCD在Main queue上的任务并执行,2者协同配合。

dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"我会在RunLoop醒来之后被RunLoop获取并且执行");
    });

最简单的一个demo
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
       ///block块 
 });

GCD使用方式可以简化成这个格式:

 dispatch_async(queue,block);
 queue:是代码执行的队列
 block:交给GCD处理的block

dispatch_queu_t (队列)

队列中分为2个种队列:Serial(串行)Concurrent(并行)

  • Serial:同时只执行一个任务。
  • Concurrent:可以并发的执行多个任务,但执行完成顺序是随机的。

系统提供6个全局并发队列,分别是一个主队列,和5个优先级不同的调度队列,除主线程队列是串行,其余都是并行队列,优先级越高,越优先执行

  • Main queue:主线程队列,在此队列执行将会在主线程调用,串行.
  • 优先级为QOS_CLASS_USER_INTERACTIVE的调度队列:user interactive等级表示任务需要被立即执行提供好的体验。
  • 优先级为QOS_CLASS_USER_INITIATED的调度队列:user initiated等级表示任务由UI发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
  • 优先级为QOS_CLASS_USER_DEFAULT的调度队列:默认优先级。
  • 优先级为QOS_CLASS_USER_UTILITY的调度队列:utility等级表示需要长时间运行的任务。
  • 优先级为QOS_CLASS_USER_BACKGROUND的调度队列:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。

获取不同级别队列的代码

    ///获取主线程队列
    dispatch_get_main_queue();
    ///QOS_CLASS_USER_INTERACTIVE 队列
    dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    ///QOS_CLASS_USER_INITIATED 队列
    dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
    ///QOS_CLASS_DEFAULT 队列
    dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    ///QOS_CLASS_UTILITY 队列
    dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
    ///QOS_CLASS_UTILITY 队列
    dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
    第二个参数总是0,预留选项

当然你也可以用DISPATCH_QUEUE_PRIORITY枚举来获取不同优先级的队列
映射关系:

 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND
创建自己的队列

当系统队列不满足需求的时候我们也可以自己创建队列。

    ///创建串行队列,名字叫com.SERIAL
    dispatch_queue_create("com.SERIAL", DISPATCH_QUEUE_SERIAL);
    ///创建并行队列,名字叫com.CONCURRENT
    dispatch_queue_create("com.CONCURRENT", DISPATCH_QUEUE_CONCURRENT);

也可以为自定义队列设置优先级

方法一:

    ///创建一个队列的配置,分别是串行队列,优先级为比QOS_CLASS_DEFAULT少1的优先级
    dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1);
    dispatch_queue_t queue = dispatch_queue_create("com.custom.queue_attr", attr);

注意:dispatch_queue_attr_make_with_qos_class最后一个参数必须是小于等于0的值,如果传给大于0,或者小于QOS_MIN_RELATIVE_PRIORITY的值,将会返回NULL,附文档上的说明

A relative priority within the QOS class. This value is a negative
offset from the maximum supported scheduler priority for the given class.
Passing a value greater than zero or less than QOS_MIN_RELATIVE_PRIORITY
results in NULL being returned.

方法二:

利用参考队列来设置优先级

  ///创建一个需要被设置优先级的队列
    dispatch_queue_t queue = dispatch_queue_create("com.target",NULL);
    ///取一个参考优先级的队列
    dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    ///将参考的优先级赋值于队列
    dispatch_set_target_queue(queue, referQueue);

async和sync(异步调用和同步调用)
///异步调用
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
///同步调用
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  • queue参数是block执行所在的队列,串行队列或者并行队列
  • block参数是执行的block块

异步调用会立即返回,而同步调用会阻塞当前线程,等待block执行完毕才会继续。

一些简单的demo

  dispatch_queue_t queueConcurrent = dispatch_queue_create("DISPATCH_QUEUE_CONCURRENT", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queueConcurrent, ^{
       ///todo
    });
    
    dispatch_async(queueConcurrent, ^{
      ///todo
    });

注意串行队列的死锁问题:由于dispatch_sync需要等待block被执行,这就非常容易发生死锁。如果一个串行队列,使用dispatch_sync提交block到自己队列中,就会发生死锁,如下代码

 dispatch_queue_t queueSerial = dispatch_queue_create("DISPATCH_QUEUE_SERIAL", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queueSerial, ^{
            dispatch_sync(queueSerial, ^{
                ///发生死锁
                NSLog(@"永远执行不到");
            });
        });

解释:dispatch_sync需要等待block执行完成,同时由于队列串行,block的执行需要等待前面的任务,也就是dispatch_sync执行完成。两者互相等待,永远也不会执行完成,死锁就这样发生了。


dispatch_once(只执行一次)

在APP生命周期之中只执行一次,使用实现单列的场景

static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        ///只执行一次
    });

dispatch_after(延后提交Block)

延后一段时间来提交block代码,是提交不是执行,只是在一段时间之后来提交这个block给GCD,而不能保证立即执行,具体看queue是否繁忙,使用于延后处理的事务,如弹出好评框。

///推迟1秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        ///推迟提交的block
    });

dispatch_apply(迭代)

把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用。

适用场景,遍历一个数组对内部元素做迭代。

 ///遍历10次
    dispatch_apply(10, dispatch_get_main_queue(), ^(size_t index) {
        ///遍历执行的代码
    });

dispatch_barrier_async(执行的时候独占队列)

使用dispatch_barrier将任务加入到并行队列之后,任务会在前面任务全部执行完成之后执行,任务执行过程中,其他任务无法执行,直到barrier任务执行完

最典型的使用场景是多线程读写问题

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-pre-1");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-pre-2");
    });

    dispatch_barrier_async(concurrentQueue, ^(){
        NSLog(@"dispatch-barrier");
    });
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-1");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-2");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-3");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-4");
    });
输出
2016-03-05 02:32:02.867 GCDDemo[2422:1915594] dispatch-pre-2
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-pre-1
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-barrier
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-1
2016-03-05 02:32:02.867 GCDDemo[2422:1915594] dispatch-2
2016-03-05 02:32:02.867 GCDDemo[2422:1915599] dispatch-3
2016-03-05 02:32:02.867 GCDDemo[2422:1915614] dispatch-4

Dispatch_groups(监控多个异步任务)

dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。

当group里所有事件都完成 API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。

适用场景:多个网络请求同时发出,全部完成回调处理

 dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
   
    dispatch_group_async(group, concurrentQueue, ^{
        NSLog(@"1");
        sleep(1);
    });
    dispatch_group_async(group, concurrentQueue, ^{
        NSLog(@"2");
        sleep(2);

    });
    
    //阻塞
   // dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
   //异步
    dispatch_group_notify(group, concurrentQueue, ^{
        NSLog(@"所有任务完成");
    });

dispatch_time_t (GCD定时器)

通过dispatch_time_t,我们可以用GCD做定时器

执行一次

  ///执行一次(利用dispatch_after)
    dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
    dispatch_after(timer, dispatch_get_main_queue(), ^(void){
        //执行事件
    });

重复执行

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

推荐阅读更多精彩内容

  • GCD介绍 Grand Central Dispatch (GCD) 是 Apple 开发的一个多核编程的解决方法...
    巫师学徒阅读 250评论 0 0
  • GCD (Grand Central Dispatch) :iOS4 开始引入,使用更加方便,程序员只需要将任务添...
    池鹏程阅读 1,321评论 0 2
  • 多线程 在iOS开发中为提高程序的运行效率会将比较耗时的操作放在子线程中执行,iOS系统进程默认启动一个主线程,用...
    郭豪豪阅读 2,586评论 0 4
  • 1. GCD简介 什么是GCD呢?我们先来看看百度百科的解释简单了解下概念 引自百度百科:Grand Centra...
    千寻_544f阅读 359评论 0 0
  • 一 婧娇喜欢朋友们叫她青椒,比较符合她的暴脾气,不像她的名字,太女性化,太烦。 青椒就是欢欢笑笑,嬉笑怒骂皆在脸上...
    柠檬草微青cc阅读 232评论 0 0