GCD常见函数注意点

序:近来,北方很多地方都下雪了,全线迈入了冬季。望广大园友保重身体,慎防感冒。。。。

 

好了,开启今天的话题。

 

由于最近这短时间在项目中没有运用到多线程相关的知识点,所以在闲暇时间又回顾了一下GCD及单例的相关使用,并将一些使用中的注意点整理了一下,希望能够帮助有需要的童鞋。

 

首先,咱们来看GCD。

MJ老师总是习惯把GCD称作是“牛逼的中枢调度者”,这就足以看出GCD的grand之处。

GCD是多线程的一种实现方法,而对于多线程的基本概念,这里也简单提一下。多线程,其实是在充分利用设备的多核,它的核心就是两块:任务、队列。GCD之于多线程的实现,避免不了这几个概念:同步/异步、串行/并发。

 

同步与异步决定了是否可以开启新的线程:

同步函数(dispatch_sync)不具备开启新线程的能力,只能在当前线程执行任务,注意,这里说的是当前线程,切不可盲目理解为主线程(main_queue),调起同步函数的方法在哪个线程,那么同步函数就在哪个线程执行任务。比如说,控制器的touchesBegan是在主线程调用的,那么在touchesBegan方法中如果使用同步函数(dispatch_sync),那么同步函数的任务将会在主线程中执行。

而对于异步函数,它是具备开启新线程的能力的。

 

而串行与并发,则决定了任务的执行顺序:

串行队列(DISPATCH_QUEUE_SERIAL),在串行队列中添加的任务,任务会一项一项逐步执行,不会同时进行。说到这里,可能有些童鞋就会发问了:异步函数与串行队列结合使用,不就可以同时执行了吗??这么问其实不无道理,因为异步函数具有开启新线程的能力,所以感觉在串行队列中的多项任务应该可以同时执行,但是,实际情况则不会这样。异步函数与串行队列结合使用的真实情况是,因为是异步函数,所以会开启新的线程来执行任务,但是,由于是串行队列,所以只会开启一条新的线程,任务串行执行。简单的测试结果如下:

/**
 异步函数 + 串行队列:会开启新的线程,但是由于是串行,所以只会开启一条线程,任务的执行也是一步一步来
 */
- (void)asyncSerial {
    
    dispatch_queue_t queue = dispatch_queue_create("print", NULL);
    
    dispatch_async(queue, ^{
        NSLog(@"0000currentThread == %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"1111currentThread == %@",[NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"2222currentThread == %@",[NSThread currentThread]);
    });
}
输出结果如下:

2018
-12-11 11:07:46.171560+0800 TestGCD[2008:58710] 0000currentThread == <NSThread: 0x600000270800>{number = 4, name = (null)} 2018-12-11 11:07:46.172804+0800 TestGCD[2008:58710] 1111currentThread == <NSThread: 0x600000270800>{number = 4, name = (null)} 2018-12-11 11:07:46.173201+0800 TestGCD[2008:58710] 2222currentThread == <NSThread: 0x600000270800>{number = 4, name = (null)}

继续再来看并发队列,顾名思义,并发队列意指任务可以同时执行。因此,并发队列常与异步函数来结合使用。那么,同步函数与并发队列结合使用又会是什么结果呢?咱们可以测试一下嘛,MJ老师曾说过,试一下又不会怀孕,对吧??。。。。

/**
 同步函数 + 并发队列:
 */
- (void)syncConcurrent {
    // 利用系统全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 调用同步函数,将任务添加到并发队列中
    dispatch_sync(queue, ^{
        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"0currentThread == %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"1currentThread == %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (NSInteger i = 0; i < 5; i++) {
            NSLog(@"2currentThread == %@",[NSThread currentThread]);
        }
    });
}
输出结果如下:

2018
-12-11 11:09:10.428216+0800 TestGCD[2040:59724] 0currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.428898+0800 TestGCD[2040:59724] 0currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.429803+0800 TestGCD[2040:59724] 0currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.429930+0800 TestGCD[2040:59724] 0currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.430084+0800 TestGCD[2040:59724] 0currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.430203+0800 TestGCD[2040:59724] 1currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.430436+0800 TestGCD[2040:59724] 1currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.430599+0800 TestGCD[2040:59724] 1currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.430741+0800 TestGCD[2040:59724] 1currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.431526+0800 TestGCD[2040:59724] 1currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.431951+0800 TestGCD[2040:59724] 2currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.432206+0800 TestGCD[2040:59724] 2currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.432442+0800 TestGCD[2040:59724] 2currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.432673+0800 TestGCD[2040:59724] 2currentThread == <NSThread: 0x604000260500>{number = 1, name = main} 2018-12-11 11:09:10.432891+0800 TestGCD[2040:59724] 2currentThread == <NSThread: 0x604000260500>{number = 1, name = main}

通过输出结果可以看到,任务并没有同时进行,而是一项一项、逐步执行。这是因为同步函数决定了其无法开启新的线程,进而也就不存在并发执行的概念了。

 

上面把同步/异步、串行/并发这几个概念梳理了一下,那么接下来,咱们看一下在实际使用过程中需要注意的地方。

1.使用串行队列时,切不可在同步函数中再次开启该串行队列所对应的同步函数。

dispatch_queue_t queue = dispatch_queue_create("xxxxxxx", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
    NSLog(@"start");
    dispatch_sync(queue, ^{
        NSLog(@"ssssss");
    });
    NSLog(@"end");
});

上面这段示例代码,创建了一个串行队列,然后使用同步函数往队列中添加任务,然后再在这项任务中开启同步函数,并且还是往这个串行队列中添加任务。

运行这段代码,效果会是这样:控制台输出“start”后,程序就crash了。crash的原因:Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)。

这是因为串行队列中的任务在互相等待,导致任务永远执行不下去。

 

2.dispatch_barrier_async函数使用注意点

dispatch_barrier_async函数的作用在于中途截断。

dispatch_queue_t queue = dispatch_queue_create("qqqq", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"111111111111");
    });
    dispatch_async(queue, ^{
        NSLog(@"222222222222");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"barrier");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"333333333333");
    });
    dispatch_async(queue, ^{
        NSLog(@"444444444444");
    });

这段示例代码简单运用了一下dispatch_barrier_async函数。创建一个并行队列,调用异步函数王里面先添加两项任务,然后再添加barrier函数,然后再添加两项任务。

咱们来看一下执行结果:

输出结果如下:

2018
-12-11 14:19:03.567939+0800 TestGCD[3293:112304] 111111111111 2018-12-11 14:19:03.569634+0800 TestGCD[3293:112304] 222222222222 2018-12-11 14:19:03.570057+0800 TestGCD[3293:112304] barrier 2018-12-11 14:19:03.570508+0800 TestGCD[3293:112304] 333333333333 2018-12-11 14:19:03.570689+0800 TestGCD[3293:112304] 444444444444

通过输出结果,可以看到barrier函数的切断效果。因为是异步函数 + 并发队列,如果barrier函数不存在的话,那么所添加的四项任务的执行顺序是不一定谁先谁后的,但是由于barrier函数的存在,使得在barrier函数之前添加的任务肯定能够barrier函数之前执行完。

让部分任务优先执行,这样的类似的需求,可能会在日常开发中遇到,这时候就可以用barrier函数来解决了,不过,需要注意的是,barrier函数不能与全局并发队列集合使用,具体原因暂时还没有研究清楚。(有知晓的园友请指教一下。)

ps:可能有些童鞋还不懂什么是全局并发队列。dispatch_get_global_queue,即全局并发队列,是系统提供的常用的并发队列,日常开发中,无需创建新的并发队列,可以直接使用这个全局的并发队列。同样的,与其对应的,dispatch_get_main_queue()为主队列,是全局的串行队列。

 

3.dispatch_once与懒加载

dispatch_once,即一次性代码。一次性的一些操作可以放在这个方法里面,但是这里说到的一次性,是整个程序全局性的一次性,在整个程序运行期间,只会加载一次。而对于懒加载来说,懒加载的属性所在的类,只要新创建一个对象并且使用到了这个属性,那么肯定就会有运行懒加载的代码。因此二者有区别,切不可在懒加载的业务代码中运用一次性代码的方式来处理。(这个可以自行测试一下,本文就不再罗列相关代码了。)

posted @ 2018-12-11 15:04  Foundation  阅读(696)  评论(0)    收藏  举报