#import "JSC_GCD.h"
@interface JSC_GCD ()
@property (nonatomic,weak)UIImageView *imageview;
@end
@implementation JSC_GCD
-(void)learn
{
/*
在 GCD 中,加入了两个非常重要的概念: 任务 和 队列。
任务:即操作,你想要干什么,说白了就是一段代码,在 GCD 中就是一个 Block,所以添加任务十分方便。任务有两种执行方式: 同步执行 和 异步执行
同步(sync) 和 异步(async) 的主要区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕!
如果是 同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。
如果是 异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程。
队列:用于存放任务。一共有两种队列, 串行队列 和 并行队列。
串行队列:
放到串行队列的任务,GCD 会 FIFO(先进先出) 地取出来一个,执行一个,然后取下一个,这样一个一个的执行。
并行队列:
放到并行队列的任务,GCD 也会 FIFO的取出来,但不同的是,它取出来一个就会放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD 会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行。
主队列:这是一个特殊的 串行队列。什么是主队列,大家都知道吧,它用于刷新 UI,任何需要刷新 UI 的工作都要在主队列执行,所以一般耗时的任务都要放到别的线程执行。
dispatch_queue_t queue = dispatch_get_main_queue();
Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。GCD就是为了在“多核”上使用多线程技术
GCD是纯C语言的,因此我们在编写GCD相关代码的时候,面对的函数,而不是方法。GCD中的函数大多数都以dispatch开头。
GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行。
名词解释 global 全局 queue 队列 async 异步 sync 同步
dispatch queue分为下面三种:
Serial
又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。
Concurrent
又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。
Main dispatch queue
它是全局可用的serial queue,它是在应用程序主线程上执行任务的。
//串行队列
dispatch_queue_t queue = dispatch_queue_create("jy.jsc.Queue", NULL);
dispatch_queue_t queue2 = dispatch_queue_create("jy.jsc.Queue", DISPATCH_QUEUE_SERIAL);
//并行队列
dispatch_queue_t queue3 = dispatch_queue_create("jy.jsc.Queue", DISPATCH_QUEUE_CONCURRENT);
//参数
//其中第一个参数是标识符,用于 DEBUG 的时候标识唯一的队列,可以为空
//第二个参数用来表示创建的队列是串行的还是并行的,传入 DISPATCH_QUEUE_SERIAL 或 NULL 表示创建串行队列。传入 DISPATCH_QUEUE_CONCURRENT 表示创建并行队列。
//全局并发队列
dispatch_queue_t queue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
参数:第一个参数为优先级,这里选择默认的DISPATCH_QUEUE_PRIORITY_DEFAULT 第二个参数是留给以后用的,暂时用不上,传个0
同步执行 异步执行
串行队列 当前线程,一个一个执行 其他线程,一个一个执行
并行队列 当前线程,一个一个执行 开很多线程,一起执行
或者:
全局并发队列 手动创建串行队列 主队列
同步(sync) 没有开启新线程 没有开启新线程 没有开启新线程
串行执行任务 串行执行任务 串行执行任务
异步(async) 有开启新线程 有开启新线程 没有开启新线程
并发执行任务 串行执行任务 串行执行任务
*/
}
- (void)viewDidLoad {
[super viewDidLoad];
//同步和异步的测试
[self test];
//dispatch_group_async的使用,一般和信号量一起,信号量具体用法可以看我博客 ios多个网络请求
//[self test1];
//dispatch_barrier_async的使用
//[self test2];
//常规用法
//[self test3];
}
-(void)test
{
// //并发队列
// //可以让多个任务并发(同时)执行(自动开启多个线程同时执行任务)并发功能只有在异步(dispatch_async)函数下才有效
//
// //获取全局并发队列
// dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// NSLog(@"线程 %@",[NSThread currentThread]);
// //异步任务:并发
// dispatch_async(queue, ^{
// [NSThread sleepForTimeInterval:2];
// NSLog(@"group1");
// NSLog(@"线程 %@" , [NSThread currentThread]);
// });
// dispatch_async(queue, ^{
// [NSThread sleepForTimeInterval:2];
// NSLog(@"group2");
// NSLog(@"线程 %@" , [NSThread currentThread]);
// });
// NSLog(@"----");
// //以上代码,从打印的线程来看,我们可以看到在并发队列中开了两条线程去执行这两个任务,因此这两个任务会一起执行
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"线程 %@",[NSThread currentThread]);
//同步任务:并发
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"group1");
NSLog(@"线程 %@" , [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group2");
NSLog(@"线程 %@" , [NSThread currentThread]);
});
NSLog(@"----");
//从以上代码,从打印来看,并没有开辟新的线程。因此在同步任务中,不管是并发队列还是串行队列,都不会开辟新的子线程,并且任务都是一个一个的在当前的主线程上执行
// dispatch_queue_t queue = dispatch_queue_create("jy.jsc.Queue", DISPATCH_QUEUE_SERIAL);
// NSLog(@"线程 %@",[NSThread currentThread]);
// //异步任务:串行
// dispatch_async(queue, ^{
// [NSThread sleepForTimeInterval:1];
// NSLog(@"group1");
// NSLog(@"线程 %@" , [NSThread currentThread]);
// });
// dispatch_async(queue, ^{
// [NSThread sleepForTimeInterval:2];
// NSLog(@"group2");
// NSLog(@"线程 %@" , [NSThread currentThread]);
// });
// NSLog(@"----");
// //以上代码,从打印的线程来看,只创建了一个线程,所以异步串行只会创建一个子线程,任务会在这个字线程中一个一个执行
}
-(void)test1
{
/*
全局队列:
系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列,如下:
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
当然如果不用全局队列,我们自己写一个并行队列的话就需要:
dispatch_queue_t queue3 = dispatch_queue_create("jy.jsc.Queue", DISPATCH_QUEUE_CONCURRENT);
这里也用到了系统默认就有一个串行队列main_queue:
dispatch_queue_t mainQ = dispatch_get_main_queue();
当然如果我们需要创建串行队列也可以自己写:不过一般用不到吧
dispatch_queue_t queue = dispatch_queue_create("jy.jsc.Queue", DISPATCH_QUEUE_SERIAL);
*/
//dispatch_group_async的使用
//dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了
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, ^{
//如果这里是一个延时请求,比如网络请求,那么就需要用信号量控制。
[NSThread sleepForTimeInterval:1];
NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
[NSThread sleepForTimeInterval:3];
NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"updateUi");
});
NSLog(@"----");
}
-(void)test2
{
//dispatch_barrier_async的使用
//dispatch_barrier_async是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行
dispatch_queue_t queue = dispatch_queue_create("jy.jsc.Queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"dispatch_async1");
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:4];
NSLog(@"dispatch_async2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"dispatch_barrier_async");
[NSThread sleepForTimeInterval:4];
});
dispatch_async(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"dispatch_async3");
});
NSLog(@"----");
/*
dispatch_apply
执行某个代码片段N次。
dispatch_apply(5, globalQ, ^(size_t index) {
// 执行5次
});
*/
}
-(void)test3
{
// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// // 耗时的操作
// dispatch_async(dispatch_get_main_queue(), ^{
// // 更新界面
// });
// });
UIImageView *imageview=[[UIImageView alloc]init];
self.imageview=imageview;
imageview.frame=CGRectMake(10, 100, 200, 200);
[self.view addSubview:imageview];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL * url = [NSURL URLWithString:@"http://s10.sinaimg.cn/mw690/56279263h7bdc5a0791f9&690.png"];
NSData * data = [[NSData alloc]initWithContentsOfURL:url];
UIImage *image = [[UIImage alloc]initWithData:data];
if (data != nil) {
dispatch_async(dispatch_get_main_queue(), ^{
self.imageview.image = image;
});
}
});
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
@end
接下来,我引用别人的两个例子和解释来延时线程锁死现象
示例一:
以下代码在主线程调用,结果是什么?
NSLog(@"之前 %@" , [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"dispatch_sync %@" , [NSThread currentThread]);
});
NSLog(@"之后 %@" , [NSThread currentThread]);
答案:
只会打印第一句:之前 - {number = 1, name = main} ,然后主线程就卡死了,你可以在界面上放一个按钮,你就会发现点不了了。
解释:
同步任务会阻塞当前线程所执行的队列,然后把 Block 中的任务放到指定的队列中执行,只有等到 Block 中的任务完成后才会让当前线程继续往下运行。
那么这里的步骤就是:打印完第一句后,dispatch_sync 立即阻塞当前的主线程,然后把 Block 中的任务放到 main_queue 中,可是 main_queue 中的任务会被取出来放到主线程中执行,但主线程这个时候已经被阻塞了,所以 Block 中的任务就不能完成,它不完成,dispatch_sync 就会一直阻塞主线程,这就是死锁现象。导致主线程一直卡死。
补充:之前只是借鉴别人的例子,没发现问题,最近发现例子解释不准确。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"线程 %@",[NSThread currentThread]);
//同步任务:并发
//此时,当前线程会阻塞当前主队列,而去执行全局并发队列queue,不是说当前线程阻塞后,全部队列都不会执行。以上借鉴的别人例子解释有误。因此,block代码块依然会执行。
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:2];
NSLog(@"group1");
NSLog(@"线程 %@" , [NSThread currentThread]);
});
dispatch_sync(queue, ^{
[NSThread sleepForTimeInterval:1];
NSLog(@"group2");
NSLog(@"线程 %@" , [NSThread currentThread]);
});
NSLog(@"----");
//此时当前线程的主队列被阻塞,而此时的同步任务加入的队列竟然还是主队列,因此无法执行block代码块。造成死锁现象。
NSLog(@"之前 %@" , [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"dispatch_sync %@" , [NSThread currentThread]);
});
NSLog(@"之后 %@" , [NSThread currentThread]);
示例二:
以下代码会产生什么结果?
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
NSLog(@"之前 %@" , [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"sync之前 %@" , [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"sync %@" , [NSThread currentThread]);
});
NSLog(@"sync之后 %@" , [NSThread currentThread]);
});
NSLog(@"之后 %@" , [NSThread currentThread]);
答案:
2015-07-30 02:06:51.058 test[33329:8793087] 之前 - {number = 1, name = main}
2015-07-30 02:06:51.059 test[33329:8793356] sync之前 - {number = 2, name = (null)}
2015-07-30 02:06:51.059 test[33329:8793087] 之后 - {number = 1, name = main}
很明显 sync - %@ 和 sync之后 - %@ 没有被打印出来!这是为什么呢?我们再来分析一下:
分析:
我们按执行顺序一步步来哦:
1.使用 DISPATCH_QUEUE_SERIAL 这个参数,创建了一个 串行队列。
2.打印出 之前 - %@ 这句。
3.dispatch_async 异步执行,所以当前线程不会被阻塞,于是有了两条线程,一条当前线程继续往下打印出 之后 - %@这句, 另一台执行 Block 中的内容打印 sync之前 - %@ 这句。因为这两条是并行的,所以打印的先后顺序无所谓。
4.注意,高潮来了。现在的情况和上一个例子一样了。dispatch_sync同步执行,于是它所在的线程会被阻塞,一直等到 sync 里的任务执行完才会继续往下。于是 sync 就高兴的把自己 Block 中的任务放到 queue 中,可谁想 queue 是一个串行队列,一次执行一个任务,所以 sync 的 Block 必须等到前一个任务执行完毕,可万万没想到的是 queue 正在执行的任务就是被 sync 阻塞了的那个。于是又发生了死锁。所以 sync 所在的线程被卡死了。剩下的两句代码自然不会打印。
补充:那么如何才能打印呢,根据2例子中我补充的知识来看,其实很简单,只需在开一个队列即可:
//线程阻塞2
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue2 = dispatch_queue_create("myQueue2", DISPATCH_QUEUE_SERIAL);
NSLog(@"之前 %@" , [NSThread currentThread]);
dispatch_async(queue, ^{
NSLog(@"sync之前 %@" , [NSThread currentThread]);
dispatch_sync(queue2, ^{
NSLog(@"sync %@" , [NSThread currentThread]);
});
NSLog(@"sync之后 %@" , [NSThread currentThread]);
});
NSLog(@"之后 %@" , [NSThread currentThread]);
下面我再出个例子,大家可以试一下,看看程序会怎么走?
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"之前 %@" , [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"sync %@" , [NSThread currentThread]);
});
NSLog(@"之后 %@" , [NSThread currentThread]);