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 ...
随机推荐
- 判断Java数组是否包含某个值
下面给出四种方式,其中最有效率的还是loop方式,有兴趣的话可以测试一下: 代码如下: public boolean findStr(String[] args,String str){ boolea ...
- Linux环境编程之同步(三):读写锁
概述 相互排斥锁把试图进入我们称之为临界区的全部其它线程都堵塞住.该临界区通常涉及对由这些线程共享一个或多个数据的訪问或更新.读写锁在获取读写锁用于读某个数据和获取读写锁用于写直接作差别. 读写锁的分 ...
- Android Touch事件分发
跟touch事件相关的3个方法: public boolean dispatchTouchEvent(MotionEvent ev); //用来分派event public boolean onInt ...
- webpack4 中的最新 React全家桶实战使用配置指南!
最新React全家桶实战使用配置指南 这篇文档 是吕小明老师结合以往的项目经验 加上自己本身对react webpack redux理解写下的总结文档,总共耗时一周总结下来的,希望能对读者能够有收获, ...
- Codeforces Round #222 (Div. 1) Maze —— dfs(连通块)
题目链接:http://codeforces.com/problemset/problem/377/A 题解: 有tot个空格(输入时统计),把其中k个空格变为wall,问怎么变才能使得剩下的空格依然 ...
- 进程优先级、nice值
进程cpu资源分配就是指进程的优先权(priority).优先权高的进程有优先执行权利.配置进程优先权对多任务环境的linux很有用,可以改善系统性能.还可以把进程运行到指定的CPU上,这样一来,把不 ...
- OpenCV——Perlin Noise
// define head function #ifndef PS_ALGORITHM_H_INCLUDED #define PS_ALGORITHM_H_INCLUDED #include < ...
- python+Django实现Nagios自动化添加监控项目
最近机房刚上了一批机器(有100台左右),需要使用Nagios对这一批机器进行监控.领导要求两天时间完成所有主机的监控.从原来的经验来看,两天时间肯定完成不了.那怎么办?按照之前的想法,肯定是在nag ...
- AutoIt:如何处理应用程序端口被占用的情况
为公司的部署工程师书写了一个autoIt应用程序,现在遇到下面的一种情况: 产品分服务器端和客户端,启动的时候,会启用1785端口,然后彼此通信: 现在我的autoIt应用程序需要做的事情是: 如果1 ...
- 采用Psyco实现python执行速度提高到与编译语言一样的水平
本文实例讲述了采用Psyco实现python执行速度提高到与编译语言一样的水平的方法,分享给大家供大家参考.具体实现方法如下: 一.安装Psyco很简单,它有两种安装方式,一种是源码方式,一种是二进制 ...