iOS学习笔记-----GCD 用法介绍
2016-06-03 20:42
666 查看
GCD(Grand Central Dispatch)
简介
Apple提供的一套更底层、更高效的并发编程技术,纯C语言、基于Block支持同步或异步任务处理,串行、并行的处理队列,非系统调用的信号量机制,定时任务处理,进程、文件或网络的监听任务等
优点
易用:GCD比之thread更简单易用。基于block的特性导致它能极为简单得在不同代 码作用域之间传递上下文效率:GCD实现功能 轻量、优雅,使得它在很多地方比之专门创建消耗资源的线程 更实用且快速
性能:GCD自动根据系统负载来增减线程数量,这就减少了上下文切换以及增加了计 算效率
安全:无需加锁或其他同步机制
Dispatch Queue
两种队列
Dispatch Queue是执行处理的等待队列。通过dispatch_async等函数, 按照先进先出(FIFO)顺序追加到Queue中处理,执行处理时,存在两种 Dispatch Queue:Serial Dispatch Queue :
串行队列,一个线程同时执行一个任务,可以避免数据竞争的问题 可以生成多个 Serial Dispatch Queue,各个 Serial Dispatch Queue 将并行执行
Concurrent Dispatch Queue :
并发队列,多个线程同时执行多个任务,效率高,具体是多少个线程并发执行,取决于CPU核数和CPU负荷
主队列与全局队列
Main Dispatch Queue主队列,在主线程里执行的队列。因为主线程只有一个,所以 Main Dispatch Queue 自然就是 Serial Dispatch Queue. 一切跟UI有关的操作必须放在主线程中执行,所以要追加到Main Dispatch Queue. (dispatch_get_main_queue)
Global Dispatch Queue
全局队列,所有应用程序都能够使用的 Concurrent Dispatch Queue. (dispatch_get_global_queue)
GCD使用
1.两种任务添加方式
dispatch_async 异步添加提交一个异步执行的 block块 到队列里面并且直接返回,不用等待 block 被调用
dispatch_sync 同步添加
提交一个同步执行的 block块 到队列里面并且等待,直到这个 block 执行完成,与 dispatch_async 相反 注:使用 dispatch_sync 容易引起死锁,慎重使用。比如在主线程里面执行 往主队列里面添加 任务 的操作就会引起死锁
2.使用GCD简单实现UIImageView异步加载图片
创建类目自定义方法
- (void)setImageWithURL:(NSURL *)url { //创建串行队列 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL); //依次将两个任务 异步的添加到串行队列中 //局部变量 只有添加了__blcok 才能在block中被修改 __block UIImage *image = nil; //开启多线程 加载网络图片 dispatch_async(queue, ^{ //从网络读取数据 NSData *data = [NSData dataWithContentsOfURL:url]; image = [UIImage imageWithData:data]; }); //回到主线程 显示图片到视图中 dispatch_async(queue, ^{ //重新获取主队列,并且将显示图片的操作,添加到主队列执行 dispatch_queue_t mainQueue = dispatch_get_main_queue(); //向主队列中添加界面刷新操作 dispatch_async(mainQueue, ^{ self.image = image; }); }); }
3.延迟任务 dispatch_after
/* 获取某一个时间点 * @param when 参照时间 DISPATCH_TIME_NOW = 当前时间点 * @param delta 时间差 纳秒为单位的时间 NSEC_PER_SEC = 1秒 * @return 时间点 */ dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 3); //延时调用异步任务 dispatch_after(time, dispatch_get_main_queue(), ^{ NSLog(@"延迟任务执行"); });
dispatch_after 和 performSelector:withObject:afterDelay: 的区别
1. 调用方式,前者使用block形式来调用,后者只能使用Selector 2. 后者能够通过cancel来取消还未开始的方法掉用,但是dispatch_after需要通过非常复杂的方法才能够来取消延迟任务。 3. dispatch_after的精确度比后者高很多
4.设置队列优先级
dispatch_set_target_queue(<#dispatch_object_t object#>, <#dispatch_queue_t queue#>); //改变queue的优先级与目标queue相同 //可以使多个serial queue在目标queue上 一次只有一个执行(串行执行) /* 优先级 DISPATCH_QUEUE_PRIORITY_HIGH 2 高优先级 DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认优先级 DISPATCH_QUEUE_PRIORITY_LOW (-2) 低优先级 DISPATCH_QUEUE_PRIORITY_BACKGROUND 后台执行 */
5.挂起队列
//将队列处于悬停状态,在悬停状态的队列,无法继续执行其中的任务。但是对已经开始执行的任务,无效 //创建串行队列 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL); //挂起队列 dispatch_suspend(queue); //在5秒钟之后 再进行恢复 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5); dispatch_after(time, dispatch_get_main_queue(), ^{ //恢复队列 dispatch_resume(queue); });
挂起任务 dispatch_suspend
挂起(暂停)队列里面尚未开始执行的任务,对已经正在执行的任务没有影响 使 queue 的 suspension reference count 加1
恢复任务 dispatch_resume
恢复队列里面之前被挂起的任务,使这些任务能够继续执行 使 queue 的 suspension reference count 减1
注意事项
当suspension reference count大于0时,queue就保持挂起状态。因此,必须平 衡使用suspend和resume 如果挂起了一个queue或者source,那么在销毁它之前,必须先对其进行恢复
6.一次任务
保证 dispatch_once 中的代码块在应用程序里面只执行一次,无论是不是多线程。所以可以用来实现单例模式,安全、简洁、方便。
#import "Person.h" @implementation Person static Person *p; + (instancetype)sharedPerson { if (p == nil) { //dispatch_once 一次任务 多用于单例对象 //使用dispatch_once执行的代码,在整个程序的运行过程中,一共只能执行一次 //onceToken 表示执行的标记 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ p = [[super allocWithZone:nil] init]; }); } return p; } + (instancetype)allocWithZone:(struct _NSZone *)zone { if (p == nil) { p = [Person sharedPerson]; } return p; } - (id)copy { return self; } @end
7.组任务
1.dispatch_group_async 监视一组block任务的完成,多个任务都结束后 的一个汇总处理,可以同步或异步地监视 2.dispatch_group_notify 所有任务执行结束汇总,不阻塞当前线程 3.dispatch_group_wait 等待直到所有任务执行结束,中途不能取消,阻塞当前线程
具体代码:
//创建队列 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //创建任务组 dispatch_group_t group = dispatch_group_create(); //1.添加任务到队列中,并且添加到任务组中 dispatch_group_async(group, globalQueue, ^{ NSLog(@"任务1开始"); [NSThread sleepForTimeInterval:3]; NSLog(@"任务1结束"); }); //2.在组中所有的任务完成后,会收到一个完成的通知,然后来调用相对应的Block dispatch_group_notify(group, globalQueue, ^{ NSLog(@"任务已经完成"); }); //3.监控任务是否完成 //设定时间点 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 8); //等待 等待到某个时间点后,来查看任务的完成情况 //等待操作,会堵塞当前线程 //DISPATCH_TIME_FOREVER表示一直等待,直到所有任务都完成 long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER); //返回值为0 则代表所有任务完成,不为0 表示有任务没完成 if (result == 0) { NSLog(@"所有任务已经执行完毕"); } else { NSLog(@"还有任务没有完成,%li", result); }
8.多元调用(迭代)
1. dispatch_apply 提交一个多元调用的block块到队列里面,并且等待block任务的所有迭代,完成之后才返回. 2. dispatch_apply 会阻塞当前线程,推荐在 dispatch_async 中执行 dispatch_apply 函数. 3. 结合concurrent queue,dispatch_apply能实现一个高性能的循环迭代.
NSMutableArray *array = [NSMutableArray arrayWithCapacity:1000]; for (int i = 0; i < 1000; i++) { [array addObject:@(i)]; } //多元调用(迭代) //创建并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //在全局队列中 异步的添加迭代任务 dispatch_async(queue, ^{ //多元调用 /** * 多元调用 每一次调用之间没有顺序 * * @param iterations 调用次数 * @param queue 调用所在队列 并发队列 * @param block 每一次调用 所执行的Block * */ dispatch_apply(array.count, queue, ^(size_t index) { [NSThread sleepForTimeInterval:0.3]; NSLog(@"%@", array[index]); NSLog(@"%@", [NSThread currentThread]); }); }); /* 为了提高大数据量的数组遍历的速度,所以使用多元调用 for循环遍历,每一次遍历操作,串行执行。总时间 = 每一次遍历的耗时 * 总次数 使用多元调用,将每一次遍历,放到多线程中去。在多线程中,可以同时遍历多次,这样能够提高遍历的效率 总时间 = 每一次遍历的耗时 * 总次数 / 线程数 + 线程开启关闭的时间 !! 遍历的过程,没有顺序不按照数组顺序来 */
9.设立障碍
1.dispatch_barrier_async 提交一个异步执行的带障碍的block块到队列里面,并且直接返回. 2.使用 dispatch_barrier_async,需要指定通过dispatch_queue_create函数创建的一个concurrent queue. 3.在barrier任务之前的所有任务将并行执行,任何在此之后提交的任务将不会执行直到这个 barrier任务执行完成.
//添加障碍任务 //创建并发队列 不能使用全局队列 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT); //异步的添加任务1 dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { NSLog(@"任务1 %i", i); } }); //异步的添加任务2 dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { NSLog(@"任务2 %i", i); } }); //插入障碍任务 dispatch_barrier_async(queue, ^{ NSLog(@"障碍任务开始"); [NSThread sleepForTimeInterval:3]; NSLog(@"障碍任务结束"); }); //异步的添加任务3 dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { NSLog(@"任务3 %i", i); } }); //异步的添加任务4 dispatch_async(queue, ^{ for (int i = 0; i < 10; i++) { NSLog(@"任务4 %i", i); } }); //任务1,2执行完成后,要执行障碍任务,等待障碍任务完成,才能继续执行任务3,4.
10.信号量semaphore
1.dispatch_semaphore_t持有计数的信号,使用计数来实现该信号功能。计数为0时等待,计数大于等于1时, 减去1而不等待 2.dispatch_semaphore_create 创建新的计数信号 3.dispatch_semaphore_wait 信号量为0时等待,大于等于1时,减1而不等待 4.dispatch_semaphore_signal 发信号,使信号加1
//使用信号量 来设置同时访问资源的线程数 //创建队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); __block NSMutableArray *array = [[NSMutableArray alloc] init]; //创建信号量 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); //循环插入数据 for (int i = 0; i < 100000; i++) { dispatch_async(queue, ^{ /** * 判断信号量 是否能够继续执行 访问公共资源 * 如果此时信号量不为0 则信号量减1,然后继续执行代码 * 信号量为0 则等待,直到时间超时或者信号量大于0 * 保证信号量为0时,其他线程不能访问 */ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [array addObject:@(i)]; //公共资源访问完毕,释放信号量 dispatch_semaphore_signal(semaphore); }); }
多线程总结
GCD pk NSOperationQueue
GCD是纯C语言的API;NSOperationQueue是基于GCD的OC版本的封装GCD的执行速度比NSOperationQueue快
GCD只支持FIFO的队列,且任务一旦添加到队列则无法取消;NSOperationQueue 可以很方便的调整执行顺序,可以添加依赖,设置最大并发量
NSOperationQueue支持KVO,可以检测Operation的状态(执行、结束、取消)
GCD or NSOperationQueue
GCD本身非常简单、易用、效率高,对于不复杂的多线程操作,会节省代码量,而Block参数的使用,会是代码更为易读,建议在简单项目中使用NSOperation是对线程的高度抽象,在项目中使用它,会使项目的程序结构更好,子类化NSOperation的设计思路,是具有面向对象的优点(复用、封装),使得实现是多线程支持,而接口简单,建议在复杂项目中使用
Sleep和Wait的区别
1.sleep是NSThread中的一个方法,wait是线程锁中的一个方法.2.sleep用于线程控制,使某一个线程进入到休眠状态.wait用于线程通信,使一个线程进入到等待状态,但是当另一个线程解锁时,会唤醒当前等待的线程.
3.sleep不会释放线程,在休眠状态中,会一直占用当前线程.wait会释放当前线程,将线程空闲出来.当wait被唤醒时,会重新获取线程的控制权.
相关文章推荐
- Python3写爬虫(四)多线程实现数据爬取
- 如何组织构建多文件 C 语言程序(二)
- 如何写好 C main 函数
- C#实现多线程的同步方法实例分析
- Lua和C语言的交互详解
- 浅谈chuck-lua中的多线程
- C#简单多线程同步和优先权用法实例
- C#多线程学习之(四)使用线程池进行多线程的自动管理
- C#多线程编程中的锁系统(三)
- 解析C#多线程编程中异步多线程的实现及线程池的使用
- C#多线程学习之(六)互斥对象用法实例
- 基于一个应用程序多线程误用的分析详解
- C#多线程学习之(三)生产者和消费者用法分析
- C#多线程学习之(一)多线程的相关概念分析
- C#多线程之Thread中Thread.IsAlive属性用法分析
- 分享我在工作中遇到的多线程下导致RCW无法释放的问题
- C#多线程编程之使用ReaderWriterLock类实现多用户读与单用户写同步的方法
- C#控制台下测试多线程的方法
- 21天学习android开发教程之SurfaceView与多线程的混搭
- Ruby 多线程的潜力和弱点分析