Grand Central Dispatch(GCD)详解(转)
概述
GCD是苹果异步执行任务技术,将应用程序中的线程管理的代码在系统级中实现。开发者只需要定义想要执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可以统一管理,也可执行任务,这样比以前的线程更有效率。
GCD的使用
dispatch_sync与dispatch_async
-
dispatch_sync
synchronous同步,一旦调用dispatch_sync方法,那么指定的处理(block)追加到指定Dispatch Queue中在执行结束之前该函数都不会返回,也就是说当前的线程会阻塞,等待dispatch_sync在指定线程执行完成后才会继续向下执行。 -
dispatch_async
synchronous异步,一旦调用dispatch_async方法,那么指定的处理(block)追加到指定的Dispatch Queue中,dispatch_async不会做任何等待立刻返回,当前线程不受影响继续向下执行。
注意
使用dispatch_sync容易造成死锁,一般情况下应该使用dispatch_async,例如
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"1");
});
NSLog(@"2"); //这行代码不会输出
因为主线程等待dispatch_sync执行结束,而dispatch_sync又要在主线程中执行block。所以造成了死锁下面的代码不会执行。一般情况下应使用dispatch_async。
Dispatch Queue
Dispatch Queue是执行处理的等待队列,通过Block把想要执行的处理追加到Dispatch Queue中,根据追加的顺序通过FIFO(先进先出)来执行处理。
有两种类型的队列,一种是Serial Dispatch Queue(串行队列)等待正在执行中的处理当处理结束时再执行队列中下一个处理。一种是Concurrent Dispatch Queue(并发队列)不等待现在执行中的处理。
| Dispatch Queue种类 | 说明 |
|---|---|
| Serial Dispatch Queue | 等待现在执行中的处理结束 |
| Concurrent Dispatch Queue | 不等待现在执行中的处理结束 |
用代码详细说明两种队列
dispatch_async(queue,block0);
dispatch_async(queue,block1);
dispatch_async(queue,block2);
dispatch_async(queue,block3);
dispatch_async(queue,block4);
当queue为Serial Dispatch Queue时输出
block0
block1
block2
block3
block4
当queue为Concurrent Dispatch Queue时输出
block1
block0
block2
block4
block3
-
Serial Dispatch Queue
当上面的queue为Serial Dispatch Queue时按顺序执行,先执行block0执行结束后执行block1、block2依次类推,因为是串行执行所以系统此时只开辟了一个线程来处理 -
Concurrent Dispatch Queue
当上面的queue为Concurrent Dispatch Queue时,因为不用等待执行中的处理结束,所以首先执行block0,不管block0是否结束都开始执行后面的block1,不等block1执行结束都执行block2依次类推。以为是并发执行,实际上是开辟了多个线程同时执行多个处理。
关于Concurrent Dispatch Queue线程问题 Concurrent Dispatch Queue中并行处理根据Dispatch Queue中的处理数量,机器CPU负载等一些状态来决定应该开辟多少个线程来处理。假如此时只能开辟三个线程但是要处理5个block事件系统应是下面这样处理:
| 线程0 | 线程1 | 线程2 |
|---|---|---|
| block0 | block1 | block2 |
| block3 | block4 |
此时线程0处理block0,线程1处理block1,线程2处理block2。当block0执行玩后执行block3,可能此时block1还没有执行完,只有block1执行结束后才会执行block4。所以说并发队列执行的顺序是不确定的。
Main Dispatch Queue与Global Dispatch Queue
系统已经为我们提供了几种Dispatch Queue,不用我们去主动创建。Main Dispatch Queue把处理追加到当前的主线程RunLoop中执行。Global Dispatch Queue是把处理追加到一个Concurrent Dispatch Queue队列中处理。Global Dispatch Queue有四个优先级,系统提供的Dispatch Queue如下表所示:
| 名称 | Dispatch Queue 的种类 | 说明 |
|---|---|---|
| Main Dispatch Queue | Serial 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 | 执行优先级(后台) |
系统提供的Dispatch Queue获取
dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0); dispatch_queue_t globalQueueDefau = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_queue_t globalQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); dispatch_queue_t globalQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue
自己通过dispatch_queue_creat函数创建的Dispatch Queue不管是串行队列还是并发的队列的优先级都是与Global Dispatch Queue的默认优先级相同,使用dispatch_set_target_queue可以变更Dispatch Queue的优先级 dispatch_set_target_queue使用
dispatch_queue_t serialQueue = dispatch_queue_create("com.test.serialQueue", NULL);
dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_set_target_queue(serialQueue, globalQueueHigh);
上面的代码实现了通过dispatch_queue_creat创建的串行队列优先级默认变成了最高优先级,实现的效果是当有多个默认优先级的Serial Dispatch Queue并发执行时,如果设置了某一个Serial Dispatch Queue优先级为最高,那么先执行这个最高优先级的队列,然后再并发执行其他优先级相同的队列。
dispatch_after
如果想在某一时间后执行某一操作,实现定时器的效果可以用dispatch_after来实现
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);
//从当前时间开始的10秒后
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"");
}); //将10秒后将要执行的操作追加到主线程进行执行
注意
上面的代码中dispatch_after并不是在10秒之后执行某一操作,而是在10秒后把要执行的操作追加到主线程中。比如主线程每0.01秒执一次RunLoop,那么这个追加操作最快10秒执行,最慢10+0.01秒执行。
Dispatch Group
上面介绍到如果使用Concurrent Dispatch Queue的话是不能确定队列中任务的执行顺序的,如果Concurrent Dispatch Queue中有三个任务要在这三个任务都执行结束后进行某个操作,这时就需要用到Dispatch Group。
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(@"block0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"Finish");
});
代码执行结果如下
block2
block1
block0
Finish
当追加到Dispatch Group中的处理全部结束时,dispatch_group_notify将会执行追加的Block。
dispatch_group_wait
与dispatch_group_notify类似dispatch_group_wait也可以达到相同的效果
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(@"block0");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block1");
});
dispatch_group_async(group, queue, ^{
NSLog(@"block2");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"Finish");
执行结果如下
block2
block1
block0
Finish
dispatch_group_wait的效果是等待group追加的操作全部执行完后再执行下面的代码,第二个参数表示等待的时间,DISPATCH_TIME_FOREVER表示一直等待group的处理结果。直到处理完成才执行下面的代码。
如果只想等待一段指定的时间的话改变DISPATCH_TIME_FOREVER即可
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
NSLog(@"Finish");
}
else{}
上面代码表示只等待1秒不管group中的处理是否全部完成都要执行下面的代码,当result = 0表示group中的处理已经处理完成,否则没有完成。
dispatch_barrier_async
如果在Concurrent Dispatch Queue中追加五个操作,这时想先并发执行前三个操作,等前三个操作都执行结束后再并发的执行后两个操作,这时就需要用到dispatch_barrier_async函数,具体实现如下:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
NSLog(@"block0");
});
dispatch_async(queue, ^{
NSLog(@"block1");
});
dispatch_async(queue, ^{
NSLog(@"block2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier");
});
dispatch_async(queue, ^{
NSLog(@"block3");
});
dispatch_async(queue, ^{
NSLog(@"block4");
});
代码执行结果如下
block1
block0
block2
barrier
block3
block4
使用dispatch_barrier_async会等待在它之前追加到Concurrent Dispatch Queue中的所有操作都执行结束之后,再执行在它之后追加到Concurrent Dispatch Queue中的操作。
与Dispatch Group的区别
Dispatch Group是等待追加到它队列里面的所有操作执行结束。而dispatch_barrier_async是等待在它之前追加到它对列里面的操作。一个是等待队列执行结束,一个是等待队列中某些操作执行结束。
dispatch_apply
dispatch_apply是按照指定的次数把操作(block)追加到指定的Dispatch Group中
示例1
dispatch_queue_t queueSerial = dispatch_queue_create("com.myProject.queueSerial", NULL);
dispatch_apply(5, queueSerial, ^(size_t index) {
NSLog(@"%zu",index);
});
运行结果
0
1
2
3
4
示例2
dispatch_queue_t queueCurrnt = dispatch_queue_create("com.myProject.queueCurrnt", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, queueCurrnt, ^(size_t index) {
NSLog(@"%zu",index);
});
运行结果
3
0
1
2
4
dispatch_apply第一个参数是要向队列中追加几次操作,第二个参数是将要追加操作的次数,第三个参数是用来区分第几次追加的操作。示例1中是向串行队列Serial Dispatch Queue追加操作。示例2中是向并发队列Concurrent Dispatch Queue追加操作。
dispatch_queue_create
在上面的示例中用到了dispatch_queue_create函数这个是用来创建Serial Dispatch Queue与Concurrent Dispatch Queue。这个函数第一个参数是队列的标识符,标识符的写法最好按照域名倒写的方法来表示Dispatch Queue,这样方便在调试中查看。第二个参数表示创建队列的类型当为NULL表示创建串行队列,DISPATCH_QUEUE_CONCURRENT表示创建并行队列。
dispatch_suspend与dispatch_resume
如果再进行某个操作时,不想执行队列中的操作,在这个操作完成时再执行队列中的操作,这时用dispatch_suspend会挂起当前的队列,此时不会执行队列中追加的操作。而用dispatch_resume会恢复挂起的队列。dispatch_suspend与dispatch_resume必须成对调用,有挂起就应该有恢复。
dispatch_queue_t queue = dispatch_queue_create("com.myProject.queueCurrnt", NULL);
dispatch_suspend(queue);
dispatch_async(queue, ^{
for (unsigned int i = 0; i<10; i++) {
NSLog(@"Concurrent");
}
});
for (unsigned int i = 0; i<10; i++) {
NSLog(@"Serial");
}
dispatch_resume(queue);
如果没有dispatch_suspend与dispatch_resume那么"Concurrent"与"Serial"会交替的输出,如果使用dispatch_suspend会把队列挂起然后执行下面的代码当"Serial"全部输出之后dispatch_resume恢复队列开始输出"Concurrent"。
Dispatch Semaphore
当用Concurrent Dispatch Queue对数据库操作时容易发生数据竞争,当有100条数据要进行写入操作时,因为是并发操作如果此时正在写入第1条数据,同时第3条数据也要写入。这时程序就会发生错误,用Dispatch Semaphore可以解决这种问题,当然Dispatch Semaphore不仅仅局限于此。Dispatch Semaphore类似于信号等待,当对一个对象进行一项操作时,在这操作期间不允许其他操作来访问对象Dispatch Semaphore会设置一道屏障来阻止其他操作,到操作完成后向Dispatch Semaphore发送一个信号告诉它对象可以进行操作,然后开始进行下一操作,此时Dispatch Semaphore会再次屏蔽其他操作,直到收到对象操作完成的信号。
- dispatch_semaphore_create
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
创建一个dispatch_semaphore_t类型的对象semaphore,这个就相当于上面所说的信号,它的参数用来判断是否需要等待。也就是上面所得是否屏蔽其他操作,当参数为0时,是等待。参数为1或者大于1时,是不等待。当参数为0时会一直等待直到收到信号,收到一次信号semaphore会自加1,这样semaphore大于或者等于1,所以等待取消会执行下面的操作。
- dispatch_semaphore_wait
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
用来执行等待操作,当
dispatch_semaphore_waitsemaphore = 0时dispatch_semaphore_wait开始等待阻止其他将要进行的操作。直到接受到信号,因为接受信号semaphore自加1所以dispatch_semaphore_wait取消等待。
注意
当执行dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);这行代码时,如果semaphore大于或者等于1,这行代码会自动将semaphore减去1。每运行一次semaphore - 1直到semaphore为0。
- dispatch_semaphore_signal
dispatch_semaphore_signal(semaphore);
dispatch_semaphore_signal会让semaphore进行加1的操作。
Dispatch Semaphore代码示例
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [NSMutableArray array];
/**
创建Dispatch Semaphore 并且设置semaphore为1
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
for (unsigned int i = 0; i<100; i++) {
dispatch_async(queue, ^{
/**
因为semaphore为1所以dispatch_semaphore_wait不等待执行下面的操作
semaphore自减1此时semaphore==0 下一次操作将会等待,一直等到semaphore >= 1
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
[array addObject:[[NSObject alloc] init]];
/**
上面添加数据执行完后dispatch_semaphore_signal操作会使semaphore加1
此时其他线程中的dispatch_semaphore_wait因为semaphore = 1 所以取消等待执行下面操作
*/
dispatch_semaphore_signal(semaphore);
});
}
dispatch_once
如果在应用中一段代码只想让它执行一次,那么就需要用到dispatch_once,一般用于创建单例。
static NSObject *object = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
object=[[NSObject alloc] init];
});

浙公网安备 33010602011771号