笔记 - 多线程之GCD

目录

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

一、概念

  • 1.1、什么是GCD?

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

  • 1.2、GCD的队列的概念
并发队列(Concurrent Dispatch Queue)
- 可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
- 并发功能只有在异步(dispatch_async)函数下才有效


串行队列(Serial Dispatch Queue)
- 让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
  • 1.3、容易混淆的术语 (同步、异步、并发、串行)
同步和异步主要影响:能不能开启新线程
- 同步:在当前线程中执行任务,不具备开启新线程的能力
- 异步:在新的线程中执行任务,具备开启新线程的能力


并发和串行主要影响:任务的执行方式
- 并发:多个任务并发(同时)执行
- 串行:一个任务执行完毕后,再执行下一个任务

二、常用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的多线程方案有哪几种?你更倾向于哪一种?
  • 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』

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章