美文网首页
笔记 - 多线程之GCD

笔记 - 多线程之GCD

作者: 强子ly | 来源:发表于2019-11-21 19:07 被阅读0次

    目录

    • 概念
    • 常用API的使用
    • 关于GCD的面试题

    一、概念

    • 1.1、什么是GCD?

    定义想执行的任务,并追加到适当的 Dispatch Queue中

    • 1.2、GCD的队列的概念
    并发队列(Concurrent Dispatch Queue)
    - 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
    - 并发功能只有在异步(dispatch_async)函数下才有效
    
    
    串行队列(Serial Dispatch Queue)
    - 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
    
    • 1.3、容易混淆的术语 (同步、异步、并发、串行)
    同步和异步主要影响:能不能开启新线程
    - 同步:在当前线程中执行任务,不具备开启新线程的能力
    - 异步:在新的线程中执行任务,具备开启新线程的能力
    
    
    并发和串行主要影响:任务的执行方式
    - 并发:多个任务并发(同时)执行
    - 串行:一个任务执行完毕后,再执行下一个任务
    
    各种队列的执行效果.png

    二、常用API的使用

    • 2.1、dispatch_sync
    dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>,
                  <#^(void)block#>)
    
    用同步的方式执行任务
    - 参数一、队列
    - 参数二、任务
    
    • 2.2、dispatch_async
    dispatch_async(<#dispatch_queue_t  _Nonnull queue#>,
                   <#^(void)block#>)
    
    - 参数一、队列
    - 参数二、任务
    
    • 2.3、获取 Dispatch Queue

    • 2.3.1、通过API的生成(dispatch_queue_create)

    dispatch_queue_create(<#const char * _Nullable label#>,
                          <#dispatch_queue_attr_t  _Nullable attr#>);
    
    参数一:指定生成返回的Dispatch Queue的名称
    - 推荐使用应用程序ID这种逆序全程域名,在Instruments及CrashLog中方便调试
    
    参数二:指定生成返回的Dispatch Queue的类型
    - 指定为 NULL 或 DISPATCH_QUEUE_SERIAL,生成Serial Dispatch Queue;
    - 指定为DISPATCH_QUEUE_CONCURRENT,生成Concurrent Dispatch Queue
    
    例:
    dispatch_queue_t creatQueue1 = dispatch_queue_create("com.example.gcd.xxx",
                                                         NULL);
    dispatch_queue_t creatQueue2 = dispatch_queue_create("com.example.gcd.xxx",
                                                         DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t creatQueue3 = dispatch_queue_create("com.example.gcd.xxx",
                                                         DISPATCH_QUEUE_CONCURRENT);
    
    • 2.3.2、获取系统提供的Dispatch Queue
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    - 在主线程中执行的 Dispatch Queue
    
    
    dispatch_queue_t globeQueue = dispatch_get_global_queue(<#long identifier#>,
                                                            <#unsigned long flags#>)
    - 所有应用程序都能够使用的 Concurrent Dispatch Queue
    - 优先级:
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2                // 高优先级
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0             // 默认优先级
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2)              // 低优先级
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  // 后台优先级
    - ⚠️:通过XNU内核用于Global Dispatch Queue的线程不能保证实时性能,因此执行优先级只是大致的判断
    
    使用:
    dispatch_queue_t globeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,
                                                            0);
    dispatch_queue_t globeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
                                                            0);
    dispatch_queue_t globeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW,
                                                            0);
    dispatch_queue_t globeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,
                                                            0);
    
    • 2.4、dispatch_after
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW,
                                 (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)),
                   dispatch_get_main_queue(), ^{
    });
    
    ⚠️在多长时间之后执行,这是我们常用的API之一,现在说一下需要注意的点:
    - dispatch_after函数不是在指定时间后执行处理,而是在指定时间追加处理到 Dispatch Queue;
    - 本身存在延迟:执行的时间 = 传入参数 + runloop循环1次的时间 
    
    • 2.5、dispatch_once
    - (instancetype)init {
        if (self = [super init]) {
            static int initialized = NO;
            if (initialized == NO) {
                // 初始化单例
            }
            initialized = YES;
        }
        return self;
    }
    
    - (instancetype)init {
        if (self = [super init]) {
            static dispatch_once_t pred;
            dispatch_once(&pred, ^{
                // 初始化单例
            });
        }
        return self;
    }
    
    第一段代码存在的隐患:
    如果在多核CPU中,在正在更新表示是否初始化的标志变量时读取,就有可能多次执行初始化处理。
    
    • 2.6、Dispatch Group

    使用场景:在追加到Dispatch Queue中多个处理全部结束后想执行结果处理。也就是说等多个异步线程执行完成后执行

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_async(queue, ^{
        NSLog(@"执行一");
    });
    dispatch_async(queue, ^{
        NSLog(@"执行二");
    });    
    dispatch_async(queue, ^{
        NSLog(@"执行三");
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"三条线程执行完成");
    });
    
    
    2019-10-07 12:24:22.914339+0800 GCD[10825:325226] 执行二
    2019-10-07 12:24:22.914345+0800 GCD[10825:325223] 执行一
    2019-10-07 12:24:22.914369+0800 GCD[10825:325224] 执行三
    2019-10-07 12:24:22.930029+0800 GCD[10825:325078] 三条线程执行完成
    

    也可以使用 dispatch_group_wait函数仅等待全部处理执行结束

    dispatch_group_wait(<#dispatch_group_t  _Nonnull group#>,
                        <#dispatch_time_t timeout#>)
    
    - 参数一、传入线程组
    - 参数二、指定等待的时间 (DISPATCH_TIME_FOREVER意味着永久等待)
    
    
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_async(queue, ^{
        NSLog(@"执行一");
    });
    dispatch_async(queue, ^{
        NSLog(@"执行二");
    });    
    dispatch_async(queue, ^{
        NSLog(@"执行三");
    });
    sleep(3);
    
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"三条线程执行完成");
    
    • 2.7、dispatch_barrier_async(栅栏函数)

    在进程管理中起到一个栅栏的作用,它等待所有位于barrier函数之前的操作执行完毕后执行,并且在barrier函数执行之后,barrier函数之后的操作才会得到执行,
    ⚠️该函数需要同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"执行一");
    });
    dispatch_async(queue, ^{
        NSLog(@"执行二");
    });
    dispatch_barrier_async(queue, ^{
        sleep(3);
        NSLog(@"xxxxxxxxxx");
    });
    dispatch_async(queue, ^{
        NSLog(@"执行三");
    });
    dispatch_async(queue, ^{
        NSLog(@"执行四");
    });
    
    
    2019-10-07 12:48:55.348575+0800 GCD[11221:340677] 执行一
    2019-10-07 12:48:55.348582+0800 GCD[11221:340680] 执行二
    2019-10-07 12:48:58.350728+0800 GCD[11221:340680] xxxxxxxxxx
    2019-10-07 12:48:58.350999+0800 GCD[11221:340678] 执行四
    2019-10-07 12:48:58.351001+0800 GCD[11221:340680] 执行三
    
    • 2.8、dispatch_set_target_queue
      更改Dispatch Queue的执行优先级

    • 2.9、dispatch_apply
      该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_CONCURRENT);
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"------%zu", index);
    });
    NSLog(@"执行完成");
    
    
    2019-10-07 16:18:05.663250+0800 GCD[12009:372598] ------0
    2019-10-07 16:18:05.663277+0800 GCD[12009:372679] ------1
    2019-10-07 16:18:05.663277+0800 GCD[12009:372683] ------2
    2019-10-07 16:18:05.663302+0800 GCD[12009:372678] ------3
    2019-10-07 16:18:05.663446+0800 GCD[12009:372679] ------4
    2019-10-07 16:18:05.663581+0800 GCD[12009:372598] 执行完成
    

    ⚠️:由于dispatch_apply函数也与 dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数

    • 3.0、dispatch_suspenddispatch_resume
    • 3.1、Dispatch Semaphore
      持有计数信号

    三、关于GCD的面试题

    • 你理解的多线程?
    • iOS的多线程方案有哪几种?你更倾向于哪一种?
    • 你在项目中用过GCD么
    • GCD的队列类型
    • 说一下OperationQueue和GCD的区别,以及各自的优势
    • 线程安全的处理手段有哪些?
    • OC你了解的锁有哪些?在你回答基础上进行二次提问?
      自旋锁和互斥锁对比?使用以上锁需要注意哪些?使用C/OC/C++,任选其一,实现自旋或互斥?口述即可
    • 3.1、iOS的多线程方案有哪几种?你更倾向于哪一种?
    iOS中的常见多线程方案
    • 3.2、自旋锁、互斥锁比较
    什么情况使用自旋锁比较划算?
    
    - 预计线程等待锁的时间很短
    - 加锁的代码(临界区)经常被调用,但竞争情况很少发生
    - CPU资源不紧张
    
    
    什么情况使用互斥锁比较划算?
    - 预计线程等待锁的时间较长
    - 单核处理器
    - 临界区有IO操作
    - 临界区代码复杂或者循环量大
    - 临界区竞争非常激烈
    
    • 3.3、关于死锁的相关面试题
    NSLog(@"执行任务1");
    dispatch_queue_t main = dispatch_get_main_queue();
    dispatch_sync(main, ^{
        NSLog(@"执行任务2");
    });
    NSLog(@"执行任务3");
    
    2019-10-07 10:30:04.809292+0800 GCD[10060:304837] 执行任务1
    ❌产生死锁
    
    NSLog(@"执行任务1");
    dispatch_queue_t main = dispatch_get_main_queue();
    dispatch_async(main, ^{
        NSLog(@"执行任务2");
    });
    NSLog(@"执行任务3");
    
    2019-10-07 10:29:03.893940+0800 GCD[10038:303891] 执行任务1
    2019-10-07 10:29:03.894175+0800 GCD[10038:303891] 执行任务3
    2019-10-07 10:29:03.918505+0800 GCD[10038:303891] 执行任务2
    
    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.xxx", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        dispatch_sync(queue, ^{
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
    
    
    2019-10-07 10:31:09.843690+0800 GCD[10091:306352] 执行任务1
    2019-10-07 10:31:09.843905+0800 GCD[10091:306352] 执行任务5
    2019-10-07 10:31:09.843937+0800 GCD[10091:306413] 执行任务2
    ❌产生死锁
    

    通过上述代码我们可以得出一个结论:使用sync函数往当前串行队列中添加任务,会卡住当前的串行队列
    如果某些场景需要这样做,可以使用两个队列来避免上面产生死锁问题。

    NSLog(@"执行任务1");
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.1", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t queue2 = dispatch_queue_create("com.example.gcd.2", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"执行任务2");
        dispatch_sync(queue2, ^{
            NSLog(@"执行任务3");
        });
        NSLog(@"执行任务4");
    });
    NSLog(@"执行任务5");
    

    后续待扩展(线程锁)、GNUstep

    这篇文章写的非常棒,可以看看 iOS多线程--彻底学会多线程之『GCD』

    相关文章

      网友评论

          本文标题:笔记 - 多线程之GCD

          本文链接:https://www.haomeiwen.com/subject/vfaupctx.html