iOS多线程之GCD

iOS多线程之GCD

什么是GCD

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

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

串行是指同一时间只有一个任务执行,并发是指同一时间可以执行多个任务。

2. Synchronous vs. Asynchronous 同步 vs. 异步

同步是指一个函数等函数体执行完了才返回,异步是指函数立即返回(此时函数体还没有执行完成)。

3. Critical Section 临界区

就是一段代码不能被并发执行,也就是,两个线程不能同时执行这段代码。

4. Race Condition 竞态条件

程序的输出结果依赖于不同事件的执行顺序。

5. Deadlock 死锁

两个(或多个)进程(或线程)都卡住了,等待对方完成或执行某些操作。第一个不能完成是因为它在等待第二个的完成。但第二个也不能完成,因为它在等待第一个的完成。

6. Thread Safe 线程安全

线程安全的代码能在多线程或并发任务中被安全的调用,而不会导致任何问题(数据损坏,崩溃,等)。线程不安全的代码在某个时刻只能在一个上下文中运行。一个线程安全代码的例子是 NSDictionary 。你可以在同一时间在多个线程中使用它而不会有问题。另一方面,NSMutableDictionary 就不是线程安全的,应该保证一次只能有一个线程访问它。

7. Context Switch 上下文切换

一个上下文切换指当你在单个进程里切换执行不同的线程时存储与恢复执行状态的过程。这个过程在编写多任务应用时很普遍,但会带来一些额外的开销。

8. Concurrency vs Parallelism 并发与并行

并发是指在同一个时间段内可以有多个任务执行,但是同一时刻只能有一个任务执行。并行是指即使在同一时刻也有多个任务执行。

9. Queues 队列

GCD 提供有 dispatch queues 来处理代码块,这些队列管理你提供给 GCD 的任务并用 FIFO 顺序执行这些任务。这就保证了第一个被添加到队列里的任务会是队列中第一个开始的任务,而第二个被添加的任务将第二个开始,如此直到队列的终点。

10. Serial Queues 串行队列

串行队列中的任务一次执行一个,每个任务只在前一个任务完成时才开始。而且,你不知道在一个 Block 结束和下一个开始之间的时间长度,如图



这些任务的执行时机受到 GCD 的控制;唯一能确保的事情是 GCD 一次只执行一个任务,并且按照我们添加到队列的顺序来执行。
由于在串行队列中不会有两个任务并发运行,因此不会出现同时访问临界区的风险。

11. Concurrent Queues 并发队列

在并发队列中的任务能得到的保证是它们会按照被添加的顺序开始执行,但是无法保证任务的开始必须在上一个任务完成之后,也就是说上一个任务还没完成下一个任务就开始了。任务可能以任意顺序完成,你不会知道何时开始运行下一个任务,或者任意时刻有多少 Block 在运行。如图



何时开始一个 Block 完全取决于 GCD 。如果一个 Block 的执行时间与另一个重叠,也是由 GCD 来决定是否将其运行在另一个不同的核心上,如果那个核心可用,否则就用上下文切换的方式来执行不同的 Block 。

12. Queue Types 队列类型

首先,系统提供给你一个叫做 主队列(main queue) 的特殊队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。然而,它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。这个队列就是用于发生消息给 UIView 或发送通知的。
系统同时提供给你好几个并发队列。它们叫做 全局调度队列(Global Dispatch Queues) 。
最后,你也可以创建自己的串行队列或并发队列。这就是说,至少有五个队列任你处置:主队列、四个全局调度队列,再加上任何你自己创建的队列。

使用
  1. dispatch_queue_create

         //创建一个串行队列
         dispatch_queue_t serialQue = dispatch_queue_create"serialQueue", DISPATCH_QUEUE_SERIAL);
         
         //创建一个并发队列
         dispatch_queue_t conQue = dispatch_queue_create("conCurrentQue", DISPATCH_QUEUE_CONCURRENT);
         
         //创建一个高优先级并发队列
         dispatch_queue_attr_t queAttr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INTERACTIVE, -1);
         dispatch_queue_create(@"attrQue", queAttr);
    
  2. Main Dispatch Que Or Global Dispatch Que
    //获取主队列
    dispatch_queue_t mainQue = dispatch_get_main_queue();

         //获取全局高优先级并发队列
         //优先级
        //  QOS_CLASS_USER_INTERACTIVE > QOS_CLASS_USER_INITIATED > QOS_CLASS_UTILITY > QOS_CLASS_DEFAULT > QOS_CLASS_BACKGROUND
         dispatch_queue_t globalQue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    
  3. dispatch_set_target_queue
    使用dispatch_queue_create创建的queue不管是串行的还是并行的,都使用与默认优先级全局队列相同的优先级。如果要变更生成队列的优先级,就要使用此函数。生成一个高优先级的并发队列的代码入下:
    dispatch_queue_t highQue = dispatch_queue_create("highPri", DISPATCH_QUEUE_CONCURRENT);
    dispatch_queue_t gloablHighQue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    dispatch_queue_t gloablDefaultQue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);

     dispatch_set_target_queue(highQue, gloablHighQue);
             
     dispatch_async(gloablDefaultQue, ^{
             
     NSLog(@"this is defaulPri queue");
     });
     dispatch_async(highQue, ^{
             
     NSLog(@"this is highPri queue");
     });
    

    运行结果如下:

    2016-03-29 09:04:28.958 LZThreadPro04GCD48247:4511919 this is highPri queue
    2016-03-29 09:04:28.958 LZThreadPro04GCD48247:4511909 this is defaulPri queue

  4. dispatch_after
    可以让指定任务延迟执行,如让任务延迟3秒执行。
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

            NSLog(@"delayed action");
            
        });
    
  5. Dispatch Group
    在追加到队列中的多个任务全部结束后想执行结束处理。下列代码展示了当几个任务全部结束后才做最后处理的情况。
    NSLog(@"1 + 2 + 3 + ... + 100 = ");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    dispatch_queue_t mainQ = dispatch_get_main_queue();
    __block NSInteger sum1 = 0;
    __block NSInteger sum2 = 0;
    __block NSInteger sum3 = 0;
    __block NSInteger sum4 = 0;
    __block NSInteger sum5 = 0;
    __block NSInteger sum6 = 0;
    __block NSInteger sum7 = 0;
    __block NSInteger sum8 = 0;
    __block NSInteger sum9 = 0;
    __block NSInteger sum10 = 0;
    __block NSInteger sumTotal = 0;

         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 1; j < 11; j ++) {
         sum1 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 11; j < 21; j ++) {
         sum2 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 21; j < 31; j ++) {
         sum3 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 31; j < 41; j ++) {
         sum4 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 41; j < 51; j ++) {
         sum5 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 51; j < 61; j ++) {
         sum6 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 61; j < 71; j ++) {
         sum7 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 71; j < 81; j ++) {
         sum8 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 81; j < 91; j ++) {
         sum9 += j;
         }
         
         });
         
         dispatch_group_async(group, queue, ^{
         for (NSInteger j = 91; j < 101; j ++) {
         sum10 += j;
         }
         
         });
         
         dispatch_group_notify(group, mainQ, ^{
         
         sumTotal = sum1 + sum2 + sum3 + sum4 + sum5 + sum6 + sum7 + sum8 + sum9 + sum10;
         NSLog(@"%li", sumTotal);
         });
    
  6. dispatch_barrier_async
    在访问数据库或文件时,使用串行队列可避免数据竞争的问题。写入处理确实不可与写入处理以及读取处理并行处理,但是如果读取处理与读取处理并行,也不会出问题,为了高效进行访问,读取处理追加到并行队列,写入处理在没有读取处理进行的情况下追加到串行队列。这时候可以使用此函数。
    示例代码如下:
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    for (NSInteger i = 0; i < 10; i ++) {

             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"reader... %li", i);
             
             });
             }
             
             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"writer...");
             
             });
             
             for (NSInteger i = 10; i < 20; i ++) {
             
             dispatch_barrier_async(queue, ^{
             [NSThread sleepForTimeInterval:1];
             NSLog(@"reader...%li", i);
             
             });
             }
             NSLog(@"---------------");
    
  7. dispatch_suspend dispatch_resume
    当追加大量处理到队列时,在追加处理的过程中,有时希望不执行已追加的处理。这种情况下,只要挂起队列即可,当可以执行再恢复。
    dispatch_suspend(queue);
    dispatch_resume(queue);
    这俩函数对已经执行的处理没有影响,挂起后,追加到队列但尚未运行的处理会停止执行。而恢复则使得这些处理能够继续执行。

  8. dispatch_semaphore
    当并行的处理更新数据时,会产生数据不一样的情况,有时应用程序还会异常结束,此函数可以用来控制线程互斥访问共享资源。如下示例代码
    NSMutableArray *arrayM = [NSMutableArray array];
    dispatch_queue_t queue = dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    dispatch_semaphore_t sem = dispatch_semaphore_create(1);

     for (NSInteger i = 0; i < 1000; i ++) {
     dispatch_async(queue, ^{
        
     dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
     
     [arrayM  addObject:@(i)];
     
     dispatch_semaphore_signal(sem);
     
     });
     }
     }
    
  9. dispatch_once
    该函数是保证在应用程序中只执行一次制定处理的。
    static dispatch_once_t once;

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

推荐阅读更多精彩内容

  • GCD (Grand Central Dispatch) :iOS4 开始引入,使用更加方便,程序员只需要将任务添...
    池鹏程阅读 1,313评论 0 2
  • 1. GCD简介 什么是GCD呢?我们先来看看百度百科的解释简单了解下概念 引自百度百科:Grand Centra...
    千寻_544f阅读 354评论 0 0
  • Demo地址 一 概念 全称是Grand Central Dispatch,中枢调度器 纯C语言,提供了非常多强大...
    Mitchell阅读 649评论 0 4
  • 多线程 在iOS开发中为提高程序的运行效率会将比较耗时的操作放在子线程中执行,iOS系统进程默认启动一个主线程,用...
    郭豪豪阅读 2,576评论 0 4
  • 前言 在学习Android编程的时候,我们经常会使用 runOnUiThread(),把UI相关的操作放到里面。而...
    ChenJZ阅读 11,795评论 22 36