iOS Kit

iOS GCD之任务与队列

2020-11-08  本文已影响0人  远方竹叶

什么是GCD?

Grand Central Dispatch(GCD)是 Apple 开发的一个多核编程的解决方法。它是一套纯 C 语言的 API,主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。

为什么要用GCD?有什么优势?

核心

在学习使用之前,先介绍两个核心概念:任务和队列

任务

就是 执行操作 的意思,换句话说就是在线程中执行的那段代码,在 GCD 中是放在 block 中的。执行任务有两种方式:同步执行(sync)和异步执行(async)

异步添加任务到指定的队列中,它不会做任何等待,可以继续执行任务。可以在新的线程中执行任务,具备开启新线程的能力。

同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行。只能在当前线程中执行任务,不具备开启新线程的能力。

异步执行(async)虽然具有开启新线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关

队列(Dispatch Queue)

这里的队列指执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。

GCD 中,有两种队列:串行队列(Serial Dispatch Queue)和并发队列(Concurrent Dispatch Queue)

每次只有一个任务被执行。让任务一个接着一个地执行。(只开启一个线程,一个任务执行完毕后,再执行下一个任务)

可以让多个任务并发(同时)执行。(可以开启多个线程,并且同时执行任务)

并发队列的并发功能只有在异步函数下才有效

使用步骤

GCD 的使用非常简单,只有两步

队列的创建方法

可以通过 dispatch_queue_create 来创建队列,源码如下:

API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_queue_t
dispatch_queue_create(const char *_Nullable label,
        dispatch_queue_attr_t _Nullable attr);

可以看到,需要传入两个参数:第一个参数表示队列的唯一标识符,用于 DEBUG,可为空;第二个参数用来识别是串行队列还是并发队列。DISPATCH_QUEUE_SERIAL 表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。

dispatch_queue_t seialQueue = dispatch_queue_create("xxx", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("xxx", DISPATCH_QUEUE_CONCURRENT);

队列的获取方法

GCD 提供了一个特殊的串行队列:主队列(Main Dispatch Queue)。所有放在主队列中的任务,都会放到主线程中执行。获取方法如下:

dispatch_queue_t mainQueue = dispatch_get_main_queue();

此外,GCD 还提供了一个全局并发队列:(Global Dispatch Queue)

API_AVAILABLE(macos(10.6), ios(4.0))
DISPATCH_EXPORT DISPATCH_CONST DISPATCH_WARN_RESULT DISPATCH_NOTHROW
dispatch_queue_global_t
dispatch_get_global_queue(intptr_t identifier, uintptr_t flags);

可以看到,获取全局队列也需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用 0 即可。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

任务的创建方法

GCD 提供了两种执行任务的创建方法:

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    // 同步执行任务代码
});

// 异步执行任务创建方法
dispatch_async(queue, ^{
    // 异步执行任务代码
});

综上所述,GCD 为开发者提供了两种队列(串行/并发)以及两种任务执行方法(同步/异步),所以这里面就有了四种组合。再结合上面提到的主队列和全局并发队列(可以当作是普通的并发队列),现在就有了6种组合:

区别 主队列 串行队列 并发队列
同步 没有开启新线程,死锁 没有开启新线程,顺序执行任务 没有开启新线程,顺序执行任务
异步 没有开启新线程,顺序执行任务 有开启新线程(1条),顺序执行任务 有开启新线程,并发执行任务

这里我们先给出不同组合的区别,下面详细讲解

基本使用

下面分别介绍下并发队列和串行队列的两种执行方式

并发队列+同步执行

在当前线程中执行任务,不会开启新的线程,执行完一个任务,再执行另一个任务

- (void)syncConcurrent {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"并发队列+同步执行---开始");
    
    dispatch_queue_t queue = dispatch_queue_create("sync.concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"并发队列+同步执行---结束");
}

运行项目,输出结果如下:

可以看到:

并发队列+异步执行

可以开启多个线程,任务交替执行(即我们肉眼看到的同时执行)

- (void)asyncConcurrent {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"并发队列+异步执行---开始");
    
    dispatch_queue_t queue = dispatch_queue_create("async.concurrent", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"并发队列+异步执行---结束");
}

运行项目,输出结果如下:

可以看到:

串行队列+异步执行

具备开启新线程的能力,但是因为任务是在串行队列中执行,当前任务执行完毕,才会执行下一个任务

- (void)asyncSerial {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"串行队列+异步执行---开始");
    
    dispatch_queue_t queue = dispatch_queue_create("async.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"串行队列+异步执行---结束");
}

运行项目,输出结果如下:

可以看到:

串行队列+同步执行

不会开启新的线程,任务会顺序执行的。

- (void)syncSerial {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"串行队列+同步执行---开始");
    
    dispatch_queue_t queue = dispatch_queue_create("sync.serial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"串行队列+同步执行---结束");
}

运行项目,输出结果如下:

可以看到:

主队列

GCD 自带的一种特殊的队列:

主队列也有两种组合方式

主队列+异步执行

只在主线程中执行任务,执行完一个任务,再执行下一个任务

- (void)asyncMain {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"主队列+异步执行---开始");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"主队列+异步执行---结束");
}

运行项目,输出结果如下:

可以看到:

主队列+同步执行

同步执行 + 主队列在不同线程中调用结果也是不一样,在主线程中调用会出现死锁,而在其他线程中则不会。

  1. 在主线程中调用 主队列+同步执行

互相等待(死锁)

可以看到:

程序崩溃了,因为我们在主线程中执行 syncMain 方法,相当于把 syncMain 放到了主线程的队列中。同步执行会等待当前队列中的任务执行完毕,才会继续执行。当我们把任务追加到主队列中,任务 1 就在等待主线程处理完 syncMain 任务,而 syncMain 任务需要等待任务 1 执行完毕,才能继续执行。两个任务相互等待,卡住(死锁)后续都执行不了

  1. 在其他线程中调用 主队列+同步执行

不会开启新线程,执行完一个任务,继续执行下一个任务

// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

- (void)syncMain {
    NSLog(@"当前线程:%@", [NSThread currentThread]);
    NSLog(@"在其他线程中主队列+同步执行---开始");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        //任务1
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务1所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务2
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务2所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queue, ^{
        //任务3
        for (int i = 0; i < 2; i++) {
            sleep(2); //模拟耗时操作
            NSLog(@"任务3所在的线程:%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"在其他线程中主队列+同步执行---结束");
}

运行项目,输出结果如下:

可以看到:

syncMain 任务放到了其他线程中执行,当任务1、任务2、任务3追加到主队列中,这三个任务都会在主线程中执行。syncMain 任务在其他线程中执行到追加任务1到主队列中,因为主队列现在没有正在执行的任务,所以,会直接执行主队列的任务1,等任务1执行完毕,再接着执行任务2、任务3。所以这里不会卡住线程

上一篇下一篇

猜你喜欢

热点阅读