简介
GCD, Grand Central Dispatch, 可译为”强大的中枢调度器”, 是一种异步执行任务技术。基于libdispatch, 纯C语言实现。(环境:Mac OS X 10.6 +, iOS 4+)
为什么要用 GCD
- GCD 可用于多核的并行运算,会自动利用更多的 CPU 内核
- GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
基本概念
队列
Dispatch Queue是执行处理的等待队列。
执行任务的等待队列,即用来存放任务的队列。队列是一种特殊的线性表,采用 FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。Dispatch Queue按照是否等待处理可以分为serial Dispatch Queue和Concurrent Dispatch Queue。
串行队列(serial Dispatch Queue)
每次只有一个任务被执行,任务一个接着一个地执行。
并行队列(Concurrent Dispatch Queue)
在同一时间可以有多个任务被执行。
同步 (Synchronous)
在当前线程中执行任务,不具备开启新线程的能力。提交的任务在执行完成后才会返回。同步函数: dispatch_sync()。
异步 (Asynchronous)
在新线程中执行任务,具备开启新线程的能力。提交的任务立刻返回,在后台队列中执行。异步函数: dispatch_async()。
GCD的使用步骤和组合方式
- 创建一个队列(串行队列或并发队列)
- 将任务追加到任务的等待队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)
串行队列 | 并行队列 | 主队列 | |
---|---|---|---|
同步执行 | 不开启新线程,串行执行任务 | 不开启新线程,串行执行任务 | 死锁 |
异步执行 | 开启新线程(1条),串行执行任务 | 开启新线程,并发执行任务 | 不开启新线程,串行执行任务 |
同步执行 +串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.jun.serial",DISPATCH_QUEUE_SERIAL);
NSLog(@"线程信息%@",[NSThread currentThread]);
dispatch_sync(serialQueue,^{
NSLog(@"1-线程信息%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue,^{
NSLog(@"2-线程信息%@",[NSThread currentThread]);
});
dispatch_sync(serialQueue,^{
NSLog(@"3-线程信息%@",[NSThread currentThread]);
});
执行结果
2019-11-11 11:52:12.024148+0800 iosTest[11225:645870] 线程信息<NSThread: 0x6000012b3040>{number = 1, name = main}
2019-11-11 11:52:12.024324+0800 iosTest[11225:645870] 1-线程信息<NSThread: 0x6000012b3040>{number = 1, name = main}
2019-11-11 11:52:12.024410+0800 iosTest[11225:645870] 2-线程信息<NSThread: 0x6000012b3040>{number = 1, name = main}
2019-11-11 11:52:12.024497+0800 iosTest[11225:645870] 3-线程信息<NSThread: 0x6000012b3040>{number = 1, name = main}
同步执行 + 并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jun.concurrent",DISPATCH_QUEUE_CONCURRENT);
NSLog(@"线程信息%@",[NSThread currentThread]);
dispatch_sync(concurrentQueue,^{
NSLog(@"1-线程信息%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue,^{
NSLog(@"2-线程信息%@",[NSThread currentThread]);
});
dispatch_sync(concurrentQueue,^{
NSLog(@"3-线程信息%@",[NSThread currentThread]);
});
执行结果
2019-11-11 11:55:08.379352+0800 iosTest[11279:647739] 线程信息<NSThread: 0x600001968540>{number = 1, name = main}
2019-11-11 11:55:08.379479+0800 iosTest[11279:647739] 1-线程信息<NSThread: 0x600001968540>{number = 1, name = main}
2019-11-11 11:55:08.379599+0800 iosTest[11279:647739] 2-线程信息<NSThread: 0x600001968540>{number = 1, name = main}
2019-11-11 11:55:08.379695+0800 iosTest[11279:647739] 3-线程信息<NSThread: 0x600001968540>{number = 1, name = main}
同步执行 + 主队列
在主队列中执行同步操作会引起死锁。
异步执行 + 串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.jun.serial",DISPATCH_QUEUE_SERIAL);
NSLog(@"线程信息%@",[NSThread currentThread]);
dispatch_async(serialQueue,^{
NSLog(@"1-线程信息%@",[NSThread currentThread]);
});
dispatch_async(serialQueue,^{
NSLog(@"2-线程信息%@",[NSThread currentThread]);
});
dispatch_async(serialQueue,^{
NSLog(@"3-线程信息%@",[NSThread currentThread]);
});
执行结果
2019-11-11 11:56:33.786021+0800 iosTest[11307:648849] 线程信息<NSThread: 0x600001d2e940>{number = 1, name = main}
2019-11-11 11:56:33.786268+0800 iosTest[11307:648902] 1-线程信息<NSThread: 0x600001d20240>{number = 3, name = (null)}
2019-11-11 11:56:33.786353+0800 iosTest[11307:648902] 2-线程信息<NSThread: 0x600001d20240>{number = 3, name = (null)}
2019-11-11 11:56:33.786454+0800 iosTest[11307:648902] 3-线程信息<NSThread: 0x600001d20240>{number = 3, name = (null)}
异步执行 + 并行队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jun.concurrent",DISPATCH_QUEUE_CONCURRENT);
NSLog(@"线程信息%@",[NSThread currentThread]);
dispatch_async(concurrentQueue,^{
sleep(3);
NSLog(@"1-线程信息%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue,^{
sleep(1);
NSLog(@"2-线程信息%@",[NSThread currentThread]);
});
dispatch_async(concurrentQueue,^{
sleep(2);
NSLog(@"3-线程信息%@",[NSThread currentThread]);
});
执行结果
2019-11-11 11:58:06.329150+0800 iosTest[11338:650092] 线程信息<NSThread: 0x600001b78e00>{number = 1, name = main}
2019-11-11 11:58:07.333266+0800 iosTest[11338:650148] 2-线程信息<NSThread: 0x600001b00500>{number = 3, name = (null)}
2019-11-11 11:58:08.331958+0800 iosTest[11338:650150] 3-线程信息<NSThread: 0x600001b09540>{number = 4, name = (null)}
2019-11-11 11:58:09.332356+0800 iosTest[11338:650149] 1-线程信息<NSThread: 0x600001b001c0>{number = 5, name = (null)}
异步执行 + 主队列
dispatch_async(dispatch_get_main_queue(),^{
NSLog(@"更新UI");
});
其他常用方法
dispatch_after
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延迟3秒执行");
});
对于时间要求不严格的可以使用这个方法,因为3秒只是block追到到主队列中的时间。如果主队列中有大量操作需要处理,会延迟操作的执行时间。第一个参数是指定的dispatch_time_t类型。该值有dispatch_time或dispatch_walltime。前者通常用于相对时间,后者为绝对时间。如果对时间精度要求高建议使用NSTimer。
dispatch_group_xxx
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(@"操作1");
});
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"操作2");
});
dispatch_notify(group, queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"耗时操作完成,更新UI");
});
});
dispatch_notify用于前面group中加入的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(@"操作1");
});
dispatch_group_async(group, queue, ^{
sleep(3);
NSLog(@"操作2");
});
dispatch_group_wait(group,DISPATCH_TIME_FOREVER);
NSLog(@"操作1和操作2完成之后才会执行");
暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会往下继续执行。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue,^{
NSLog(@"操作1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue,^{
sleep(8);
NSLog(@"操作2");
dispatch_group_leave(group);
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"end");
dispatch_group_enter、dispatch_group_leave组合等同于dispatch_group_async。
dispatch_once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"只执行一次的代码");
});
dispatch_once 保证 block 只会被执行一次,一般用于单例模式中初始化 static 的单例对象。
dispatch_barrier_xxx
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jun.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
sleep(8);
NSLog(@"操作1");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"操作2");
});
dispatch_barrier_async(concurrentQueue, ^{
NSLog(@"操作3");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"操作4");
});
dispatch_async(concurrentQueue, ^{
NSLog(@"操作5");
});
只有在操作1、操作2完成之后 才会执行后续的操作。barrier就像一个栅栏,把queue分为barrier之前和barrier之后。先执行barrier之前的,再执行barrier之中的,最后执行barrier之后的。
dispatch_apply
dispatch_queue_t globarQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(3,globarQueue,^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"end");
打印结果
2019-11-11 13:45:18.772546+0800 iosTest[12458:705620] 0
2019-11-11 13:45:18.772548+0800 iosTest[12458:705681] 2
2019-11-11 13:45:18.772549+0800 iosTest[12458:705682] 1
2019-11-11 13:45:18.772683+0800 iosTest[12458:705620] end
开启多条线程,并发执行,相比于for循环在耗时操作中极大的提高效率和速度。
dispatch_semaphore_xxx
可以借助信号量控制同时进行任务的数量。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.jun.concurrent",DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue,^{
NSLog(@"操作1");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue,^{
sleep(10);
NSLog(@"操作2");
dispatch_semaphore_signal(semaphore);
});
dispatch_async(concurrentQueue,^{
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"操作3");
});
dispatch_semaphore_create:创建一个semaphore并初始化信号的总量。
dispatch_semaphore_signal:发送一个信号,让信号总量加1。
dispatch_semaphore_wait:可以使总信号量减1,当信号总量为0时就会一直等待(阻塞所在线程),否则就可以正常执行。
semaphore 是持有计数的信号,计数为0时等待,计数为1或者大于1,调用dispatch_semaphore_wait减1不继续等待。
dispatch_block_XXX
dispatch_block_wait会阻塞当前线程,并等待前面的任务执行完毕。
dispatch_block_notify 不会阻塞当前线程,会在指定的 block 执行结束后将指定 block 插入到指定的 queue 中。
dispatch_queue_t serialQueue = dispatch_queue_create("com.jun.serial",DISPATCH_QUEUE_SERIAL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
NSLog(@"start block1");
[NSThread sleepForTimeInterval:3];
NSLog(@"end block1");
});
dispatch_async(serialQueue, block1);
long result = dispatch_wait(block1, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
if (result == 0) {
NSLog(@"success perform block1");
} else {
NSLog(@"time out");
}
dispatch_block_t block2 = dispatch_block_create(0, ^{
NSLog(@"start block2");
[NSThread sleepForTimeInterval:3];
NSLog(@"end block2");
});
dispatch_async(serialQueue, block2);
dispatch_block_wait(block1, DISPATCH_TIME_FOREVER);
dispatch_block_notify(block1,queue1,block2);
dispatch_block_cancel(block2);
dispatch_set_target_queue
dispatch_queue_t targetQueue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_set_target_queue(targetQueue, globalQueue);
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT);
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_async(queue1, ^{
NSLog(@"queue1 1");
});
dispatch_async(targetQueue, ^{
NSLog(@"async");
});
dispatch_async(queue2, ^{
NSLog(@"queue2 1");
});
dispatch_async(queue2, ^{
NSLog(@"queue2 2");
});
dispatch_async(queue2, ^{
NSLog(@"queue2 3");
});
dispatch_async(queue1, ^{
NSLog(@"queue1 2");
});
dispatch_async(queue1, ^{
sleep(1);
NSLog(@"queue1 3");
});
输出结果
2019-11-25 16:53:57.804485+0800 iosTest[1981:130563] queue1 1
2019-11-25 16:53:57.804573+0800 iosTest[1981:130563] queue1 2
2019-11-25 16:53:58.808788+0800 iosTest[1981:130563] queue1 3
2019-11-25 16:53:58.808959+0800 iosTest[1981:130563] async
2019-11-25 16:53:58.809040+0800 iosTest[1981:130563] queue2 1
2019-11-25 16:53:58.809106+0800 iosTest[1981:130563] queue2 2
2019-11-25 16:53:58.809206+0800 iosTest[1981:130563] queue2 3
dispatch_suspend和dispatch_resume
dispatch_suspend(queue)//暂停某个队列
dispatch_resume(queue)//恢复某个队列
这些函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复。