1.什么是GCD
全称是Grand Central Dispatch,可译为“牛X的中枢调度器”
纯C语言,提供了非常多强大的函数
2.GCD的优势
GCD是苹果公司为多核的并行运算提出的解决方案
GCD会自动利用更多的CPU内核(比如双核、四核)
GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
3.GCD中有2个核心概念
任务:执行什么操作
队列:用来存放任务
将任务添加到队列中
GCD会自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则:先进先出,后进后出
有4个术语比较容易混淆:同步、异步、并发、串行
同步和异步决定了要不要开启新的线程
同步:在当前线程中执行任务,不具备开启新线程的能力
异步:在新的线程中执行任务,具备开启新线程的能力
并发和串行决定了任务的执行方式
并发:多个任务并发(同时)执行
串行:一个任务执行完毕后,再执行下一个任务
代码分析上面的四个概念:
#pragma mark - 1异步并发
- (void)asyncGlobal {
// 取得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 使用异步调用
dispatch_async(queue, ^{
NSLog(@"result1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"result2, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"result3, %@",[NSThread currentThread]);
});
}
#pragma mark - 2异步串行
- (void)asyncSerial {
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("Serial", NULL);
// 使用异步调用
dispatch_async(queue, ^{
NSLog(@"result1, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"result2, %@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"result3, %@",[NSThread currentThread]);
});
}
#pragma mark - 3同步并发
- (void)syncGlobal {
// 取得全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 使用同步调用
dispatch_sync(queue, ^{
NSLog(@"result1, %@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"result2, %@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"result3, %@",[NSThread currentThread]);
});
}
#pragma mark - 4同步串行
- (void)syncSerial {
// 创建串行队列
dispatch_queue_t queue = dispatch_queue_create("Serial", NULL);
// 使用同步调用
dispatch_sync(queue, ^{
NSLog(@"result1, %@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"result2, %@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"result3, %@",[NSThread currentThread]);
});
}
上面的代码,我们可以通过观察打印线程信息 [NSThread currentThread] 来判断是否有开启新的线程及开启新的线程的情况。上面的执行结果,可以用下面的图表来描述:
用文字总结,结果如下:
同步 (sync) 不开起新的线程
并发队列: 不开起新的线程(用默认的主线程)
串行队列: 不开起新的线程(用默认的主线程)
异步 (async) 开启新的线程
并发队列: 能开启多条线程(取出队列中的任务,子线程执行;接着取出队列中后面的任务,开辟其他线程执行这条任务....)
串行队列: 只开启一条子线程(取出队列中的任务,子线程执行,执行完毕后,继续取出任务,子线程继续执行....)
4.线程间通信示例
从子线程回到主线程
dispatch_async(
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//执行耗时的异步操作...
dispatch_async(dispatch_get_main_queue(),^{
// 回到主线程,执行UI刷新操作
});
});
上面的代码片段是经典的线程间通信使用方法。
Example:
从网络下载图片资源,并显示到主线程UI上面
// 并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 开启子线程
dispatch_async(queue, ^{
NSURL *url = [NSURL URLWithString:@"http://pic.cnr.cn/list/201209/W020120914588236390160.jpg"];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
// 讲image信息在主线程上面呈现(这样就不会卡死了)
dispatch_async(dispatch_get_main_queue(), ^{
self.myImageView.image = image;
});
});
5. 队列组
有这么一种需求
首先:分别异步执行两个耗时的操作
其次:等两个异步操作都执行完毕后,再回到主线程执行操作
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 执行1个耗时的异步操作
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的异步操作都执行完毕后,回到主线程...
});
Example:
从网络异步下载两张图片,当两张图片均下载完毕后,完成图片的拼接操作
效果图如下:
实现代码如下:
#pragma mark - 并发下载多张图片
- (void)downloadImage {
dispatch_group_t group = dispatch_group_create();
__block UIImage *image1 = nil;
dispatch_group_async(group, GlobalQueue, ^{
image1 = [self imageWithUrl:@"http://www.2qqtouxiang.cn/uploads/allimg/100917/1_100917230241_2.jpg"];
});
__block UIImage *image2 = nil;
dispatch_group_async(group, GlobalQueue, ^{
image2 = [self imageWithUrl:@"http://img3.douban.com/view/photo/albumicon/public/p1446733286.jpg"];
});
// 上面两个GCD是异步同时下载图片的,下面这句代码是等到两张图片全部下载完毕后便会执行
dispatch_group_notify(group, MainQueue, ^{
self.image1.image = image1;
self.image2.image = image2;
// 拼接图片
UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, 0.0);
[image1 drawInRect:CGRectMake(0, 0, 50, 100)];
[image2 drawInRect:CGRectMake(50, 0, 50, 100)];
self.myImage.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
}
- (UIImage *)imageWithUrl:(NSString *)urlStr {
NSURL *url = [NSURL URLWithString:urlStr];
NSData *data = [NSData dataWithContentsOfURL:url];
return [UIImage imageWithData:data];
}
6. GCD其他好用的方法
#pragma mark - 延时执行
- (void)delay {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"延时3秒执行~~~");
});
}
#pragma mark - 不管调用多少次只执行一次
- (void)doOnce {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSLog(@"一次性执行");
});
}