这篇文章对iOS的多线程技术GCD的常用方法做了总结
另一篇NSOperation在这里
本文代码
- GCD:
简介:
- 全称是Grand Central Dispatch,可译为“牛逼的中枢调度器”
- 纯C语言,提供了非常多强大的函数
- GCD是苹果公司为多核的并行运算提出的解决方案
- GCD会自动利用更多的CPU内核(比如双核、四核)
- GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
- 我们只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
GCD两个核心概念:
- 任务 : 干什么事情,执行什么操作
- 队列 : 用什么方式(串行,并发)来存放任务
首先介绍GCD执行任务时常用的两个函数:
- 用同步的方式来执行任务:
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 用异步的方式来执行任务:
ddispatch_async(dispatch_queue_t queue, dispatch_block_t block);
同步和异步的区别:
同步 : 只在当前线程执行 , 不开其他线程
异步 : 可以在新的线程中执行任务 , 有开线程的能力 (但是不一定开,要看用什么队列)
然后是两种GCD队列:
- 并发队列:
可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)
(并发功能只有用异步(dispatch_async)函数才有效)
//创建并发队列:
dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
//获得全局并发队列:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
- 串行队列
让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)
// 创建串行队列(队列类型传递NULL或者DISPATCH_QUEUE_SERIAL)
dispatch_queue_t queue = dispatch_queue_create("name", NULL);
//使用dispatch_get_main_queue()获得主队列
//主队列是GCD自带的一种特殊的串行队列
//放在主队列中的任务,都会放到主线程中执行(不管是同步函数,还是异步函数)
dispatch_queue_t queue = dispatch_get_main_queue();
代码实践各种队列与函数的组合:
-
同步函数 + 串行队列:
//同步+串行
-(void)syncSerial{
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);
//用同步函数把任务放入串行队列:
dispatch_sync(queue, ^{
NSLog(@"-------download1--%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-------download2--%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-------download3--%@",[NSThread currentThread]);
});
}
打印结果:
2017-06-26 13:25:53.744 HXThread[1628:247794] -------download1--<NSThread: 0x60800007b100>{number = 1, name = main}
2017-06-26 13:25:53.745 HXThread[1628:247794] -------download2--<NSThread: 0x60800007b100>{number = 1, name = main}
2017-06-26 13:25:53.745 HXThread[1628:247794] -------download3--<NSThread: 0x60800007b100>{number = 1, name = main}
结论 :
同步函数是在当前线程执行,不具备开线程的能力,然后串行队列又是的任务又是串行执行,所以没有开新的线程,大家一个一个的执行
-
同步函数 + 并发队列:
//获得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//用同步函数 把任务加入并行队列
//用同步函数把任务放入串行队列:
dispatch_sync(queue, ^{
NSLog(@"-------download1--%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-------download2--%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-------download3--%@",[NSThread currentThread]);
});
打印结果:
2017-06-26 13:34:57.588 HXThread[1674:254728] -------download1--<NSThread: 0x60800007fe80>{number = 1, name = main}
2017-06-26 13:34:57.591 HXThread[1674:254728] -------download2--<NSThread: 0x60800007fe80>{number = 1, name = main}
2017-06-26 13:34:57.594 HXThread[1674:254728] -------download3--<NSThread: 0x60800007fe80>{number = 1, name = main}
结论:
同步函数是在当前线程执行,不具备开线程的能力,就算你是并发队列也不好使,任务还是在当前线程,大家一个一个的来
-
异步函数+串行队列:
//异步函数 + 串行队列
-(void)asyncSerial{
//创建串行队列
dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_SERIAL);
//用异步函数 将任务加入串行队列
dispatch_async(queue, ^{
NSLog(@"-------download1--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"-------download2--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"-------download3--%@",[NSThread currentThread]);
});
}
打印结果:
2017-06-26 13:48:02.264 HXThread[1720:261890] -------download1--<NSThread: 0x600000066680>{number = 4, name = (null)}
2017-06-26 13:48:02.264 HXThread[1720:261890] -------download2--<NSThread: 0x600000066680>{number = 4, name = (null)}
2017-06-26 13:48:02.264 HXThread[1720:261890] -------download3--<NSThread: 0x600000066680>{number = 4, name = (null)}
结论:
异步函数具有开线程的能力,但是串行队列是挨着来,所以就开一个线程,大家在这个新线程中一个一个整
-
异步函数+并发队列
//创建一个并发队列
dispatch_queue_t queue = dispatch_queue_create("name", DISPATCH_QUEUE_CONCURRENT);
//用异步函数 将任务加入并发队列
dispatch_async(queue, ^{
NSLog(@"-------download1--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"-------download2--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"-------download3--%@",[NSThread currentThread]);
});
打印结果:
2017-06-26 13:51:57.523 HXThread[1749:264765] -------download2--<NSThread: 0x60800007ea40>{number = 6, name = (null)}
2017-06-26 13:51:57.523 HXThread[1749:264699] -------download1--<NSThread: 0x60800007e280>{number = 5, name = (null)}
2017-06-26 13:51:57.523 HXThread[1749:264766] -------download3--<NSThread: 0x600000264e00>{number = 7, name = (null)}
结论:
异步函数已经有了开线程的能力,并发队列又是又是让任务并发执行,所以开了多条线程,大家一起干
-
异步函数+主队列
//获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//用异步函数 把任务放到主队列中
dispatch_async(queue, ^{
NSLog(@"-------download1--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"-------download2--%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"-------download3--%@",[NSThread currentThread]);
});
打印结果:
2017-06-26 13:56:52.117 HXThread[1788:268167] -------download1--<NSThread: 0x608000078d40>{number = 1, name = main}
2017-06-26 13:56:52.121 HXThread[1788:268167] -------download2--<NSThread: 0x608000078d40>{number = 1, name = main}
2017-06-26 13:56:52.121 HXThread[1788:268167] -------download3--<NSThread: 0x608000078d40>{number = 1, name = main}
结论:
虽然异步函数有开线程的能力,但是队列是主队列,谁也不好使,大家都在主线程执行,一个一个挨着来
-
同步函数+主队列
//获取主队列
dispatch_queue_t queue = dispatch_get_main_queue();
//用同步函数 将任务加入主队列
dispatch_sync(queue, ^{
NSLog(@"-------download1--%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-------download2--%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"-------download3--%@",[NSThread currentThread]);
});
没有打印结果.程序报错
结论:
当程序执行到NSLog(@"-------download1--%@",[NSThread currentThread]);这一行的时候
这一行打印任务就加入到了主队列,因为又是在主线程,所以打印任务要在主线程的当前任务结束后立马执行
当前任务就是当前方法
但是当前方法要等打印结束之后才能结束
所以打印任务等当前方法任务结束才能执行
当前方法这个任务又等打印执行结束才能结束
就卡死了..........
以上就是两种函数和两种队列的组合使用,总结一下:
- 同步函数不会开线程,不管是并发队列,还是串行队列
- 异步函数有开线程的能力,串行队列开一条,并发队列开多条
- 为什么说异步函数是有开线程的能力,而不是肯定开,就因为遇到主队列还是不开线程
- 同步+主队列卡死...
线程之间的通信:
代码实现:
//子下载图片,回到主线程更新UI
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
//回到主线程更新UIUI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
GCD其他常用函数:
- 一次性代码:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"这里的代码在程序执行过程中,只会执行一次");
});
- 延时;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延时2秒后执行这里的代码");
});
- 快速遍历
//多线程快速遍历数组
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"------索引:%zu------%@",index,[NSThread currentThread]);
});
- 队列组
类似任务依赖
//全局并发队列
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, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://img.pconline.com.cn/images/photoblog/9/9/8/1/9981681/200910/11/1255259355826.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
self.image1 = [UIImage imageWithData:data];
});
dispatch_group_async(group, queue, ^{
// 图片的网络路径
NSURL *url = [NSURL URLWithString:@"http://pic38.nipic.com/20140228/5571398_215900721128_2.jpg"];
// 加载图片
NSData *data = [NSData dataWithContentsOfURL:url];
// 生成图片
self.image2 = [UIImage imageWithData:data];
});
//当group中的任务都执行完了以后,就会执行group中notify中的内容
dispatch_group_notify(group, queue, ^{
//合成图片
UIGraphicsBeginImageContext(CGSizeMake(100, 100));
[self.image1 drawInRect:CGRectMake(0, 0, 50, 100)];
[self.image2 drawInRect:CGRectMake(50, 0, 50, 100)];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = image;
});
});
- GCD定时器:
//定时器在哪个队列进行
dispatch_queue_t queue = dispatch_get_main_queue();
//创建定时器
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//定时器的开始时间
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
//设置时间间隔
uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
dispatch_source_set_timer(timer, start, interval, 0);
// 设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"------------%@", [NSThread currentThread]);
});
// 启动定时器
dispatch_resume(self.timer);
值得注意的是
GCD定时器时间间隔是纳秒
1秒 = 1*10的九次方
不过GCD为我们提供了一个宏 NSEC_PER_SEC
(1.0 * NSEC_PER_SEC) 就是一秒
GCD 定时器不受 RunLoop 运行模式的影响
有关 RunLoop 和定时器的关系, 请看我这篇文章:
http://www.jianshu.com/p/5ef8f28025b9
网友评论