iOS 多线程实现-GCD

一、基本概念

1、GCD的介绍

GCD是Grand Central Dispatch的简称,是一套基于C语言的底层API。
GCD简化了多线程的实现,有以下优点:
GCD可用于多核的并行运算;
GCD会自动利用更多的CPU内核(比如双核、四核);
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码。
GCD抽象层次最高,使用也简单,因此,苹果也推荐使用GCD。但是在这里也需要注意的是使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。

2、GCD中两个核心概念:任务和队列

任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力

同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力
队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。在GCD中有两种队列:串行队列并行队列

并行队列(Concurrent Dispatch Queue):可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务)
并行功能只有在异步(dispatch_async)函数下才有效
串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

关于同步异步、串行并行和线程的关系,下面通过一个表格来总结:

iOS 多线程实现-GCD_第1张图片
由上图我们可以得出我们有3种队列,2种任务执行方式,那么我们就有了6种不同的组合方式。我们接下来将逐一说明。这6种不同的组合方式是 :

1.并行队列 + 同步执行
2.并行队列 + 异步执行
3.串行队列 + 同步执行
4.串行队列 + 异步执行
5.主队列 + 同步执行
6.主队列 + 异步执行

二、GCD的基本使用

1、队列的创建方法

1.1、使用dispatch_queue_create来创建队列
//参数一:表示队列的唯一标识符,可为空
//参数二:用来识别是串行队列还是并行队列
//dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t  _Nullable attr);

// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
1.2、使用dispatch_get_global_queue来创建全局并行队列
//GCD默认提供了全局的并行队列(参数一:队列优先级 参数二:暂时没用)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

2、任务的创建方法

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});

3、并行队列 + 同步执行

- (void) syncConcurrent
{
    NSLog(@"begin-- 并行队列 + 同步执行");
    dispatch_queue_t queue= dispatch_queue_create("mytest.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 并行队列 + 同步执行");
}

4、并行队列 + 异步执行

- (void) asyncConcurrent
{
    NSLog(@"begin-- 并行队列 + 异步执行");
    dispatch_queue_t queue= dispatch_queue_create("mytest.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"end-- 并行队列 + 异步执行");
}

5、串行队列 + 同步执行

- (void) syncSerial
{
    NSLog(@"begin-- 串行队列 + 同步执行");
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 串行队列 + 同步执行");
}

6、串行队列 + 异步执行

- (void)asyncSerial
{
    NSLog(@"begin-- 串行队列 + 异步执行");
    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 串行队列 + 异步执行");
}

7、主队列 + 同步执行

//造成死锁:main_queue正在处理syncMain ,而syncMain方法中又有同步事件要处理 ,造成相互等待
- (void)syncMain
{
    NSLog(@"begin-- 主队列 + 同步执行");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 主队列 + 同步执行");
}

8、主队列 + 异步执行

- (void)asyncMain
{
    NSLog(@"begin-- 主队列 + 异步执行");
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; i++) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"end-- 主队列 + 异步执行");
}

三、GCD的其他方法

1、GCD的栅栏方法 (dispatch_barrier_async)
1.1、什么是dispatch_barrier_async函数

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

1.2、dispatch_barrier_async函数的作用

1.实现高效率的数据库访问和文件访问
2.避免数据竞争

- (void) asyncbarrier
{
  //同dispatch_queue_create函数生成的concurrent Dispatch Queue队列一起使用
    dispatch_queue_t queue = dispatch_queue_create("mytest.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });
   
    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });
    
    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}

输出结果:1 2 --> barrier -->3 4 其中12 与 34 由于并行处理先后顺序不定

2、 GCD只执行一次 ( dispatch_once)

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了GCD的dispatch_once方法。使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});
3、GCD任务组 ( dispatch_group)

了解完队列之后,很自然的会有一个想法:我们怎么知道所有任务都已经执行完了呢?
在单个串行队列中,这个不是问题,因为只要把回调block添加到队列末尾即可。但是对于并行队列,以及多个串行、并行队列混合的情况,就需要使用dispatch_group了。

这里需要指出dispatch_group_wait方法是一个很有用的方法,它的完整定义如下:

 dispatch_group_wait(<#dispatch_group_t  _Nonnull group#>, <#dispatch_time_t timeout#>)

第一个参数表示要等待的group,第二个则表示等待时间。返回值表示经过指定的等待时间,属于这个group的任务是否已经全部执行完,如果是则返回0,否则返回非0。
第二个dispatch_time_t类型的参数还有两个特殊值:DISPATCH_TIME_NOW 和 DISPATCH_TIME_FOREVER。
前者表示立刻检查属于这个group的任务是否已经完成,后者则表示一直等到属于这个group的任务全部完成。

dispatch_group_wait代码使用示例

- (void)groupSync
{
    dispatch_queue_t disqueue =  dispatch_queue_create("mytest.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_t disgroup = dispatch_group_create();
    dispatch_group_async(disgroup, disqueue, ^{
        NSLog(@"任务一完成");
    });
    dispatch_group_async(disgroup, disqueue, ^{
        sleep(9);
        NSLog(@"任务二完成");
    });
    dispatch_group_notify(disgroup, disqueue, ^{
        NSLog(@"dispatch_group_notify 执行");
    });
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_wait(disgroup, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
        NSLog(@"dispatch_group_wait 结束");
    });
}

由上面的示例可以看出:
向group中放入两个任务(准确讲是将任务加入到了并行队列disqueue中执行,然后队列和group关联当队列上任务执行完毕时group会进行同步),第二个任务会等待8秒所以第一个任务会先完成;会先打印任务一完成再打印任务二完成,当两个任务都完成时dispatch_group_notify中的block会执行;会接着打印dispatch_group_notify 执行;dispatch_group_wait设置了超时时间为5秒所以它会在5秒后停止等待打印dispatch_group_wait 结束(任务二会等待9秒所以它会在任务二完成前打印);
输出结果:任务一完成 --> dispatch_group_wait 结束 --> 任务二完成 --> dispatch_group_notify 执行

有时候我们会有这样的需求:分别异步执行2个耗时操作,然后当2个耗时操作都执行完毕后再回到主线程执行操作。这时候我们也可以用dispatch_group。dispatch_group通常有两种用法
我们可以先把任务放到队列中,然后将队列放入队列组中。
调用队列组的dispatch_group_notify回到主线程执行操作。具体如下:

用法一:
dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //执行1个耗时
        NSLog(@"Time_1");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //执行1个耗时
        NSLog(@"Time_2");
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //执行1个耗时
        NSLog(@"Time_3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任务均完成,刷新界面");
    });

用法二: 以耗时的网络请求操作为例
dispatch_group_t group = dispatch_group_create();
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求1
        [网络请求:{
        成功:dispatch_group_leave(group);
        失败:dispatch_group_leave(group);
}];
    });
    dispatch_group_enter;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求2
        [网络请求:{
        成功:dispatch_group_leave;
        失败:dispatch_group_leave;
}];
    });
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //请求3
        [网络请求:{
        成功:dispatch_group_leave(group);
        失败:dispatch_group_leave(group);
}];
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //界面刷新
        NSLog(@"任务均完成,刷新界面");
    });
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);

方法二中需要注意:
使用dispatch_group_enter(group)和dispatch_group_leave(group),这种方式使用更为灵活,enter和leave必须配合使用,有几次enter就要有几次leave,否则group会一直存在。当所有enter的block都leave后,会执行dispatch_group_notify的block。

4、GCD的延时执行方法 (dispatch_after)

当我们需要延迟执行一段代码时,就需要用到GCD的dispatch_after方法。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 3秒后异步执行这里的代码
   NSLog(@"run-----");
});
5、GCD的快速迭代方法 (dispatch_apply)

比如说遍历0~99这100个数字,通常我们会用for循环遍历,但是GCD给我们提供了快速迭代的方法dispatch_apply,使我们可以同时遍历。for循环的做法是每次取出一个元素,逐个遍历。dispatch_apply可以同时遍历多个数字。代码实现如下:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(100, queue, ^(size_t index) {
    NSLog(@"%zd------%@",index, [NSThread currentThread]);
});

你可能感兴趣的:(iOS 多线程实现-GCD)