【iOS】GCD-基础篇

OS X Snow Leopard 和 iOS 开始,apple公司为开发者引入了新的多线程编程功能"Grand Central Dispatch"(超级派发中心,也称大调度中心)。


1. 概要

1.1 什么是GCD

GCD是异步执行任务的一种技术。开发者只需要定义需要执行的任务并将任务提交到适当的Dispatch Queue中GCD就能生成必要的线程并有计划的执行任务。

dispatch_async(queue, ^{
    
    /*
     * 长时间处理
     * 例如:数据库访问
     **/
    
    /*
     * 长时间处理结束,主线程使用该处理结果
     **/
    
    dispatch_async(dispatch_get_main_queue(), ^{
       
        /*
         * 回到主线程进行处理
         * 如:根据数据更新用户界面
         **/
        
    });
    
});

上面的任务是GCD一种简单实现,定义了一个block任务(长时间数据库处理任务),然后将该任务异步提交到队列queue中,GCD根据队列中的任务生成必要的线程,来执行长时间数据库处理任务,并提交新任务到主队列(回到主线程)进行UI刷新工作。

思考:还有没有其他方式从子线程回到主线程?哪种方式好?

1.2 多线程编程

先来看几个关键概念
线程:一条CPU指令连续不阶段的执行流就是线程。
多线程:多核CPU可以并列的执行多条cpu指令流。
原生核CPU:真正意义上的多核CPU,每个核心相互独立,拥有自己的总线。
封装核CPU:简单的封装多个单核CPU,只有一条共用总线。
目前为止,iphone 6s是最新的iOS平台设备,采用的是原生双核A9处理器。

多线程编程存在问题:

  • 1.资源竞争:多个线程更新相同资源导致数据不一致
  • 2.死锁:停止等待事件的线程会导致多个线程相互持续等待
  • 3.内存消耗:使用太多线程会消耗内存
    后面给出以上问题解决方案。

多线程应用场景:
主线程主要用来处理界面刷新,屏幕点击事件等操作,长时间的处理不要放在主线程中执行,应该放在其他线程中执行。

2.GCD 主要 API

2.1 Dispatch Queue

开发者要做的只是在block中定义想要执行的任务并提交到适当的Dispatch Queue中,Dispatch Queue是执行处理的等待队列,负责控制任务进出线程的次序,执行处理时遵循先进先出(FIFO)原则,等待队列分为Serial Dispatch Queue (同步等待队列) 和 Concurrent Dispatch Queue (异步等待队列)。

表 2.1-1 Dispatch Queue种类

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

示例:

dispatch_async(queue, blk0);
dispatch_async(queue, blk1);
dispatch_async(queue, blk2);
dispatch_async(queue, blk3);
dispatch_async(queue, blk4);
dispatch_async(queue, blk5);
dispatch_async(queue, blk6);
dispatch_async(queue, blk7);
  • 当 queue 是 Serial Dispatch Queue 时,执行顺序是:blk0->blk1->blk2->blk3->blk4->blk5->blk6->blk7
  • 当 queue 是 Concurrent Distach Queue 时,执行顺序如下表
    表2.1-2 Concurrent Dispatch Queue 执行示例
线程0 线程1 线程2 线程3
blk0 blk1 blk2 blk3
blk4 blk6 blk5
blk7 .

由上可以发现

  • GCD会为同步等待队列开辟一条线程,同步等待队列里地任务是按照FIFO的原则在该条线程中逐个处理;
  • GCD会为异步等待队列开辟多条线程,异步等待队列里的任务是会并行的在多个线程中进行处理。
    本例中,GCD为异步等待队列开辟了四条线程,所以一次能并发的执行blk0-blk3四条任务,剩余任务会遵循FIFO原则,等待前面的任务执行完毕,有空闲线程,再利用该空闲线程执行。具体的可创建线程数和最大并发线程数有关,属于系统管理。

应用场景:

  • 同步等待队列serial dispatch queue适用于需要按顺序执行的任务;
  • 异步等待队列Concurrent dispatch queue适用于不需要按顺序执行,执行效率高的场景;
2.2 创建Dispatch Queue

获取队列有两种方式,手动创建和直接获取
手动创建
使用 dispatch_queue_create 函数生成

//创建同步等待队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_SERIAL);
//创建异步等待队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);

函数中第一个参数使用反向域名的方式标记队列,可以设为NULL,为了获取更多调试信息建议设置;第二个参数选择使用serial或Concurrent队列。

IMPORTANT
If your app is built with a deployment target of OS X v10.8 and later or iOS v6.0 and later, dispatch queues are typically managed by ARC, so you do not need to retain or release the dispatch queues.
For compatibility with existing code, this behavior is configurable. See Dispatch Queues and Automatic Reference Counting for details.
翻译:如果你的iOS app版本大于6.0,不需要手动管理手动创建的dispatch对象

�直接获取
使用系统标准提供的Dispatch Queue

//主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//系统全局并发队列
dispatch_queue_t golbalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

主队列是serial dispatch queue,系统全局队列是Concurrent dispatch queue,很多时候我们没必要手动创建异步队列,直接使用系统的就够了。

2.3 dispatch_set_target_queue

设置使用dispatch_queue_create函数手动创建生成队列的优先级

//创建一个队列
dispatch_queue_t myqueue = dispatch_queue_create("com.example.myqueue", DISPATCH_QUEUE_CONCURRENT);
//创建目标队列
dispatch_queue_t targetQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//myqueue 使用 targetQueue的优先级策略
dispatch_set_target_queue(myqueue, targetQueue);

应用场景:

  • 需要变更执行优先级的情况
  • 需要防止并行执行的场景,可以将serial queue设为并发队列的参考。
2.4 dispatch_after

定时处理

//设置定时器为3秒
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

//3秒后提交任务到主队列
dispatch_after(time, dispatch_get_main_queue(), ^{
    
    //操作任务
});

注意:3秒后提交不等于3秒后执行,具体执行时间和当前正在处理任务数以及RunLoop周期有关

应用场景:

  • 需要延时提交的情况
2.5 Dispatch Group

分组管理任务,能监测整个分组中的任务执行状态。

//获取队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//创建分组
dispatch_group_t group = dispatch_group_create();

//异步提交block任务到等待队列并分组
dispatch_group_async(group, queue, ^{NSLog(@"blk0");});
dispatch_group_async(group, queue, ^{NSLog(@"blk1");});
dispatch_group_async(group, queue, ^{NSLog(@"blk2");});

/*当上面提交到group中的block全部执行完毕,将完成操作的block提交到queue中等待执行*/
dispatch_group_notify(group, queue, ^{
    //完成操作
    NSLog(@"完成");
});

执行结果:
blk1 -> blk2 -> blk0 ->完成

因为queue是Concurrent queue 所以多个线程并行执行,执行完毕后再执行"完成"

这里还有一个函数值得注意,dispatch_group_wait函数,该函数也可以等待group分组任务完成后做一些特殊操作,会在指定时间内等待分组任务执行完毕,然后进入下一行代码;如果指定时间内分组内任务未执行完毕,该函数返回1,否则返回0.

//一直等待直到完成
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

//等待3秒时间
long result = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC));
if (result == 0) {
    
    /*
     * group分组内的任务全都执行完毕
     */
} else {

    /*
     * group分组内某个任务还在执行中
     */
}

应用场景:

  • 在提交到队列中的所有任务执行完毕后做结束处理
  • 提交任务到主队列,将主队列和一个group关联,可以监测任务是否都执行完毕,不耗费多余等待时间。

dispatch_group_enter
指示一个block任务已经进入group状态
dispatch_group_leave
指示一个block任务执行完毕,可以退出组管理
以上两个方法,类似于任务计数器,协同group管理自己的任务,当没有任务,即任务计数为0时调用dispatch_group_notify函数

2.6 dispatch_barrier_async

这里申明一个概念**dispatch_async **和 dispatch_sync区别

  • dispatch_sync:提交任务到队列,并等待block中任务执行完毕进入下一步
  • dispatch_async:提交任务到队列,不等待block中任务执行完毕即可进入下一步。
dispatch_async(queue, ^{NSLog(@"blk0_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk1_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk2_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk3_for_reading");});

dispatch_barrier_async(queue, ^{
    //blk4 writing任务
   //添加blk4到队列中,blk4前面提交的block任务执行完毕后,blk4后面提教的block任务才能执行
});
dispatch_barrier_sync(queue, ^{
    //blk4 writing任务
    
    /*如果使用这个函数
     添加blk4到队列中,并等待blk4前面的任务执行完毕,执行blk4,然后进入下一步继续提交任务。
     
     */
    
});

dispatch_async(queue, ^{NSLog(@"blk5_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk6_for_reading");});
dispatch_async(queue, ^{NSLog(@"blk7_for_reading");});

上面的代码中,dispatch_barrier_async 和 dispatch_barrier_sync都起到了隔离blk0-blk3 和 blk5-blk6执行顺序功能,不同的是dispatch_barrier_async这一步代码不会阻塞,会继续向下执行提交操作,dispatch_barrier_sync会阻塞代码,停止提交,等待执行完毕载提交。

2.7 dispatch_apply

该函数按指定的次数将指定的Block提交到指定的dispatch queue中,并等待全部执行完毕。当queue是Concurrent dispatch queue时,所有提交的block可以异步的执行,但是要注意安全操作问题。

dispatch_apply(10, queue, ^(size_t index) {
   
    NSLog(@"%zu", index);
    
});

可以放入一个全局队列,异步执行。

2.8 dispatch_suspend/dispatch_resume

暂停队列 和 恢复队列

dispatch_suspend(queue);//暂停
dispatch_resume(queue);//恢复
2.9 dispatch semaphore

出现数据竞争的时候,可以进行排他控制,使用信号机制,0代表等待返回,阻塞代码,直到信号>=1
生成计数信号
dispatch_semaphore_t semphore = dispatch_semphore_create(1);

使用wait函数判断计数信号数值是否>=1,是则-1并返回;否则阻塞代码等待,直到某处将信号值设置为>=1.
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)

将计数值+1
dispatch_semaphore_signal(semaphore)

应用场景:

  • 资源竞争,控制先后顺序
3.0 dispatch_once

保证某个操作只执行一次

static dispatch_once_t pred;
dispatch_once(&pred,^{
//do something
  
});
3.1 dispatch I/O

使用多个线程将数据分段更快的读取数据。

现在我们回到一开始提出三个使用多线程的问题,资源竞争、死锁、内存消耗

参考:
GCD官方文档
《Objective-c 高级编程》

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

推荐阅读更多精彩内容

  • iOS中GCD的使用小结 作者dullgrass 2015.11.20 09:41*字数 4996阅读 20199...
    DanDanC阅读 820评论 0 0
  • 本篇博客共分以下几个模块来介绍GCD的相关内容: 多线程相关概念 多线程编程技术的优缺点比较? GCD中的三种队列...
    有梦想的老伯伯阅读 1,018评论 0 4
  • 1. GCD简介 什么是GCD呢?我们先来看看百度百科的解释简单了解下概念 引自百度百科:Grand Centra...
    千寻_544f阅读 362评论 0 0
  • 标签(空格分隔): java 方法一:split() 方法——JDK 1.4 or later## 例子: 分隔符...
    背影杀手不太冷阅读 13,095评论 1 3
  • 满街都是新鞋, 我是多么寒伧。 缠着妈妈一路哭闹 直到突然看见, 一位失脚的病人。
    云中守望阅读 206评论 0 4