GCD 概念 是 Apple 开发的一个多核编程的较新的解决方法。 它主要用于优化应用程序以支持多核(CPU内核)处理器以及其他对称多处理系 统。 它是一个在线程池模式的基础上执行的并发任务。 在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。 特点 多核的并行运算 GCD 会自动利用更多的 CPU 内核(比如双核、四核) 自动管理多线程 GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),不需要编 写任何线程管理代码 线程池 基于线程池模式的基础上执行的并发任务 使用方便快捷 不需要用户管理线程的声明周期 缺点 不支持取消任务 线程 任务 被加入 队列,等到任务出队列时,任务别放到对应的 线程里等待执行 主线程1M大小 子线程500Kb ,所以子线程数量不能无节制,需要控制在合适数量 队列 串行队列 seria dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL); 串行队列 负责将任务逐个 加入 对应的一条 线程中,任务是线程安全的 只有调度一条线程的能力,多了也没有用 并发队列 concurrent DISPATCH_QUEUE_CONCURRENT dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT); 并发队列负责将任务 放到 不同的线程中,任务不是按顺序的 如果配合同步方法,那么也会逐个执行,并发不生效,因为只有一条线程可调度 有同时调度多条线程的能力 主队列 main queue dispatch_queue_t queue = dispatch_get_main_queue(); 任务放到主队列里排队,出队列后投放到主线程 里依次执行 是个串行队列 只能调度 主线程 能力 全局并发队列 global queue dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); app 默认创建的全局 并发队列 对线程最大条数有限制 栅栏函数不能配合全局队列,会无效 有同时调度多条线程的能力,但是系统内部优化结果,数量会有限制 任务 指事件粒度 同步执行 sync GCD 代码: dispatch_sync(queue, ^{ // 这里放同步执行任务代码 }); 同步执行意味着 该代码后面所有任务都要等待此任务执行完成 低效: 只会在当前线程依次执行 没有创建线程的能力,只能使用当前线程 线程安全的 异步执行 async 在某个时间段内,多个任务会被“同时”执行 dispatch_async(queue, ^{ // 这里放异步执行任务代码 }); 高效: 多任务多线程执行 有创建多线程的能力 线程不安全 组合 将队列组合同步异步效果: 1. 同步执行 + 并发队列 当前线程 依次执行: 同步不能开启新线程,只能当前线程依次执行任务 2. 异步执行 + 并发队列 多条子线程 并发执行: 异步开启多线程能力,并发能管理多个线程,完美配合 3. 同步执行 + 串行队列 当前线程 依次执行: 同步不能开启新线程,只能当前线程依次执行任务 4. 异步执行 + 串行队列 一条子线程 依次执行: 能开启多条新线程,但是串行队列没有管理多线程能力,结果只创建了一条子线 5. 主线程 + 同步执行 + 主队列 死锁崩溃: 同步不能创建线程,在主线程执行,而主队列又将该任务调度到主线程 该方法之 后,当前函数又依赖 主队列完成任务。 6. 主线程 + 异步执行 + 主队列 主线程 依次执行: 能开启多条新线程,但是主队列只能管理主线程能力,结果还是在线程上 7. 子线程 + 同步执行 + 主队列 主线程 依次执行: 么有开启新线程能力,函数所在任务等待 主队列调度,主队列调度任务到主线 程,不冲突,所以任务会依次执行 8. 子线程 + 异步执行 + 主队列 主线程 依次执行: 能开启多条新线程,但是主队列只能管理主线程能力,结果还是在线程上 组合嵌套 『异步执行+并发队列』 嵌套 『同步+ 同一个并发队列』 串行执行任务 『同步执行+并发队列』 嵌套 『同步+ 同一个并发队列』 串行执行任务 『异步执行+串行队列』嵌套 『同步+ 串行队列』 死锁 dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL); dispatch_async(queue, ^{ // 异步执行 + 串行队列 dispatch_sync(queue, ^{ // 同步执行 + 当前串行队列 // 追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 }); }); 嵌套内层是异步 不会死锁 线程间的通信 // 获取全局并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 获取主队列 dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(queue, ^{ // 异步追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 // 回到主线程 dispatch_async(mainQueue, ^{ NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 }); }); 栅栏函数 dispatch_barrier_sync 同步栅栏,不建议使用 dispatch_barrier_async 异步栅栏,将异步任务隔开,栅栏前 - 栅栏里-栅栏后 三个阶段的异步任务依次 执行 同步方法不受 影响 不可使用全局并发队列 示例 // 创建并发队列 dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ // 栅栏前异步任务1 //... }); dispatch_barrier_async(queue, ^{ // 栅栏内的异步任务2 // ... }); dispatch_async(queue, ^{ // 栅栏后异步任务3 //... }); 延时函数 dispatch_after dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 2.0 秒后异步追加任务代码到主队列,并开始执行 NSLog(@"after---%@",[NSThread currentThread]); // 打印当前线程 }); 单例 once dispatch_once static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 只执行 1 次的代码(这里面默认是线程安全的) }); 迭代方法 dispatch_apply 循环 ,多线程并发执行,且打乱顺序 普通for 是串行 高效: 如果遍历里面执行的任务比较耗时,那么使用dispatch_apply 异步执行,不考虑 顺序的情况下 各个任务的执行时间长短不定,最后结束顺序也不定,但是 `apply---end` 一 定在最后执行,有点像栅栏 如下: - (void)apply { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); NSLog(@"apply---开始遍历"); dispatch_apply(6, queue, ^(size_t index) { // index 是乱序的 NSLog(@"%zd---%@",index, [NSThread currentThread]); }); NSLog(@"apply--end -遍历结束"); } 队列组 dispatch_group 通知 dispatch_group_notify 通知 dispatch_group_async 队列组异步执行 异步执行多个任务,然后在任务都执行完成之后,接收通知,继续处理任务,也 可以切换到主线程 代码示例: - (void)groupNotify { dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // ️ 追加异步任务 1 到group组 }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // ️ 追加异步任务 2 到group组 }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // ️ 等前面的异步任务 1、任务 2 都执行完毕后,回到主线程执行下边 任务 NSLog(@"3---%@",[NSThread currentThread]); // 打印当前线程 }); } end enter & leave dispatch_group_enter 标志着一个任务追加到 group,执行一次,相当于 group 中未执行完毕任务数 +1 dispatch_group_leave 标志着一个任务离开了 group,执行一次,相当于 group 中未执行完毕任务数 -1 `dispatch_group_enter`、`dispatch_group_leave` 组合, 效果其实等同于`dispatch_group_async`。 - (void)groupEnterAndLeave { 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, ^{ XXX // ️ 追加任务 1 // ️ 任务结束 dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async(queue, ^{ XXX // ️ 追加任务 2 // ️ 任务结束 dispatch_group_leave(group); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // ️ 等前面的异步操作都执行完毕后,回到主线程. // xxx }); } wait dispatch_group_wait 暂停当前线程(阻塞当前线程),等待指定的 group 中的任务执行完成后,才会 往下继续执行 缺点:会堵塞线程,效果同步栅栏 - (void)groupWait { NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当 前线程 NSLog(@"group---begin"); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // ️ 追加任务 1 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"1---%@",[NSThread currentThread]); // 打印当前线程 }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // ️ 追加任务 2 [NSThread sleepForTimeInterval:2]; // 模拟耗时操作 NSLog(@"2---%@",[NSThread currentThread]); // 打印当前线程 }); // ️ 等待上面的任务全部完成后,会往下继续执行( ️ 会阻塞当前线程) dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"group---end"); } end 信号量1 线程同步 dispatch_semaphore 通过计数原理操作隔断任务 提供了三个方法: dispatch_semaphore_create(0) :创建一个 Semaphore 并初始化信号的总量 0 dispatch_semaphore_wait :信号量 - 1,信号总量小于 0 时就会一直等待 (阻塞所在线程),否则就可以正常执行。 dispatch_semaphore_signal :发送一个信号,让信号总量加 1,如果 >= 0 就会跳过wait 继续执行 线程同步 线程同步就是,有异步又有同步代码时,后面的同步代码会先等前面的异步函数 执行完成之后再执行 通常,OC中异步函数在前,同步函数在后,是会先执行后面的同步函数,再执行 前面的异步任务。 我们可以使用同步栅栏,或者GCD的wait堵塞线程,使得异步任务结束后,再执 行后面的同步任务。 其实GCD 的信号量也可以实现这个效果,它通过信号量计数来实现 下面 AFURLSessionManager.m 里一段逻辑, 就是线程同步逻辑: - (NSArray *)tasksForKeyPath:(NSString *)keyPath { __block NSArray *tasks = nil; // ️ 执行1 : 创建信号量,初始化0 dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { // ️ 执行3 : 处理task 任务逻辑 xxx // ️ 执行4 : 处理结束,信号+1 ,此时 -1 + 1 >= 0,解锁 wait, 继续执行其后面代码 dispatch_semaphore_signal(semaphore); }]; // ️ 执行2 : 信号-1,这个是 0-1 < 0 ,这个时候线程堵塞 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); return tasks; } 上面代码逻辑可知,return tasks; 返回代码的执行,是在异步函数执行结束才 调用的。 如果没有信号量,那么异步回调 里task 还没处理就已经 return 了,此时数据还 没返回呢。 信号量2 前面介绍了信号量堵塞线程使得同步任务等待前面的异步任务介绍后再执行。 这里介绍信号量锁 比如一段 修改数组的代码, [self.dataArray addObject:@"obj"]; 被多个子线程同时调用,那么修改时就可能导致数据读写异常,甚至崩溃 如果此代码会被异步调用,一般我们可以加锁: @synchronized (self) { [self.dataArray addObject:@"obj"]; } 通用也可以使用信号量,而且效率还高 信号量锁效果: // ️ 1.异步函数 - (void)testFun { // ️ 创建总信号1(关键) self.semaphore= dispatch_semaphore_create(1); __weak typeof(self) weakSelf = self; dispatch_async(queue1, ^{ [weakSelf workSafe]; }); dispatch_async(queue2, ^{ [weakSelf workSafe]; }); } // ️2.写任务 - (void)workSafe { //信号-1 ️ dispatch_semaphore_wait(self.semaphore, DISPATCH_TIME_FOREVER); // xxx 执行写任务代码 ️ // 信号+1 ️ dispatch_semaphore_signal(self.semaphore); } 解释: 线程1第一个异步函数调用 workSafe 方法时,先执行wait ,此时 总信号 1-0 = 0,代码继续执行 接着分两种情况: 1.任务执行完,还没有其它线程调用workSafe。那么 任务结束后 signal 使得信 号 +1,此时 0+1=1,其它任务再进来依然正常进行。 2.任务未结束,线程2第二个异步函数进来。 此时wait后 总信号 0-1 = 0,线程2代码堵塞,等待信号量改变。 一段时间后, 线程1 任务结束, 调用signal 使得信号 -1 +1=0,释放锁。线程2开始获得资 源继续执行任务, 后面线程2结束任务后信号又恢复1. 如此周而复始 注意: 信号量作为锁用最好不要使用在嵌套任务场景,递归场景操作不好可能会有异常 并发量 使用信号量控制,前面已经介绍过应用了 dispatch_source 应用场景:`GCDTimer` 不依赖Runloop 精度比NSTimer高 Timer