多线程之GCD

什么是 GCD

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

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

首先,我们先来了解一下在 iOS 并发编程中非常重要的三个术语,这是我们理解 iOS 并发编程的基础:

  • 进程(process),指的是一个正在运行中的可执行文件。每一个进程都拥有独立的虚拟内存空间和系统资源,包括端口权限等,且至少包含一个主线程和任意数量的辅助线程。另外,当一个进程的主线程退出时,这个进程就结束了;
  • 线程(thread),指的是一个独立的代码执行路径,也就是说线程是代码执行路径的最小分支。在 iOS 中,线程的底层实现是基于 POSIX threads API 的,也就是我们常说的 pthreads ;
  • 任务(task),指的是我们需要执行的工作,是一个抽象的概念,用通俗的话说,就是一段代码。
串行 vs. 并发

从本质上来说,串行和并发的主要区别在于允许同时执行的任务数量。串行,指的是一次只能执行一个任务,必须等一个任务执行完成后才能执行下一个任务;并发,则指的是允许多个任务同时执行。

同步 vs. 异步

同样的,同步和异步操作的主要区别在于是否等待操作执行完成,亦即是否阻塞当前线程。同步操作会等待操作执行完成后再继续执行接下来的代码,而异步操作则恰好相反,它会在调用后立即返回,不会等待操作的执行结果。

队列 vs. 线程

在 iOS 中,有两种不同类型的队列,分别是串行队列和并发队列。正如我们上面所说的,串行队列一次只能执行一个任务,而并发队列则可以允许多个任务同时执行。iOS 系统就是使用这些队列来进行任务调度的,它会根据调度任务的需要和系统当前的负载情况动态地创建和销毁线程,而不需要我们手动地管理。

Operation Queues vs. Grand Central Dispatch (GCD)

简单来说,GCD 是苹果基于 C 语言开发的,一个用于多核编程的解决方案,主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。而 Operation Queues 则是一个建立在 GCD 的基础之上的,面向对象的解决方案。它使用起来比 GCD 更加灵活,功能也更加强大。下面简单地介绍了 Operation Queues 和 GCD 各自的使用场景:

  • Operation Queues :相对 GCD 来说,使用 Operation Queues 会增加一点点额外的开销,但是我们却换来了非常强大的灵活性和功能,我们可以给 operation 之间添加依赖关系、取消一个正在执行的 operation 、暂停和恢复 operation queue 等;

  • GCD :则是一种更轻量级的,以 FIFO 的顺序执行并发任务的方式,使用 GCD 时我们并不关心任务的调度情况,而让系统帮我们自动处理。但是 GCD 的短板也是非常明显的,比如我们想要给任务之间添加依赖关系、取消或者暂停一个正在执行的任务时就会变得非常棘手。

GCD和RunLoop的关系

在每个RunLoop周期醒来之后,会获取GCD在Main queue上的任务并执行,2者协同配合。

dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"我会在RunLoop醒来之后被RunLoop获取并且执行");
    });

最简单的一个demo
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
       ///block块 
 });

GCD使用方式可以简化成这个格式:

 dispatch_async(queue,block);
 queue:是代码执行的队列
 block:交给GCD处理的block

dispatch_queu_t (队列)

队列中分为2个种队列:Serial(串行)Concurrent(并行)

  • Serial:同时只执行一个任务。
  • Concurrent:可以并发的执行多个任务,但执行完成顺序是随机的。

系统提供6个全局并发队列,分别是一个主队列,和5个优先级不同的调度队列,除主线程队列是串行,其余都是并行队列,优先级越高,越优先执行

  • Main queue:主线程队列,在此队列执行将会在主线程调用,串行.
  • 优先级为QOS_CLASS_USER_INTERACTIVE的调度队列:user interactive等级表示任务需要被立即执行提供好的体验。
  • 优先级为QOS_CLASS_USER_INITIATED的调度队列:user initiated等级表示任务由UI发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
  • 优先级为QOS_CLASS_USER_DEFAULT的调度队列:默认优先级。
  • 优先级为QOS_CLASS_USER_UTILITY的调度队列:utility等级表示需要长时间运行的任务。
  • 优先级为QOS_CLASS_USER_BACKGROUND的调度队列:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。

获取不同级别队列的代码

    ///获取主线程队列
    dispatch_get_main_queue();
    ///QOS_CLASS_USER_INTERACTIVE 队列
    dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0);
    ///QOS_CLASS_USER_INITIATED 队列
    dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0);
    ///QOS_CLASS_DEFAULT 队列
    dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    ///QOS_CLASS_UTILITY 队列
    dispatch_get_global_queue(QOS_CLASS_UTILITY, 0);
    ///QOS_CLASS_UTILITY 队列
    dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0);
    第二个参数总是0,预留选项

当然你也可以用DISPATCH_QUEUE_PRIORITY枚举来获取不同优先级的队列
映射关系:

 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND
创建自己的队列

当系统队列不满足需求的时候我们也可以自己创建队列。

    ///创建串行队列,名字叫com.SERIAL
    dispatch_queue_create("com.SERIAL", DISPATCH_QUEUE_SERIAL);
    ///创建并行队列,名字叫com.CONCURRENT
    dispatch_queue_create("com.CONCURRENT", DISPATCH_QUEUE_CONCURRENT);

也可以为自定义队列设置优先级

方法一:

    ///创建一个队列的配置,分别是串行队列,优先级为比QOS_CLASS_DEFAULT少1的优先级
    dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_DEFAULT, -1);
    dispatch_queue_t queue = dispatch_queue_create("com.custom.queue_attr", attr);

注意:dispatch_queue_attr_make_with_qos_class最后一个参数必须是小于等于0的值,如果传给大于0,或者小于QOS_MIN_RELATIVE_PRIORITY的值,将会返回NULL,附文档上的说明

A relative priority within the QOS class. This value is a negative
offset from the maximum supported scheduler priority for the given class.
Passing a value greater than zero or less than QOS_MIN_RELATIVE_PRIORITY
results in NULL being returned.

方法二:

利用参考队列来设置优先级

  ///创建一个需要被设置优先级的队列
    dispatch_queue_t queue = dispatch_queue_create("com.target",NULL);
    ///取一个参考优先级的队列
    dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    ///将参考的优先级赋值于队列
    dispatch_set_target_queue(queue, referQueue);

async和sync(异步调用和同步调用)
///异步调用
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
///同步调用
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  • queue参数是block执行所在的队列,串行队列或者并行队列
  • block参数是执行的block块

异步调用会立即返回,而同步调用会阻塞当前线程,等待block执行完毕才会继续。

一些简单的demo

  dispatch_queue_t queueConcurrent = dispatch_queue_create("DISPATCH_QUEUE_CONCURRENT", DISPATCH_QUEUE_CONCURRENT);

    dispatch_sync(queueConcurrent, ^{
       ///todo
    });
    
    dispatch_async(queueConcurrent, ^{
      ///todo
    });

注意串行队列的死锁问题:由于dispatch_sync需要等待block被执行,这就非常容易发生死锁。如果一个串行队列,使用dispatch_sync提交block到自己队列中,就会发生死锁,如下代码

 dispatch_queue_t queueSerial = dispatch_queue_create("DISPATCH_QUEUE_SERIAL", DISPATCH_QUEUE_SERIAL);
        dispatch_async(queueSerial, ^{
            dispatch_sync(queueSerial, ^{
                ///发生死锁
                NSLog(@"永远执行不到");
            });
        });

解释:dispatch_sync需要等待block执行完成,同时由于队列串行,block的执行需要等待前面的任务,也就是dispatch_sync执行完成。两者互相等待,永远也不会执行完成,死锁就这样发生了。


dispatch_once(只执行一次)

在APP生命周期之中只执行一次,使用实现单列的场景

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

dispatch_after(延后提交Block)

延后一段时间来提交block代码,是提交不是执行,只是在一段时间之后来提交这个block给GCD,而不能保证立即执行,具体看queue是否繁忙,使用于延后处理的事务,如弹出好评框。

///推迟1秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        ///推迟提交的block
    });

dispatch_apply(迭代)

把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用。

适用场景,遍历一个数组对内部元素做迭代。

 ///遍历10次
    dispatch_apply(10, dispatch_get_main_queue(), ^(size_t index) {
        ///遍历执行的代码
    });

dispatch_barrier_async(执行的时候独占队列)

使用dispatch_barrier将任务加入到并行队列之后,任务会在前面任务全部执行完成之后执行,任务执行过程中,其他任务无法执行,直到barrier任务执行完

最典型的使用场景是多线程读写问题

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-pre-1");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-pre-2");
    });

    dispatch_barrier_async(concurrentQueue, ^(){
        NSLog(@"dispatch-barrier");
    });
    
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-1");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-2");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-3");
    });
    dispatch_async(concurrentQueue, ^(){
        NSLog(@"dispatch-4");
    });
输出
2016-03-05 02:32:02.867 GCDDemo[2422:1915594] dispatch-pre-2
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-pre-1
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-barrier
2016-03-05 02:32:02.867 GCDDemo[2422:1915593] dispatch-1
2016-03-05 02:32:02.867 GCDDemo[2422:1915594] dispatch-2
2016-03-05 02:32:02.867 GCDDemo[2422:1915599] dispatch-3
2016-03-05 02:32:02.867 GCDDemo[2422:1915614] dispatch-4

Dispatch_groups(监控多个异步任务)

dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。

当group里所有事件都完成 API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。

适用场景:多个网络请求同时发出,全部完成回调处理

 dispatch_queue_t concurrentQueue = dispatch_queue_create("com.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t group = dispatch_group_create();
   
    dispatch_group_async(group, concurrentQueue, ^{
        NSLog(@"1");
        sleep(1);
    });
    dispatch_group_async(group, concurrentQueue, ^{
        NSLog(@"2");
        sleep(2);

    });
    
    //阻塞
   // dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
   //异步
    dispatch_group_notify(group, concurrentQueue, ^{
        NSLog(@"所有任务完成");
    });

dispatch_time_t (GCD定时器)

通过dispatch_time_t,我们可以用GCD做定时器

执行一次

  ///执行一次(利用dispatch_after)
    dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC);
    dispatch_after(timer, dispatch_get_main_queue(), ^(void){
        //执行事件
    });

重复执行

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(_timer, dispatch_walltime(NULL,0), 1 * NSEC_PER_SEC, 0); //每秒执行
dispatch_source_set_event_handler(_timer, ^{
    //在这里执行事件
});
dispatch_resume(_timer);

你可能感兴趣的:(多线程之GCD)