iOS多线程开发之GCD

GCD简介

GCDGrand Central Dispatch的缩写,是基于C语言。是苹果公司为多核的的并行运算提出的解决方案。GCD负责创建线程和调度需要执行的任务,线程是由系统管理的。

开发中经常使用到GCD里的知识例如:

  • 创建单例的时候:
static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       //code
   });

这个让code只执行一次。

  • 获取主线程
dispatch_async(dispatch_get_main_queue(), ^{
        //code
    });
  • 异步执行
dispatch_async(dispatch_queue_create("robertlee", DISPATCH_QUEUE_SERIAL), ^{
        //code
    });

GCD能做的事远不止这些,接下来让我们了解一下GCD。GCD有两个核心概念,分别是任务和队列,接下来让我们介绍一下。

队列和任务

任务

任务也就是用户提交给队列的工作单元,也就是代码块,在GCD中需要是以block的形式存在。

队列

Dispatch Queue(队列) 是用来存放任务的集合,负责管理开发者提交的任务。队列严格遵循FIFO(先进先出)的原则,队列会维护和使用一个线程池来处理用户提交的任务,线程池来执行队列管理的任务。

队列主要分两种:

  • 串行队列(Serial Dispatch Queue):
    串行队列中的线程池中只有一个线程,每次只能执行一个任务,前一个任务执行完,才能够执行下一个任务。

  • 并行队列(Concurrent Dispatch Queue):
    并行队列的线程池提供了多个线程,可以多个任务同时执行。

队列的创建

//表示队列
dispatch_queue_t
//创建队列,第一个参数表示队列的字符串,第二个控制创建队列的类型,
//若第二个参数为NULL,则表示创建的队列为串行队列
dispatch_queue_create(const char *_Nullable label,
        dispatch_queue_attr_t _Nullable attr);
  • 创建串行队列
//创建串行队列:
dispatch_queue_t syncqueue = dispatch_queue_create("robertlee.testqueue", DISPATCH_QUEUE_SERIAL);
  • 创建并行队列
//创建并行队列
dispatch_queue_t casyncqueue = dispatch_queue_create("robertleel.testqueue", DISPATCH_QUEUE_CONCURRENT);
  • 获取全局并发队列

全局并发队列可以同时并行的执行多个任务,系统提供了多个并发的队列,整个应用内共享,可以使用dispatch_get_global_queue()函数获取这些队列

dispatch_queue_global_t
//第一个参数用于指定队列的优先级, 第二个参数为以后做准备,填0即可。
dispatch_get_global_queue(long identifier, unsigned long flags);

//全局并发队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

系统宏定义了几个优先级:

#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 //后台
  • 获取主队列

GCD自带了一个串行队列,就是主队列,只要提交到主队列中的任务,都会在主线程中执行。通过dispatch_get_main_queue()函数获得主队列

//获取主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();

提交任务

上面已经介绍了队列的创建,接下来让我们看看如何提交任务。

  • 同步提交任务
dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);

dispatch_sync_f(dispatch_queue_t queue,
        void *_Nullable context, dispatch_function_t work);

GCD提供了两种方法进行同步提交任务,第一种方法是将代码块以同步的方式提交给queue队列。第一个参数是目标队列,第二个参数为代码块。
第二种方式是将函数以同步的方式提交给队列,还包括了content 上下文。

  • 异步的方式提交任务

    dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
    dispatch_async_f(dispatch_queue_t queue,

    void *_Nullable context, dispatch_function_t work);
    

同步异步的方法名只差一个a字母,一个以同步方式执行任务,一个以异步的方式执行任务,后者在开发中最常用。
大致用法如下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_P 大专栏  iOS多线程开发之GCDRIORITY_DEFAULT, 0), ^(void) {
                //code

                dispatch_async(dispatch_get_main_queue(), ^(void) {
                    //code
                });
            });

先异步执行代码块,在执行完之后,可能会刷新页面、修改UI等步骤,需要到主线程中执行。

上面我们说了有3种创建队列的方式,又有两种队列执行方式,所以按照正常情况下会有6中组合方式。

  • 1、同步(sync) + 并行 //没开启新线程 串行执行任务
  • 2、同步(sync) + 串行 // 没开启新线程 串行执行任务
  • 3、同步(sync) + 主队列 //导致死锁 引发程序奔溃
  • 4、异步(async) + 并行 // 开启新线程、并行执行任务
  • 5、异步(async) + 串行 // 开启新线程,串行执行任务
  • 6、异步(async) + 主队列 // 没开启新线程 串行执行任务

总结:同步异步觉得是否开启新线程,并发串行决定任务的执行方式。

其他常用方法

单次执行任务 dispatch_once()

dispatch_once(dispatch_once_t *predicate,
        DISPATCH_NOESCAPE dispatch_block_t block);

刚开始已经提到了单次执行方法。第一个参数表示dispatch_once_t类型的指针,第二个参数是代码块,在创建代理的时候经常用到。

多次重复执行任务 dispatch_apply()

GCD中可以多次执行相同代码,使用dispatch_apply()函数,第一个参数表示次数,第二个参数表示执行队列,第三个参数表示代码块。

void dispatch_apply(size_t iterations,
        dispatch_queue_t DISPATCH_APPLY_QUEUE_ARG_NULLABILITY queue,
        DISPATCH_NOESCAPE void (^block)(size_t));

延时方法 dispatch_after()

dispatch_after(dispatch_time_t when, dispatch_queue_t queue,
        dispatch_block_t block);

延时方法,代码块在延时之后才开始执行:

  • 第一个参数表示延时时间
  • 第二个参数表示延迟完执行任务的队列
  • 第三个参数表示代码块

GCD 的队列组:dispatch_group

有时候我们在请求页面数据的时候,可能需要请求2个接口或者多个,我们希望所有请求完之后再刷新界面,这个时候如何保证接口都请求完成呢,这就是GCD队列组的用处了。

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"0");
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"1");
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"2");
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"3");
});

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSLog(@"4");
});

dispatch_group_leave(group);

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"end");
});

也可以使用dispatch_semaphore_tdispatch_group_wait实现,但是我们后面会介绍这个方法。

GCD 信号量 dispatch_semaphore

信号量在使用线程同步的时候经常用到,dispatch_semaphore有三种方法可以使用

dispatch_semaphore_create:初始化一个信号量
dispatch_semaphore_signal:发送一个信号,让信号总量加1
dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。

在YYCache中如何使用dispatch_semaphore来给线程加锁:

YYDiskCache中使用的线程锁为:定义了一个信号量:dispatch_semaphore_t _lock;

#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
#define Unlock() dispatch_semaphore_signal(self->_lock)

这就是GCD,有很多未使用的功能都没有介绍,下次遇到再补充。

感谢:

iOS多线程:『GCD』详尽总结