[iOS]GCD知识点梳理

1.1、什么是GCD

Grand Central Dispatch (GCD)是异步执行任务的技术之一。它用非常简洁的技术方式,实现了极为复杂的多线程编程。

下面是GCD使用得最多的一个例子:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 处理耗时的操作
        dispatch_async(dispatch_get_main_queue(), ^{
            // 主线程刷新用户界面
        });
    });

可以看到GCD采用的是block的标识句法,显得非常简洁。


1.2、多线程编程

什么是线程?

1个CPU执行的的CPU命令列为一条无分叉路径。

什么是多线程?

这种无分叉路径不只1条,存在多条时即为:“多线程”。

什么是多线程编程?

由于使用多线程的应用程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列的执行多个线程一样。而且在具有多个CPU的情况下,是真的提供了多个CPU核并列执行多个线程的技术。这种利用多线程编程的技术成为多线程编程。

应用程序主线程及阻塞

应用程序在启动的时候,通过最先执行的线程,即“主线程”来描述用户界面、处理触摸屏幕的事件等。如果在主线程中进行长时间的处理,就会妨碍主线程的执行(阻塞)。


1.3、Dispatch Queue

Dispatch Queue是什么呢?如其名称所示,是执行处理的等待队列。应用程序编程人员通过dispatch_async函数等API,在block中记述想要执行的处理并将其追加到Dispatch Queue中。Dispatch Queue追加的顺序(先进先出FIFO,First-In-First-Out)执行。

在执行处理时存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。Serial Dispatch Queue称为:串行队列,同时执行的处理只有1个,而Concurrent Dispatch Queue称为:并行队列,同时执行的处理可以有多个,这时执行的顺序会根据处理的内容和系统的状态发生改变。


1.4、Dispatch Queue Create

第一种方法是通过GCD的API生成Dispatch Queue。

通过dispatch_queue_create函数可以生成Dispatch Queue。以下源代码生成了Serial Dispatch Queue。

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd", NULL);

dispatch_queue_create函数可以生成任意多个Dispatch Queue。当生成多个Serial Dispatch Queue的时候,各个Serial Dispatch Queue将并行执行。虽然在1个Serial Dispatch Queue中一次只能执行1个任务,但是如果将处理分为4个Serial Dispatch Queue,就可以同时并行处理4个任务。

dispatch_queue_create函数的第一个参数指定Serial Dispatch Queue的名称,第二个参数如果是串行队列,可以像上面列子一样设置为NULL,如果是生成并行队列,则指定为DISPATCH_QUEUE_ CONCURRENT。

dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.example.gcd", DISPATCH_QUEUE_CONCURRENT);


1.5 、Main Dispatch Queue/Global Dispatch Queue

Main Dispatch Queue真如名称中包含main一样,是在主线程中执行的Dispatch Queue。因为主线程只有一个,所以Main Dispatch Queue自然就是Serial Dispatch Queue。

Global Dispatch Queue是所有应用程序都能够使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个生成Concurrent Dispatch Queue。

Global Dispatch Queue有四个优先级。分别是:高优先级(High Priority)、默认优先级(Default Priority)、低优先级(Low Priority)和后台优先级(Backgroud Priority)。

各个Dispatch Queue的获取方法:

// Main Dispatch Queue
    dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
    
    // Global Dispatch Queue 高优先级
    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    // Global Dispatch Queue 默认优先级
    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    // Global Dispatch Queue 低优先级
    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    
    // Global Dispatch Queue 后台优先级
    dispatch_queue_t globalDispatchQueueBackgroud = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

1.6、Dispatch Group

追加到Dispatch_queue中的多个处理全部结束之后想执行结束处理,对于并发队列(Concurrent Dispatch_queue)而言,需要使用Dispatch Group来实现。

比如,追加3个block到Global Dispatch Queue中,这些block会全部执行完毕,就会执行Main Dispatch Queue中结束处理用的block。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk0");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"done");
    });

源代码执行结果如下:

blk0

blk1

blk2

done

由于向Global Dispatch Queue即Concurrent Dispatch Queue中追加处理,多个线程并行执行,所以追加处理的顺序不定。执行时会发生变化,但是执行结果的done一定是最后执行的。

如果在实际项目开发中,遇到要等待几个线程任务执行结束后再来执行某个任务,可以利用Dispatch Group的这种特性。


1.7、dispatch_barrier_async

dispatch_barrier_async 函数会等到追加到Concurrent Dispatch Queue上的并行执行全部完毕之后,再将指定的处理追加到该Concurrent Dispatch Queue中。然后在由dispatch_barrier_async函数追加的处理执行结束后,Concurrent Dispatch Queue才恢复为一般动作,追加到该Concurrent Dispatch Queue的处理又开始并行执行。

这里需要注意:Concurrent Dispatch Queue指的是通过dispatch_queue_create创建的并行队列,而对全局并发队列无效。

来看一下例子:

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier",DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"blk0");
    });
    dispatch_async(queue, ^{
        NSLog(@"blk1");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk3");
    });
    
    dispatch_barrier_async(queue, ^{
        NSLog(@"blk8");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk4");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk5");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk6");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"blk7");
    });

输出结果:

2018-05-18 22:37:55.069707+0800 20180514[1857:71006] blk2
2018-05-18 22:37:55.069707+0800 20180514[1857:71004] blk3
2018-05-18 22:37:55.069707+0800 20180514[1857:71007] blk0
2018-05-18 22:37:55.069707+0800 20180514[1857:71005] blk1
2018-05-18 22:37:55.070078+0800 20180514[1857:71005] blk8
2018-05-18 22:37:55.070247+0800 20180514[1857:71006] blk5
2018-05-18 22:37:55.070246+0800 20180514[1857:71005] blk4
2018-05-18 22:37:55.070263+0800 20180514[1857:71007] blk6
2018-05-18 22:37:55.070271+0800 20180514[1857:71004] blk7

并且多次执行查看输出结果,我们会发现:在blk8执行之前,blk0,blk1,blk2,blk3这四个任务是并发执行的,执行的顺序有可能会不同,但是因为blk8是通过dispatch_barrier_async函数添加到这四个任务之后的,所以程序必须先执行完前面四个任务后才会执行dispatch_barrier_async函数,而且在此执行之后的四个任务:blk4,blk5,blk6,blk7又开始并发的执行了。


1.8、dispatch_sync

dispatch_sync中的"sync"意味着“同步”,也就是将指定的block同步追加到指定的Disptach Queue中。在追加block之前,dispatch_sync函数会一直等待。

dispatch_sync容易造成死锁。例如:如果在主线程中执行下面的代码就会死锁:

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        NSLog(@"locked");
    });

该源代码在主线程执行block,并等待其执行结束。而实际上主线程正在执行这些源代码,所以无法执行追加到Main Dispatch Queue中的block。下面的例子也一样。

    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"lock");
        });
    });

Main Dispatch Queue 中执行的block等待Main Dispatch Queue中执行的block结束。

当然,Serial Dispatch Queue也会遇到相同的问题。

    dispatch_queue_t queue = dispatch_queue_create("com.example.queue",NULL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"lock.");
        });
    });

另外,由dispatch_barrier_async函数中含有async可推断出,相应的也有dispatch_barrier_sync函数。dispatch_barrier_sync函数的作用是在等待追加的处理全部执行结束后,再追加处理到Dispatch Queue中,此外,它还与dispatch_sync函数相同,会等待追加处理的执行结束。

在以后的编程中,我们最好慎用这些同步等待处理执行的API,因为使用这种API时,稍有不慎就会引起死锁。


1.9、dispatch_apply

dispatch_apply函数按指定次数将指定的blockz追加到指定的Dispatch Queue中,并等待全部执行结束。

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, queue, ^(size_t index) {
        NSLog(@"%zu",index);
    });

    NSLog(@"Done");

执行结果为:

2018-05-21 22:55:06.667144+0800 GCDDemo[1284:28711] 3
2018-05-21 22:55:06.667142+0800 GCDDemo[1284:28710] 1
2018-05-21 22:55:06.667138+0800 GCDDemo[1284:28709] 2
2018-05-21 22:55:06.667138+0800 GCDDemo[1284:28611] 0
2018-05-21 22:55:06.667386+0800 GCDDemo[1284:28711] 5
2018-05-21 22:55:06.667386+0800 GCDDemo[1284:28710] 4
2018-05-21 22:55:06.667397+0800 GCDDemo[1284:28709] 6
2018-05-21 22:55:06.667515+0800 GCDDemo[1284:28611] 7
2018-05-21 22:55:06.667631+0800 GCDDemo[1284:28711] 8
2018-05-21 22:55:06.667742+0800 GCDDemo[1284:28710] 9
2018-05-21 22:55:06.668403+0800 GCDDemo[1284:28611] Done

因为在Global Dispatch Queue中执行处理,所以各个处理的执行时间不定,但是输出结果中最后Done必定在最后位置,这是因为dispatch_apply函数会等待全部处理执行结束。

第一个参数是重复的次数,第二个参数为追加对象的Dispatch Queue,第三个参数是追加的处理。第三个参数的block为带有参数的block,这是为了按第一个参数重复追加block并区分各个block而使用。例如要对 NSArray类对象的所有元素执行处理时,不必一个一个编写for循环。

    NSArray *array = @[@"a",@"b",@"c"];
    dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_apply([array count], queue, ^(size_t index) {
        NSLog(@"%zu:%@",index, [array objectAtIndex:index]);
    });

    NSLog(@"Done");
    
    NSArray *array = @[@"a",@"b",@"c"];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 在Globle Dispatch Queue中非同步执行
    dispatch_async(queue, ^{
        // 等待dispatch_apply 函数中全部执行结束
        dispatch_apply([array count], queue, ^(size_t index) {
            // 并列处理包含在NSArray对象的全部对象
            NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
        });

        // dispatch_apply函数中的处理全部执行结束
        // 在Main Dispatch Queue中非同步执行
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"done");
        });
    });

执行结果:

2018-05-21 23:03:05.686519+0800 GCDDemo[1331:32924] 0:a
2018-05-21 23:03:05.686724+0800 GCDDemo[1331:32924] 1:b
2018-05-21 23:03:05.686883+0800 GCDDemo[1331:32924] 2:c
2018-05-21 23:03:05.687087+0800 GCDDemo[1331:32924] Done

这样可简单的在Global Dispatch Queue中对所有元素执行block。

另外,由于dispatch_apply 函数也与dispatch_sync函数相同,会等待执行结束,因此推荐在dispatch_async函数中非同步的执行dispatch_apply函数。

    NSArray *array = @[@"a",@"b",@"c"];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    // 在Globle Dispatch Queue中非同步执行
    dispatch_async(queue, ^{
        // 等待dispatch_apply 函数中全部执行结束
        dispatch_apply([array count], queue, ^(size_t index) {
            // 并列处理包含在NSArray对象的全部对象
            NSLog(@"%zu:%@",index,[array objectAtIndex:index]);
        });

        // dispatch_apply函数中的处理全部执行结束
        // 在Main Dispatch Queue中非同步执行
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"done");
        });
    });


2.10、dispatch_suspend / dispatch_resume

当追加大量处理到Dispatch Queue时, 有时希望不执行已追加的处理。在这种情况下,只需要挂起Dispatch Queue即可。当可以执行时再恢复。

dispatch_suspend函数挂起指定的Dispatch Queue。

disaptch_resume函数恢复指定的Dispatch Queue。

dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"开始挂起");
    dispatch_suspend(queue);
    sleep(1);
    NSLog(@"1秒后恢复执行");
    dispatch_resume(queue);
    NSLog(@"done.");

打印结果:

2018-05-22 22:36:30.964967+0800 GCDDemo[1106:28846] 开始挂起
2018-05-22 22:36:31.966365+0800 GCDDemo[1106:28846] 1秒后恢复执行
2018-05-22 22:36:31.966581+0800 GCDDemo[1106:28846] done.


2.11、Dispatch Semaphore

dispatch semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。所谓信号,类似于过马路时常用的手旗。可以通过时举旗,不可通过时放下手旗。dispatch semaphore中使用计数来实现,计数为0时等待,计数为1或大于1时,减去1而不等待。

dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    // 生成Dispatch Semaphore
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    NSMutableArray *array = [[NSMutableArray alloc] init];
    for (int i=0; i<3; i++) {
        dispatch_async(queue, ^{
            // 等待Dispatch Semaphore,一直等待直到Dispatch Semaphore的计数大于等于1
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            
            /*
             * 由于Dispatch Semaphore的计数值达到大于等于1
             * 所以讲Dispatch Semaphore的计数值减去1
             * dispatch_semaphore_wait函数执行返回。
             * 即执行到此时的
             * Dispatch Semaphore的计数为0
             * 由于可访问NSMutableArray类对象的线程只有1个
             * 因此可以安全的进行更新
             */
            [array addObject:[NSNumber numberWithInt:i]];
            NSLog(@"array---->%@",array);
            
            /*
             * 排他控制处理结束
             * 通过dispatch_semaphore_signal函数将Dispatch Semaphore的
             * 计数值加1
             * 如有通过dispatch_semaphore_wait函数等待Dispatch Semaphore的计数值增加的线程,就由最先等待的线程执行。
             */
            
            dispatch_semaphore_signal(semaphore);
            NSLog(@"同步执行结束。");
        });
    }

执行结果:

2018-05-22 23:29:06.665072+0800 GCDDemo[1701:63950] array---->(
    0
)
2018-05-22 23:29:06.665349+0800 GCDDemo[1701:63950] 同步执行结束。
2018-05-22 23:29:06.665535+0800 GCDDemo[1701:63950] array---->(
    0,
    1
)
2018-05-22 23:29:06.666285+0800 GCDDemo[1701:63950] 同步执行结束。
2018-05-22 23:29:06.666805+0800 GCDDemo[1701:63950] array---->(
    0,
    1,
    2
)
2018-05-22 23:29:06.667062+0800 GCDDemo[1701:63950] 同步执行结束。

更多应用场景:

/*应用场景*/
    
    /* 1.保持线程同步,将异步操作转换为同步操作
     * 说明:由于创建的semaphore的信号量为0,在程序执行到dispatch_semaphore_wait
     * 时候会阻塞当前线程,由于等待的时间是DISPATCH_TIME_FOREVER,所以这个等待是永久的,一直
     * 等到block里面的异步操作执行完毕才停止等待。
     * block体内修改j的值,使j的值等于100。所以最后打印的j值为100。
     */
    dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore  = dispatch_semaphore_create(0);
    __block int j = 0;
    dispatch_async(queue, ^{
        j = 100;
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"j------>%d",j);
    
    /* 2.线程加锁。
     *
     */
    
    dispatch_queue_t que = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semp = dispatch_semaphore_create(1);
    for (int i = 0; i < 3; i++) {
        dispatch_async(que, ^{
            // 相当于加锁
            dispatch_semaphore_wait(semp, DISPATCH_TIME_FOREVER);
            NSLog(@"i------>%d",i);
            // 相当于解锁
            dispatch_semaphore_signal(semp);
        });
    }


2.12、dispatch_once

dispatch_once 函数是保证在应用程序执行中只执行一次的API,通常我们在编写单例时用到它。

static Userinfo *shareInfo = nil;
+ (instancetype)shared {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shareInfo = [[Userinfo alloc] init];
    });
    
    return shareInfo;
}


2.13、dispatch_after

经常有这样的情况,想在3秒后执行处理。这时候可以使用dispatch_after函数来实现。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"wait at least three seconds.");
    });

需要注意的是,dispatch_after并不是在指定的时间后执行处理。而只是在指定的时间追加到Dispatch Queue。

第一个参数指定时间用的是dispatch_time_t类型的值。该值由dispatch_time函数或dispatch_walltime函数生成。

NSEC_PER_SEC是以秒为单位,如果要表示毫秒则用:NSEC_PER_MSEC。

第二个参数是要追加处理的Dispatch Queue。

第三个参数指定要处理的block。

(完)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值