概述
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]; });