iOS开发 - 多线程实现方案之GCD篇
GCD概念
GCD为Grand Central Dispatch的缩写,纯c语言编写,是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。详细见百度百科
GCD优点
- GCD是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
GCD核心
- 任务:执行什么操作
- 队列:用来存放任务
将任务添加到队列中,GCD会自动将队列中的任务取出,放到对应的线程中执行
注意:任务的取出遵循队列的FIFO原则:先进先出,后进后出
GCD执行任务的两种方式比较
1.同步函数:
dispatch_sync(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
只能在当前线程执行任务,不具备开启新线程的能力
2.异步函数:
dispatch_async(<#dispatch_queue_t _Nonnull queue#>, <#^(void)block#>)
在新的线程中执行任务,具备开启新线程的能力
GCD队列的两大类型
1.并发队列(Concurrent Dispatch Queue):
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效
//1.创建并发队列
/**
第一个参数:c语言字符串,标签
第二个参数:队列的类型
DISPATCH_QUEUE_CONCURRENT 并发
DISPATCH_QUEUE_SERIAL 串行
*/
dispatch_queue_t creatQueue = dispatch_queue_create("funky", DISPATCH_QUEUE_CONCURRENT); //2.获取全局的并发队列
/**
第一个参数:优先级 选择默认的优先级
第二个参数:留给以后用的 暂时传0
*/
dispatch_queue_t getQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, );
获得并发队列的方式
2.串行队列(Serial Dispatch Queue):
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
//1.创建串行队列
dispatch_queue_t creatQueue = dispatch_queue_create("funky", DISPATCH_QUEUE_SERIAL);
//dispatch_release(creatQueue); // 非ARC需要释放手动创建的队列 //2.获取全局的串行队列主队列 主队列是GCD自带的一种特殊的串行队列,放在主队列中的任务,都会放到主线程中执行
dispatch_queue_t getQueue = dispatch_get_main_queue();
获得串行队列的方式
GCD的几种执行任务的方式
//异步函数 + 并发队列 :会开启多条线程,队列中的任务是并发(同时)执行,开启的线程条数是由系统内部决定的
- (void)asyncConcurrent { dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
//执行任务
}); } //异步函数 + 串行队列 :会开启一条线程,队列中的任务是串行(一条执行完在执行下一个)
- (void)asyncSerial { dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
//执行任务
}); } //同步函数 + 并发队列 :不会开启线程,任务是串行执行的
- (void)syncConcurrent { dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
//执行任务
}); } //同步函数 + 串行队列 :不会开启线程,任务是串行执行的
- (void)syncSerial { dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
//执行任务
}); } //异步函数 + 主队列 :不会开启线程,在主线程中,任务是串行执行的
- (void)asyncMain { dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
//执行任务
}); } //同步函数 + 主队列 :死锁
- (void)syncMain { /**
分析:若在主线程中执行此方法,首先获得到主队列,然后发现是同步函数,(封装任务,把任务添加到队列中)
队列安排主线程来执行任务,但当前主线程在等待方法执行完毕,这样就会形成死锁 主队列特点:如果主队列发现当前主线程有任务在执行,那么主队列会暂停调用队列中的任务,直到线程空闲为止 如果此方法在子线程中调用,则不会形成死锁
*/ dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
//执行任务
}); }
GCD线程间的通信
//创建子线程下载图片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{ NSURL *url = [NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1489947239211&di=712ed19abb4549e3752acb70fbecc29e&imgtype=0&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F314e251f95cad1c8037ed8c97b3e6709c83d5112.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data]; //在主线程中更新UI
dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });
下载网络图片
GCD的常见用法
1.延迟执行
//延迟执行方法一
/**
说明:此方法在子线程中,并不会调用SEL方法,原因是afterDelay方式是使用当前线程的定时器在一定时间后调用SEL,而子线程中默认是没有定时器的
解决:1、开启线程的定时器 [[NSRunLoop currentRunLoop] run];
2、使用dispatch_after来执行定时任务
*/
[self performSelector:@selector(test) withObject:nil afterDelay:2.0]; //延迟执行方法二
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:NO]; //延迟执行方法三
//参数一:DISPATCH_TIME_NOW 从现在开始计算时间
//参数二:delayInSeconds 延迟的时间 GCD时间单位:纳秒
//参数三:队列 (可以控制延迟执行在什么线程下执行)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), queue, ^{
//执行内容
});
延迟执行的几种方式
2.一次性代码(单例中的使用)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ // 只执行1次的代码(这里面默认是线程安全的)
// 整个APP生命周期中只会执行一次
});
3.队列组的运用场景:分别异步执行2个耗时的操作,等2个异步操作都执行完毕后,再回到主线程执行操作
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{ // 执行1个耗时的异步操作 }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{ // 执行1个耗时的异步操作 }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ // 等前面的异步操作都执行完毕后,回到主线程... });
队列组
4.栅栏函数
//栅栏函数不能使用全局并发队列
//dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue = dispatch_queue_create("funky", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{
//任务1
NSLog(@"download1--------%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
//任务2
NSLog(@"download2--------%@",[NSThread currentThread]);
}); //栅栏函数 : 可以让任务1和任务2执行完毕以后在执行栅栏函数以后的其他任务
dispatch_barrier_async(queue, ^{
NSLog(@"++++++++++ 这就是个栅栏 ++++++++");
}); dispatch_async(queue, ^{
NSLog(@"download3--------%@",[NSThread currentThread]);
});
5.快速迭代(遍历)
//普通遍历方式
for (int i = ; i < ; i++) {
NSLog(@"%d------%@",i,[NSThread currentThread]);
} /**
参数1:遍历的次数
参数2:队列(并发队列)
参数3:index 索引
*/ //开子线程和主线程一起完成遍历任务,任务的遍历是并发的
dispatch_apply(, dispatch_get_global_queue(, ), ^(size_t index) {
NSLog(@"%zu------%@",index,[NSThread currentThread]);
});
遍历
6.用函数的方式
- (void)viewDidLoad {
[super viewDidLoad]; //用函数的方式来封装任务
/**
参数一:队列
参数二:参数
参数三:要调用的函数
*/
dispatch_async_f(dispatch_get_global_queue(, ), NULL, task); } void task(void *param){
//执行耗时操作...
}
dispatch_async_f 简单使用
7.几种定时器的使用
-(void)addTimer1 { //NSTimer //方式1
/*
参数一:触发时间,单位秒
参数二:定时起触发对象
参数三:定时器响应方法
参数四:用户信息
参数五:是否重复执行,YES 每个指定的时间重复执行,NO 只执行一次
*/
//会自动将创建的定时器以默认Mode添加到当前线程runloop中,无需手动添加
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES]; //立即执行
[timer fire]; //销毁定时器(销毁后不能重新开启)
[timer invalidate]; //关闭定时器
[timer setFireDate:[NSDate distantFuture]]; //开启定时器
[timer setFireDate:[NSDate distantPast]]; //方式2
NSTimer *timer2 = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
// 将定时器添加到runloop中,否则定时器不会启动
[[NSRunLoop mainRunLoop] addTimer:timer2 forMode:NSRunLoopCommonModes]; }
NSTimer
-(void)addTimer2 { // 创建displayLink
/*
当把CADisplayLink对象add到runloop中后,selector就能被周期性调用,类似于重复的NSTimer被启动了;执行invalidate操作时,CADisplayLink对象就会从runloop中移除,selector调用也随即停止,类似于NSTimer的invalidate方法 CADisplayLink是一个和屏幕刷新率同步的定时器类。CADisplayLink以特定模式注册到runloop后,每当屏幕显示内容刷新结束的时候,runloop就会向CADisplayLink指定的target发送一次指定的selector消息,CADisplayLink类对应的selector就会被调用一次,所以可以使用CADisplayLink做一些和屏幕操作相关的操作。 重要属性: frameInterval : NSInteger类型的值,用来设置间隔多少帧调用一次selector方法,默认值是1,即每帧都调用一次。 duration : readOnly的CFTimeInterval值,表示两次屏幕刷新之间的时间间隔。需要注意的是,该属性在target的selector被首次调用以后才会被赋值。selector的调用间隔时间计算方式是:调用间隔时间 = duration × frameInterval。
*/
CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(test)]; // 将创建的displaylink添加到runloop中,否则定时器不会执行
[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; //关闭定时器
displayLink.paused = YES; //开启定时器
displayLink.paused = NO; // 销毁定时器
[displayLink invalidate];
displayLink = nil; }
CADisplayLink
@property (nonatomic,strong) dispatch_source_t timer; -(void)addTimer3 { //GCD定时器不会受RunLoop的影响,并且是绝对精准的 //1.创建GCD中的定时器
/*
参数一:source的类型 DISPATCH_SOURCE_TYPE_TIMER 表示定时器
参数二:描述信息
参数三:更详细的描述信息
参数四:队列,决定GCD定时器中的任务在哪个线程中执行
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, , , dispatch_get_global_queue(, )); //2.设置定时器(起始时间|间隔时间|精准度)
/*
参数一:定时器对象
参数二:起始时间,DISPATCH_TIME_NOW 从现在开始计时
参数三:间隔时间 2.0 GCD时间单位是 纳秒
参数四:精准度 绝对精准0
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, * NSEC_PER_SEC); //3.设置定时器执行的任务
dispatch_source_set_event_handler(timer, ^{
NSLog(@"--------%@",[NSThread currentThread]);
}); //4.启动执行
dispatch_resume(timer); //timer 是一个局部变量,2s之后定时器变量可能会被释放了,所以定时器不工作,为了保证不被释放
self.timer = timer; /*
//暂停定时器
dispatch_suspend(self.timer);
//开启定时器
dispatch_resume(self.timer);
//销毁定时器
dispatch_cancel(self.timer);
*/ }
GCD定时器
参考文章: http://www.cnblogs.com/wendingding/p/3806821.html
iOS开发 - 多线程实现方案之GCD篇的更多相关文章
- iOS开发 - 多线程实现方案之Pthread篇
pthread基础 pthread是POSIX thread的简写,一套通用的多线程API,适用于Unix.Linux.Windows等系统,跨平台.可移植,使用难度大,C语言框架,线程生命周期由程序 ...
- iOS开发 - 多线程实现方案之NSOperation篇
NSOperation简介 1.实现多线程编程步骤: 配合使用NSOperation和NSOperationQueue实现多线程编程,我们不用考虑线程的生命周期.同步.加锁等问题,如下: 先将需要执行 ...
- iOS开发 - 多线程实现方案之NSThread篇
NSThread API //类方法:创建一个线程 + (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macos ...
- iOS开发多线程--技术方案
pthread 实现多线程操作 代码实现: void * run(void *param) { for (NSInteger i = 0; i < 1000; i++) { ...
- iOS 开发多线程篇—GCD的常见用法
iOS开发多线程篇—GCD的常见用法 一.延迟执行 1.介绍 iOS常见的延时执行有2种方式 (1)调用NSObject的方法 [self performSelector:@selector(run) ...
- iOS开发多线程篇—GCD介绍
iOS开发多线程篇—GCD介绍 一.简单介绍 1.什么是GCD? 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数 2.GCD的优势 G ...
- iOS开发多线程篇—GCD的基本使用
iOS开发多线程篇—GCD的基本使用 一.主队列介绍 主队列:是和主线程相关联的队列,主队列是GCD自带的一种特殊的串行队列,放在主队列中得任务,都会放到主线程中执行. 提示:如果把任务放到主队列中进 ...
- iOS开发多线程篇—GCD的常见用法
iOS开发多线程篇—GCD的常见用法 一.延迟执行 1.介绍 iOS常见的延时执行有2种方式 (1)调用NSObject的方法 [self performSelector:@selector(run) ...
- iOS开发多线程篇—GCD简介
iOS开发多线程篇—GCD介绍 一.简单介绍 1.什么是GCD? 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器” 纯C语言,提供了非常多强大的函数 2.GCD的优势 G ...
随机推荐
- 如何克隆UBUNTU14.04LTS
先对目标盘sdb做好处理,分区,格式化,挂载等操作sudo fdisk /dev/sdb1fdisk常用命令如下,m是帮助,n创建新分区,d删除分区,w保存退出.分好区后,对sdb1进行格式化和挂载: ...
- ImageIO 操作图片
/** * 读取本地图片到另一个本地文件夹 * @throws IOException */ public void copeImageToOtherFolder() throws IOExcepti ...
- animation steps属性实现帧动画
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta na ...
- jquery特效(3)—轮播图①(手动点击轮播)
写了一个轮播图练练手,先写了一个手动点击轮播的轮播图,随后我会慢慢接着深入写自动轮播图和鼠标悬浮图片停止移动轮播图等,虽然今天我生日,但是代码还是得写的,不能找借口放松自己,原地踏步也算后退. 下面来 ...
- ModuleNotFoundError: No module named 'numpy.core._multiarray_umath' ImportError: numpy.core.multiarray failed to import
出现以下错误:可能是因为你的numpy版本太低 更新numpy的版本 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple --upgra ...
- 003-更改pip的源让下载安装更加快捷
1 找到pip目录 C:\Python36\Lib\site-packages\pip\models 2 修改下面的index.py文件 将url设定为 https://pypi.douban.com ...
- 关于yolo 模型中1X1卷积层的作用
1X1卷积层的作用: 1.实现跨通道的交互和信息整合.2.进行卷积核通道数的降维和升维.3.就是可以在保持feature map 尺寸不变(即不损失分辨率)的前提下大幅增加非线性特性,把网络做得很de ...
- ubuntu16.04 NVIDIA 驱动安装
查看驱动版本号 查看驱动适用版本:NVIDIA驱动版本查询 查看显卡对应的驱动版本: 举例如下: 禁止集成的nouveau驱动 Ubuntu系统集成的显卡驱动程序是nouveau,它是第三方为NVID ...
- Ubuntu+win7 双系统修改开机启动项顺序
Ubuntu和windows双系统安装完后默认Ubuntu系统是第一启动项,等待时间是10秒 如果你想改成windows为第一启动项 先进去Ubuntu系统 打开终端 (Ctrl+Alt+T) 修改启 ...
- mysql分区表之四:分区表性能
一, 分区概念 分区允许根据指定的规则,跨文件系统分配单个表的多个部分.表的不同部分在不同的位置被存储为单独的表.MySQL从5.1.3开始支持Partition. 分区和手动分表对比 手 ...