GCD浅析

1.关于GCD

Grand Central Dispatch是异步执行任务的技术之一。我们先看一个简单的示例:

 1 - (void)doSomethingInBackground {
 2     [self performSelectorInBackground:@selector(startWork) withObject:nil];
 3 }
 4 
 5 - (void)startWork {
 6     //长时间处理操作
 7     NSLog(@"进行长时间处理");
 8     
 9     [self performSelectorOnMainThread:@selector(endWork) withObject:nil waitUntilDone:YES];
10 }
11 
12 - (void)endWork {
13     NSLog(@"完成工作并刷新页面");
14 }

这个示例实现的功能很简单:在后台线程中执行长时间的处理,处理结束后,在主线程中使用处理结果。上面实现方式是使用NSObject类的performSelectorInBackground和performSelectorOnMainThread来实现的。下面我们用GCD的方式来实现同样功能,做个对比:

1     dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
2     dispatch_async(queue, ^{
3         NSLog(@"进行长时间处理");
4         dispatch_async(dispatch_get_main_queue(), ^{
5             NSLog(@"完成工作并刷新页面");
6         });
7     });

通过上面的比较,我们可以知道,GCD更加的简洁,同样的功能,代码量减少了差不多一半。

2.多线程编程

什么是线程?线程(英语:thread)是操作系统能够进行运算调度的最小单位(来自维基百科)。iOS应用程序在启动后,首先会将包含在应用程序中的CPU指令配置到内存中,CPU从应用程序指定的地址开始,一个一个去执行CPU指令,一个CPU一次只能执行一个指令。由于现在的物理CPU芯片实际上有64个(64核)CPU,如果1个CPU核虚拟为两个CPU核工作,那么使用多个CPU核同时工作就是很正常的事情了。这种存在多个CPU核工作的即为“多线程”。

在实际开发中,多线程编程是一种容易发生问题的编程技术。我们常见的有这么几种:

  • 数据竞争:多个线程更新相同的资源会导致数据的不一致;
  • 死锁:多个线程相互持续等待;
  • 内存消耗高:每个线程都是有内存开销的,太多线程会大量的消耗内存。

多线程存在上面的这些问题,那么我们是不是应该弃用呢?答案是否定的,因为使用多线程编程,可以让我们在执行长时间的处理时,保证用户界面的即时响应。

3.GCD的API

3.1Dispatch Queue

分发队列主要有两类:

  • Serial Dispatch Queue:等待现在执行任务处理结束;
  • Concurrent Dispatch Queue:不等待现在执行处理结束;

听概念是不是感觉很抽象?没关系,下面我们来看看代码。

 1     dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL);
 2 
 3     dispatch_async(queue, ^{
 4         NSLog(@"blk1");
 5     });
 6     
 7     dispatch_async(queue, ^{
 8         NSLog(@"blk2");
 9     });
10     
11     dispatch_async(queue, ^{
12         NSLog(@"blk3");
13     });
14     
15     dispatch_async(queue, ^{
16         NSLog(@"blk4");
17     });
18     
19     dispatch_async(queue, ^{
20         NSLog(@"blk5");
21     });

可以看到,上面我们使用的是Serial Dispatch Queue,运行效果会怎么样呢?

可以看到:使用Serial Dispatch Queue,因为要等待正在执行的任务结束,并且同事执行的处理数只能有一个,所以会依次打印blk1、blk2、blk3、blk4、blk5。

现在将上面示例的DISPATCH_QUEUE_SERIAL改为DISPATCH_QUEUE_CONCURRENT,结果会怎么样呢?

结论是:使用Concurrent Dispatch Queue时,因为不用等待正在执行的任务结束,可以并行执行多个任务,也就是使用多个线程同时执行,但并行执行任务的数量取决于系统当前的状态。上面并行执行的线程情况截图如下:

3.2dispatch_queue_create

上面介绍了dispatch queue,那么怎么创建分发队列呢?我们可以使用GCD的API函数。

1 dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL);

dispatch_queue_create函数的第一个参数指定队列的名称,这个名称会在XCode调试中显示,也会显示在应用程序崩溃时所生成的CrashLog中。因此我们命名最好时取一些有意义的,以方便我们来定位问题和代码。例如下面的代码:

 1     dispatch_queue_t queue1 = dispatch_queue_create("com.gcd.test1", DISPATCH_QUEUE_SERIAL);
 2     dispatch_queue_t queue2 = dispatch_queue_create("com.gcd.test2", DISPATCH_QUEUE_SERIAL);
 3     dispatch_queue_t queue3 = dispatch_queue_create("com.gcd.test3", DISPATCH_QUEUE_SERIAL);
 4     dispatch_queue_t queue4 = dispatch_queue_create("com.gcd.test4", DISPATCH_QUEUE_SERIAL);
 5     dispatch_queue_t queue5 = dispatch_queue_create("com.gcd.test5", DISPATCH_QUEUE_SERIAL);
 6 
 7     dispatch_async(queue1, ^{
 8         NSLog(@"blk1");
 9     });
10     
11     dispatch_async(queue2, ^{
12         NSArray *arr = @[@1, @2, @3];
13         NSLog(@"%@", arr[4]);
14     });
15     
16     dispatch_async(queue3, ^{
17         NSLog(@"blk3");
18     });
19     
20     dispatch_async(queue4, ^{
21         NSLog(@"blk4");
22     });
23     
24     dispatch_async(queue5, ^{
25         NSLog(@"blk5");
26     });

从代码可以看到,queue2会崩溃,实际运行后的CrashLog如下:

dispatch_queue_create函数的第二个参数指定创建队列的类型。如果想创建Serial Dispatch Queue,那么指定类型为DISPATCH_QUEUE_SERIALNULL;如果想创建Concurrent Dispatch Queue,那么指定类型为DISPATCH_QUEUE_CONCURRENT

【注意】:dispatch_queue_create函数可以创建任意个Dispatch Queue,当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue会并行执行,系统对于每一个Serial Dispatch Queue会生成并使用一个线程。如果生成大量的Serial Dispatch Queue,那么就会创建很多线程。过多使用线程,会消耗大量内存,降低系统的响应性能。

3.3Main Dispatch Queue/Global Dispatch Queue

 上面说了用dispatch_queue_create函数生成Dispatch Queue,现在聊一下系统标准提供的几个Dispatch Queue。

名称Dispatch Queue类型说明
Main Dispatch QueueSerial Dispatch Queue主线程执行
Global Dispatch Queue(High Priority)Concurrent Dispatch Queue执行优先级:高
Global Dispatch Queue(Default Priority)Concurrent Dispatch Queue执行优先级:默认
Global Dispatch Queue(Low Priority)Concurrent Dispatch Queue执行优先级:低
Global Dispatch Queue(Background Priority)Concurrent Dispatch Queue执行优先级:后台

怎么创建?

1     dispatch_queue_t mainQueue = dispatch_get_main_queue();
2     dispatch_queue_t highQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
3     dispatch_queue_t defaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
4     dispatch_queue_t lowQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
5     dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

3.4dispatch_after

从这个函数名称,我们也能猜到它的大概作用:在xx时间后,再做某件事情。

    NSLog(@"Before:%@", [NSDate date]);
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"After:%@", [NSDate date]);
    });

【注意】:dispatch_after函数并不是在指定时间后执行处理,而只是在指定时间后追加处理(也就是Block)到Dispatch Queue。

3.5Dispatch_Group

假设现在有个新的需求:需要在 Dispatch Queue中的多个任务完成之后,再做某件事情。当然大家会马上反应过来,用Serial Dispatch Queue即可。但如果应用场景是Concurrent Dispatch Queue或者多个Dispatch Queue呢?

这个时候就需要Dispatch Group了。

 1     dispatch_queue_t queue1 = dispatch_queue_create("com.gcd.test1", DISPATCH_QUEUE_SERIAL);
 2     dispatch_queue_t queue2 = dispatch_queue_create("com.gcd.test2", DISPATCH_QUEUE_SERIAL);
 3     dispatch_queue_t queue3 = dispatch_queue_create("com.gcd.test3", DISPATCH_QUEUE_SERIAL);
 4     dispatch_queue_t queue4 = dispatch_queue_create("com.gcd.test4", DISPATCH_QUEUE_SERIAL);
 5     dispatch_queue_t queue5 = dispatch_queue_create("com.gcd.test5", DISPATCH_QUEUE_SERIAL);
 6     
 7     dispatch_group_t group = dispatch_group_create();
 8     dispatch_group_async(group, queue1, ^{
 9         NSLog(@"blk1");
10     });
11     dispatch_group_async(group, queue2, ^{
12         NSLog(@"blk2");
13     });
14     dispatch_group_async(group, queue3, ^{
15         NSLog(@"blk3");
16     });
17     dispatch_group_async(group, queue4, ^{
18         NSLog(@"blk4");
19     });
20     dispatch_group_async(group, queue5, ^{
21         NSLog(@"blk5");
22     });
23     
24     dispatch_group_notify(group, dispatch_get_main_queue(), ^{
25         NSLog(@"done");
26     });

多次运行,我们发现打印done总是在最后:

3.6dispatch_barrier_async

闭上眼睛,来想象这么一个场景:假设我们有一个本地数据库,会频繁的去读取数据和写入数据;写入是一个特殊的操作,如果在写入的时候有其他的读取操作,那么很有可能会读到脏数据,甚至也可能会造成程序的崩溃;如果只是单纯的读取操作,那么多个并行就不会发生问题。

针对这种场景,我们该怎么办呢?

 1     dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
 2     
 3     dispatch_async(queue, ^{
 4         NSLog(@"blk1");
 5     });
 6     dispatch_async(queue, ^{
 7         NSLog(@"blk2");
 8     });
 9     dispatch_async(queue, ^{
10         NSLog(@"blk3");
11     });
12     dispatch_barrier_async(queue, ^{
13         NSLog(@"blk0");
14     });
15     dispatch_async(queue, ^{
16         NSLog(@"blk4");
17     });
18     dispatch_async(queue, ^{
19         NSLog(@"blk5");
20     });
21     dispatch_async(queue, ^{
22         NSLog(@"blk6");
23     });

多次运行看结果:

通过多次运行,我们发现,不管怎么变化,blk0的打印永远在blk1、blk2、blk3的后面,blk4、blk5、blk6的前面。

3.7dispatch_sync

前面一直在说的都是“async”,意思就是非同步的将Block追加到指定的Dispatch Queue,dispatch_async函数不做任何等待;

对应的就有“sync”,意思就是同步的将Block追加到指定的Dispatch Queue。在追加Block结束之前,dispatch_sync函数会一直等待。

 1     dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
 2     dispatch_sync(queue, ^{
 3         NSLog(@"blk1");
 4     });
 5     dispatch_sync(queue, ^{
 6         NSLog(@"blk2");
 7     });
 8     dispatch_sync(queue, ^{
 9         NSLog(@"blk3");
10     });
11     dispatch_sync(queue, ^{
12         NSLog(@"blk4");
13     });
14     dispatch_sync(queue, ^{
15         NSLog(@"blk5");
16     });
17     
18     NSLog(@"主线程");

多次运行看看结果,发现结果总是固定的,另外查看线程,执行都是在主线程。

如果使用dispatch_async呢?结果怎样?多次运行,可以发现顺序是随机的。

学到这里,我们已经接触到了这么4个概念:同步、异步、串行、并行。下面总结一下:

  • 队列分为串行和并行,任务的执行分为同步和异步,异步是多线程的代名词,异步在实际应用中会开启新的线程,执行耗时操作;
  • 队列只是负责任务的调度,而不负责任务的执行,任务是在线程中执行。
  • 同步意味着在当前线程中执行,异步意味着会开启新的线程。

下面我们继续来细细品味一下这4个概念:串行同步、串行异步、并行同步、并行异步。

 3.7.1串行同步

 1     dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL);
 2     dispatch_sync(queue, ^{
 3         NSLog(@"blk1 thread:%@", [NSThread currentThread]);
 4     });
 5     dispatch_sync(queue, ^{
 6         NSLog(@"blk2 thread:%@", [NSThread currentThread]);
 7     });
 8     dispatch_sync(queue, ^{
 9         NSLog(@"blk3 thread:%@", [NSThread currentThread]);
10     });
11     dispatch_sync(queue, ^{
12         NSLog(@"blk4 thread:%@", [NSThread currentThread]);
13     });
14     dispatch_sync(queue, ^{
15         NSLog(@"blk5 thread:%@", [NSThread currentThread]);
16     });
17     
18     NSLog(@"主线程 thread:%@", [NSThread currentThread]);

运行结果:

结论:取出一个任务不放进别的线程,阻塞当前线程(不开辟新的线程,顺序执行),等任务执行完成,开始下一个任务。

 3.7.2串行异步

 1     dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_SERIAL);
 2     dispatch_async(queue, ^{
 3         NSLog(@"blk1 thread:%@", [NSThread currentThread]);
 4     });
 5     dispatch_async(queue, ^{
 6         NSLog(@"blk2 thread:%@", [NSThread currentThread]);
 7     });
 8     dispatch_async(queue, ^{
 9         NSLog(@"blk3 thread:%@", [NSThread currentThread]);
10     });
11     dispatch_async(queue, ^{
12         NSLog(@"blk4 thread:%@", [NSThread currentThread]);
13     });
14     dispatch_async(queue, ^{
15         NSLog(@"blk5 thread:%@", [NSThread currentThread]);
16     });
17     
18     NSLog(@"主线程 thread:%@", [NSThread currentThread]);

运行结果:

结论:取出一个任务放进别的线程,不阻塞当前线程,等任务执行完成,开始下一个任务。 

3.7.3并行同步

 1     dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
 2     dispatch_sync(queue, ^{
 3         NSLog(@"blk1 thread:%@", [NSThread currentThread]);
 4     });
 5     dispatch_sync(queue, ^{
 6         NSLog(@"blk2 thread:%@", [NSThread currentThread]);
 7     });
 8     dispatch_sync(queue, ^{
 9         NSLog(@"blk3 thread:%@", [NSThread currentThread]);
10     });
11     dispatch_sync(queue, ^{
12         NSLog(@"blk4 thread:%@", [NSThread currentThread]);
13     });
14     dispatch_sync(queue, ^{
15         NSLog(@"blk5 thread:%@", [NSThread currentThread]);
16     });
17     
18     NSLog(@"主线程 thread:%@", [NSThread currentThread]);

运行结果:

结论:取出一个任务不放进别的线程,阻塞当前线程(不开辟新的线程,顺序执行),等任务执行完成,开始下一个任务。

3.7.4并行异步

    dispatch_queue_t queue = dispatch_queue_create("com.gcd.test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"blk1 thread:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"blk2 thread:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"blk3 thread:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"blk4 thread:%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"blk5 thread:%@", [NSThread currentThread]);
    });
    
    NSLog(@"主线程 thread:%@", [NSThread currentThread]);

运行结果:

结论:取出一个任务放进别的线程,不阻塞当前线程,不等任务执行完成,开始下一个任务(会开启多个线程)。

3.7.5总结

 串行(Serial Dispatch Queue)并行(Concurrent Dispatch Queue)主线程(Main Queue)
同步(sync)

没有开启新线程

串行执行任务

没有开启新线程

串行执行任务

没有开启新线程

串行执行任务

异步(async)

开启一个新线程

串行执行任务

开启多个新线程

并行执行任务

没有开启新线程

串行执行任务

3.7.6死锁

死锁是同步(sync)线程操作中比较常见的一个问题,要理解死锁,我们要深刻的理解这么一句话:串行与并行说的是队列,队列是用来调度和存放任务的;而同步与异步,针对的是线程区别在于,同步会阻塞当前线程,必须要等待同步线程中的任务执行完成以后,才能继续执行下一任务;而异步线程则是不用等待。下面我们来分析几个示例,更细的理解死锁是怎么来的,又是怎么没的。

示例1:

1     NSLog(@"任务1 thread:%@", [NSThread currentThread]);  //任务1
2     dispatch_sync(dispatch_get_main_queue(), ^{
3         NSLog(@"任务2 thread:%@", [NSThread currentThread]);  //任务2
4     });
5     NSLog(@"任务3 thread:%@", [NSThread currentThread]); // 任务3

结果:

发现后面的任务2和任务3都没有打印。

3.8dispatch_once

从函数名就知道,这个函数用于保证在应用程序执行中只执行一次指定处理。通常用于实现单例。

1 + (HDFNetConfig *)sharedConfig {
2     static id sharedInstance = nil;
3     static dispatch_once_t onceToken;
4     dispatch_once(&onceToken, ^{
5         sharedInstance = [[self alloc] init];
6     });
7     return sharedInstance;
8 }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值