GCD初相识

GCD初识一

谈到iOS多线程,一般都会谈到四种方式:pthread、NSThread、GCD和NSOperation。其中,苹果推荐也是我们最经常使用的无疑是GCD。所以掌握gcd的用法也是每个ios开发者必备的技能
GCD是一套低层级的C API,通过 GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不需要直接和线程打交道。GCD在后端管理着一个线程池,它不仅决定着你的代码块将在哪个线程被执行,还根据可用的系统资源对这些线程进行管理。
特点:

  1. 快,更快的内存效率,因为线程栈不暂存于应用内存
  2. 稳,提供了自动的和全面的线程池管理机制,稳定而便捷。
  3. 准,提供了直接并且简单的调用接口,使用方便,准确。

队列和任务

学习gcd,肯定会接触到各种词汇,队列和任务

队列

调度队列是一个对象,它会以first-in、first-out的方式管理您提交的任务。GCD有三种队列类型:

  1. 串行队列,让任务一个接着一个地执行(一个任务执行完后,再执行下一个任务),GCD中获得串行有2种途径(使用dispatch_queue_create函数,并指定队列类型DISPATCH_QUEUE_SERIAL,主队列),放在主队列中的任务,都会放到主线程中执行
  2. 并行队列,可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务),并发队列会基于系统负载来合适地选择并发执行这些任务;并发功能只有在异步(dispatch_async)函数下才有效,因为异步函数才具备开启新线程的能力,而同步函数只能在当前线程中执行不具备开启线程的能力;并发队列获取2种途径 (手动创建并发队列,dispatch_queue_create,并指定队列类型DISPATCH_QUEUE_CONCURRENT;GCD默认已经提供了全局的并发队列Global queue)
  3. 主队列,与主线程功能相同;主线程是唯一可用于更新 UI 的线程。提交至main queue的任务会在主线程中执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。

任务

GCD中的任务只是一个代码块,它可以指一个block或者函数指针。根据这个代码块添加进入队列的方式,将任务分为同步任务和异步任务:

  1. 同步任务,使用dispatch_sync将任务加入队列。将同步任务加入串行队列,会顺序执行,一般不这样做并且在一个任务未结束时调起其它同步任务会死锁。将同步任务加入并行队列,会顺序执行,但是也没什么意义。

GCD常见用法及场景展示

1. dispatch_async

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
    // 一个异步的任务,例如网络请求,耗时的文件操作等等
    ...
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI刷新
        ...
    });
});

这种用法非常常见,比如开启一个异步的网络请求,待数据返回后返回主队列刷新UI;又比如请求图片,待图片返回刷新UI等等。

2. dispatch_after

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
    // 在queue里面延迟执行的一段代码
    ...
});

这为我们提供了一个简单的延迟执行的方式,比如在view加载结束延迟执行一个动画等等。

3. dispatch_once

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

可以使用其创建一个单例,也可以做一些其他只执行一次的代码

4. dispatch_group

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
    // 异步任务1
});
dispatch_group_async(group, queue, ^{
    // 异步任务2
});
dispatch_group_notify(group, mainQueue, ^{
    // 任务完成后,在主队列中做一些操作
    ...
});

模拟网络请求的代码:

dispatch_group_enter(group);
[JDApiService getActivityDetailWithActivityId:self.activityId Location:stockAddressId SuccessBlock:^(NSDictionary *userInfo) {
    // 数据返回后一些处理
    ...

    dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
    // 数据返回后一些处理
    ...

    dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[JDApiService getAllCommentWithActivityId:self.activityId PageSize:3 PageNum:self.commentCurrentPage SuccessBlock:^(NSDictionary *userInfo) {
    // 数据返回后一些处理
    ...

    dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
    // 数据返回后一些处理
    ...

    dispatch_group_leave(group);
}];
执行dispatch_group_notify的任务。
dispatch_group_notify(group, mainQueue, ^{
    // 一般为回主队列刷新UI
    ...
});

5. dispatch_barrier_async

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    // 任务1
    ...
});
dispatch_async(queue, ^{
    // 任务2
    ...
});
dispatch_async(queue, ^{
    // 任务3
    ...
});
dispatch_barrier_async(queue, ^{
    // 任务4
    ...
});
dispatch_async(queue, ^{
    // 任务5
    ...
});
dispatch_async(queue, ^{
    // 任务6
    ...
});

6. dispatch_apply

// for循环做一些事情,输出0123456789
for (int i = 0; i < 10; i ++) {
    NSLog(@"%d", i);
}
// dispatch_apply替换(当且仅当处理顺序对处理结果无影响环境),输出顺序不定,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函数说明
 *
 *  @brief  dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
 *         该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
 *
 *  @param 10    指定重复次数  指定10次
 *  @param queue 追加对象的Dispatch Queue
 *  @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
 *
 */
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu", index);
});

那么,dispatch_apply有什么用呢,因为dispatch_apply并行的运行机制,效率一般快于for循环的类串行机制(在for一次循环中的处理任务很多时差距比较大)。比如这可以用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性,如果用for循环,耗时较多,并且每个表单的数据没有依赖关系,所以用dispatch_apply比较好。

7. dispatch_suspend和dispatch_resume

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暂停队列queue
dispatch_resume(queue);  //恢复队列queue

这两个函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复。

8. dispatch_semaphore_signal

// a、同步问题:输出肯定为1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
    // 任务1
    dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
    NSLog(@"1\n");
    dispatch_semaphore_signal(semaphore2);
    dispatch_semaphore_signal(semaphore1);
});
dispatch_async(queue, ^{
    // 任务2
    dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
    NSLog(@"2\n");
    dispatch_semaphore_signal(semaphore3);
    dispatch_semaphore_signal(semaphore2);
});
dispatch_async(queue, ^{
    // 任务3
    dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
    NSLog(@"3\n");
    dispatch_semaphore_signal(semaphore3);
});
// b、有限资源访问问题:for循环看似能创建100个异步任务,实质由于信号限制,最多创建10个异步任务。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
        // 任务
        ...
        dispatch_semaphore_signal(semaphore);
    });
}

主要是有两种用法: 1.解决同步问题 2.解决有限资源访问

内存和安全

  • MRC:用dispatch_retain和dispatch_release管理dispatch_object_t内存。
  • ARC:ARC在编译时刻自动管理dispatch_object_t内存,使用retain和release会报错。
  • dispatch_queue是线程安全的,你可以随意往里面添加任务。

一些经常拿出来说的代码

  1. 在串行队列里的异步任务,任务里再同一个队列里异步任务
    dispatch_queue_t queue = dispatch_queue_create("com.tzjy.-02--GCD", DISPATCH_QUEUE_SERIAL);    dispatch_async(queue, ^{//block1
         NSLog(@"1");
         NSLog(@"1---%@",[NSThread currentThread]);
         dispatch_async(queue, ^{//block2
             NSLog(@"2");
             NSLog(@"2---%@",[NSThread currentThread]);
         });
         sleep(2);
         NSLog(@"3");
         NSLog(@"3---%@",[NSThread currentThread]);
     });
    
    这里执行的结果肯定是:1,3,2 且线程是同一个 因为是串行队列;
  2. 在并发队列里的异步任务,任务里再同一个队列里异步任务
     dispatch_queue_t globalqueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     dispatch_async(queue, ^{//block1
         NSLog(@"1");
         NSLog(@"1---%@",[NSThread currentThread]);
         dispatch_async(queue, ^{//block2
             NSLog(@"2");
             NSLog(@"2---%@",[NSThread currentThread]);
         });
         sleep(2);
         NSLog(@"3");
         NSLog(@"3---%@",[NSThread currentThread]);
     });
    
    这里的执行结果是1,2,3 且会有不同的两个线程,因为是并发队列;
  3. 在串行队列里同,异步任务再同步任务,会发生死锁

     dispatch_queue_t queue = dispatch_queue_create("com.tzjy.-02--GCD", DISPATCH_QUEUE_SERIAL);
     dispatch_async(queue, ^{//block1
         NSLog(@"1");
         dispatch_sync(queue, ^{//block2
             NSLog(@"2");
         });
         sleep(2);
         NSLog(@"3");
    
     });
    

    这里会崩溃掉,因为任务一会等待任务二,而任务二又会等待任务一,这样形成死锁;如果这里的队列不是串行队列,是并行队列,则不会死锁。

    如果使用dispatch_sync,如果执行dispatch_sync 这句的上下文环境的“队列”,跟 dispatch_sync(queue, ^{ 的这个queue 是同一个队列,不用想了绝逼死锁。。。当然还有一个条件,这个队列必须是单行车道,也就是串行队列,如果是并行的,不会有死锁,为什么呢?
    假如queue是个并行队列,当执行到dispatch_sync 的时候,dispatch_sync无需等待当前任务执行完毕,此时它就会执行,但是会阻塞当前线程,只有等它执行完了,下面的任务才会接着去执行了。

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