iOS多线程实现3-GCD

时间:2022-11-22 23:39:18

  一、介绍

  GCD,英文全称是Grand Central Dispatch(功能强悍的*调度器),基于C语言编写的一套多线程开发机制,因此使用时会以函数形式出现,且大部分函数以dispatch开头,虽然是C语言的但相对于苹果其它多线程实现方式,抽象层次更高,使用起来也更加方便。

  它是苹果为应对多核的并行运算提出的解决方案,它会自动利用多核进行并发处理和运算,且线程由系统自动管理(调度、运行),无需程序员参与,使用起来非常方便。

二、任务和队列

  GCD有两个核心:任务和队列。

  任务:要执行的操作或方法函数,队列:存放任务的集合,而我们要做的就是将任务添加到队列然后执行,GCD会自动将队列中的任务按先进先出的方式取出并交给对应线程执行。注意任务的取出是按照先进先出的方式,这也是队列的特性,但是取出后的执行顺序则不一定,下面会详细讨论。

  1 任务

  可以简单的认为是一个操作,一个函数,一个方法等等,在实际的开发中大多是以block(block使用详见)的形式,使用起来也更加灵活。

  2 队列

  有两种队列:串行队列和并行队列。串行队列:同步执行,在当前线程执行;并行队列:可由多个线程异步执行,但任务的取出还是FIFO的。

    队列创建,根据函数第二个参数来创建串行或并行队列。

// 参数1 队列名称
// 参数2 队列类型 DISPATCH_QUEUE_SERIAL/NULL串行队列,DISPATCH_QUEUE_CONCURRENT代表并行队列
// 下面代码为创建一个串行队列,也是实际开发中用的最多的
dispatch_queue_t serialQ = dispatch_queue_create("队列名", NULL);

  另外系统提供了两种队列:全局队列和主队列。

    全局队列属于并行队列,只不过已由系统创建的没有名字,且在全局可见(可用)。获取全局队列:

/* 取得全局队列
 第一个参数:线程优先级,设为默认即可,个人习惯写0,等同于默认
 第二个参数:标记参数,目前没有用,一般传入0
 */
serialQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    主队列属于串行队列,也由系统创建,只不过运行在主线程(UI线程)。获取主队列:

// 获取主队列
serialQ = dispatch_get_main_queue();

  3 执行方式-2种

  同步执行和异步执行。

    同步执行:不会开启线程,在当前线程执行。

    异步执行:gcd管理的线程池中有空闲线程就会从队列中取出任务执行,会开启线程。

  下面为实现同步和异步的函数,函数功能为:将任务添加到队列并执行。

iOS多线程实现3-GCD
/* 同步执行
 第一个参数:执行任务的队列:串行、并行、全局、主队列
 第二个参数:block任务
 */
void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 异步执行
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
iOS多线程实现3-GCD

三、几种类型

  很明显两种执行方式,两种队列。那么就有4种情况:串行队列同步执行、串行队列异步执行、并行队列同步执行、并行队列异步执行。哪一种会开启新的线程?开几条?是否并发?记忆起来比较绕,但是只要抓住基本的就可以,为了方便理解,现分析如下:

  1)串行队列,同步执行-----串行队列意味着顺序执行,同步执行意味着不开启线程(在当前线程执行)

  2)串行队列,异步执行-----串行队列意味着任务顺序执行,异步执行说明要开线程, (如果开多个线程的话,不能保证串行队列顺序执行,所以只开一个线程)

  3)并行队列,异步执行-----并行队列意味着执行顺序不确定,异步执行意味着会开启线程,而并行队列又允许不按顺序执行,所以系统为了提高性能会开启多个线程,来队列取任务(队列中任务取出仍然是顺序取出的,只是线程执行无序)。

  4)并行队列,同步执行-----同步执行意味着不开线程,则肯定是顺序执行

  5)主线程中主队列,同步执行-----程序执行不出来(死锁) ;原因:主队列,如果主线程正在执行代码,就不调度任务;同步执行:一直执行第一个任务直到结束。两者互相等待造成死锁。

// 在主线程执行下面代码,会死锁
dispatch_sync(dispatch_get_main_queue(), ^{
    // 要执行的代码
});

四、常用举例

  1 线程间通讯

  比如,为了提高用户体验,我们一般在其他线程(非主线程)下载图片或其它网络资源,下载完成后我们要更新UI,而UI更新必须在主线程执行,所以我们经常会使用:

iOS多线程实现3-GCD
// 同步执行,会阻塞指导下面block中的代码执行完毕
dispatch_sync(dispatch_get_main_queue(), ^{
    // 主线程,UI更新
});
// 异步执行
dispatch_async(dispatch_get_main_queue(), ^{
    // 主线程,UI更新
});
iOS多线程实现3-GCD

  2 其它常用

  全局队列,实现并发:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 要执行的代码
});

五、Dispatch Group调度组

  使用调度组,可以轻松实现在一些任务完成后,做一些操作。比如具有顺序性要求的生产者消费者等等。

  示例1  任务1完成之后执行任务2。

iOS多线程实现3-GCD
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self groupTest];
}
- (void)groupTest {
    // 创建一个组
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"开始执行");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 任务1
            // 等待1s一段时间在执行
            [NSThread sleepForTimeInterval:1];
            NSLog(@"task1 running in %@",[NSThread currentThread]);
        });
        dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
            // 任务2
            NSLog(@"task2 running in %@",[NSThread currentThread]);
        });
    });
}
iOS多线程实现3-GCD

  点击屏幕后,打印如下,可以看到任务1虽然等待了1s,任务2也不执行,只有任务1执行完毕才执行任务2.

2015-08-28 18:16:05.317 GCDTest[1468:229374] 开始执行
2015-08-28 18:16:06.323 GCDTest[1468:229457] task1 running in <NSThread: 0x7f8962f16900>{number = 2, name = (null)}
2015-08-28 18:16:06.323 GCDTest[1468:229456] task2 running in <NSThread: 0x7f8962c92750>{number = 3, name = (null)}

  示例2,其实示例1并不常用,真正用到的是监控多个任务完成之后,回到主线程更新UI,或者做其它事情。

iOS多线程实现3-GCD
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [self groupTest];
}
- (void)groupTest {
    // 创建一个组
    dispatch_group_t group = dispatch_group_create();
    NSLog(@"开始执行");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 关联任务1
            NSLog(@"task1 running in %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 关联任务2
            NSLog(@"task2 running in %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 关联任务3
            NSLog(@"task3 running in %@",[NSThread currentThread]);
        });
        dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{
            // 关联任务4
            // 等待1秒
            [NSThread sleepForTimeInterval:1];
            NSLog(@"task4 running in %@",[NSThread currentThread]);
        });
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            // 回到主线程执行
            NSLog(@"mainTask running in %@",[NSThread currentThread]);
        });
    });
}
iOS多线程实现3-GCD

  点击屏幕后,打印如下,可以看到无论其它任务然后和执行,mainTask等待它们执行后才执行。

2015-08-28 18:24:14.312 GCDTest[1554:236273] 开始执行
2015-08-28 18:24:14.312 GCDTest[1554:236352] task3 running in <NSThread: 0x7fa8f1f0c9c0>{number = 4, name = (null)}
2015-08-28 18:24:14.312 GCDTest[1554:236354] task1 running in <NSThread: 0x7fa8f1d10750>{number = 2, name = (null)}
2015-08-28 18:24:14.312 GCDTest[1554:236351] task2 running in <NSThread: 0x7fa8f1c291a0>{number = 3, name = (null)}
2015-08-28 18:24:15.313 GCDTest[1554:236353] task4 running in <NSThread: 0x7fa8f1d0e7f0>{number = 5, name = (null)}
2015-08-28 18:24:15.313 GCDTest[1554:236273] mainTask running in <NSThread: 0x7fa8f1c13df0>{number = 1, name = main}