GCD
简介:
1> iOS和OS X的核心是XNU内核,GCD是基于XNU内核实现的
2> GCD的API全部在libdispatch库中
3> GCD的底层实现主要有Dispatch Queue和Dispatch Source
Dispatch Queue:管理block(操作)
Dispatch Source:处理事件
一.并发串行,同步异步
1.GCD是面向任务和队列进行开发的,不用再关注线程这个概念.
2.GCD中有两个重要的概念:队列和任务
2.1队列决定了任务的执行方式,并发还是串行.
2.1.1并发:多个任务同时执行
2.1.2串行:任务一个接一个执行
2.1.3并发和串行只是决定了任务的执行方式,和开不开线程没有半毛钱关系
2.2任务决定了具不具备开线程的能力,异步任务和同步任务.
2.2.1异步:具有开线程的能力,但具体开多少个子线程,不是由我们决定,是由系统根据任务的多少决定开几个子线程比较合适.
2.2.2同步:不具备开线程的能力,所有的任务都放在主线程执行.因为只有一个线程,也就没有了并发和串行的概念.同步任务全部都是串行的.
2.2.3.1同步(sync)和异步(async)是修饰任务的,决定了具不具备开线程的能力
2.2.3.2并发和串行是修饰队列的,决定了队列中的任务是一起执行还是一个接着一个执行.
2.2.3.3而并发的前提需要有多个线程,只有异步才会创建多个线程,所以同步异步的优先级高于并发串行的优先级.
总结:
异步串行:
异步具备了开线程的能力,串行决定了任务一个接着一个执行.所以线程开多了也没什么卵用,系统只会开一条子线程,任务在这条子线程中一个接着一个执行.
异步并发:异步具备了开线程的能力,并发决定了子线程中的任务一起执行.所以系统分批开线程,每批开多条线程,多条线程一起执行线程中的任务.但每批具体开多少条线程是由系统决定,并不一定是有多少个任务就开多少个线程.线程开辟的数量(n)和任务的数量(m)成正相关,但并不是严格的一一对应.当开辟的线程数量(n)小于总的任务(m)的时候,前线程个数(n)个线程同时执行.而且当前边的某个线程执行完任务以后,这个线程死亡或者继续给它分配其他任务也是由系统说了算,我们无法控制.
同步串行:同步不具备开线程的能力,所以所有的任务都在主线程中执行.串行决定了任务一个接一个执行.最后的结果就是所有的任务都在子线程中循规蹈矩的一个接一个执行.
同步并发:同步不具备开线程的能力,所以所有的任务都在主线程中执行.并发决定了任务一起执行.但任务一起执行的前提是有多个线程,这里只有一个线程,所以并发不起来(同步异步的优先级高于并发串行的优先级).所以最后的结果还是所有的任务都在主线程中循规蹈矩的一个接一个执行.
二.队列
1.队列:GCD中一共有两个重要的概念:队列和任务.其中任务需要放在队列中才可能被执行.
1.1队列的分类:GCD中任务一共被分为5种:
high优先级的
default优先级的
low优先级的
backGround队列
主队列
1.1.1其中优先级高的里边的任务被执行的频率高一点点.
1.1.2一般自己创建的队列一般都用default类型.
1.2.1主队列中的任务都放在主线程中执行.
1.2.2其他的队列对应的都是子线程,他们中的任务要交给处于线程池中的线程执行.
1.2.2.1线程一共有5种状态:
①创建:创建完成,但没有开启,在内存中存在,但没有被放入线程池
②就绪:创建完成,已经开启,在内存中存在,已经被放入线程池
③被宠幸:创建完成,已经启动,在内存中存在,在线程池,并且CPU调度,队列把任务交给它执行
④阻塞:创建完成,已经启动,在内存中存在,被移出线程池,不能执行任务.
⑤销毁:不在线程池,也不在内存,被销毁,内存被回收
注:
1.这里说的内存是整个进程开辟的内存,进程是分配内存的最小单位,程序一启动,进程就划分一块内存,进程中所有的操作都在这块特定的内存中完成.
2.线程处在特定的内存中,并不一定就具有了执行任务的能力,要想执行任务,必须把它放入线程池中,放入线程池中的方式为start线程.
3.start线程以后,线程就具备了执行任务的能力,但具体执行不执行任务得有系统调度,系统调度队列把任务交给你执行你才会执行,否则就一直处于饥饿的就绪状态.
三.主队列
1.执行优先级
1.1非主队列:
其实任务放在队列中执行的整个逻辑过程是这样的:异步同步决定了是否具备开线程的能力,并发串行决定了任务在队列中如何执行.这个时候异步同步的优先级大于并发串行.
1.2主队列:如果是主队列的话,任务放在主队列,而主队列中的所有任务都需要放在主线程中执行,那么即使你开了子线程也是浪费,所以这个时候不管你是异步还是同步也不会开子线程.这个时候主队列的优先级高于异步同步.
1.3总结上面两点:
主队列优先级>异步同步>并发串行
2.同步主队列(死锁)
2.1同步:同步要求任务一个接一个执行,也就是说前一个任务没有执行完毕,后一个任务不能开始.后一个任务是在拿到前一个任务执行结果以后才开始的.
2.2主队列:任务放在主队列中执行,主队列中所有的任务都要放在主线程中执行.
2.3当调用同步主队列这个方法(
- (void)asyncMainQueue )的时候,( -
(void)asyncMainQueue )就放入主队列,只有拿到它的执行结果(也就是说只有这个方法执行完以后,程序执行到红色的}后),后放入这个队列中的任务(NSLog(@"-----下载图片1---%@", [NSThread
currentThread]);)才会执行.但-
(void)asyncMainQueue这个方法要想执行完毕的前提是(NSLog(@"-----下载图片1---%@", [NSThread
currentThread]);)必须执行完毕.这个时候就引起了死锁.
-
(void)asyncMainQueue
{
// 1.主队列(添加到主队列中的任务,都会自动放到主线程中去执行)
dispatch_queue_t
queue = dispatch_get_main_queue();
// 2.添加 任务 到主队列中
异步 执行
dispatch_async(queue,
^{
NSLog(@"-----下载图片1---%@", [NSThread currentThread]);
});
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent*)event
{
// //创建一个并发队列
//
dispatch_queue_t queue =
dispatch_queue_create("downLoad-queue", DISPATCH_QUEUE_CONCURRENT);
//创建一个串行队列
dispatch_queue_tqueue =dispatch_queue_create("queue",DISPATCH_QUEUE_SERIAL);
//创建一个异步函数,因为下载图片是耗时操作,所以放在子线程中
dispatch_async(queue, ^{
NSLog(@"%@", [NSThreadcurrentThread]);
//1.下载路径
NSURL*url = [NSURLURLWithString:@"http://img2.kwcdn.kuwo.cn/star/KuwoArtPic/2013/22/1396932137246_w.jpg"];
//2.把url数据转化成2进制数据
NSData*data = [NSDatadataWithContentsOfURL:url];
//3.把2进制数据转化成图片
UIImage*image = [UIImageimageWithData:data];
//4.刷新UI
/**这里为什么没有死锁呢?
*一定要抓住本质:
死锁:主队列+同步而不是同步+主队列
只有在主队列的任务中,再次添加同步任务才会导致死锁.这里主队列的任务指的是红色的代码块,而紫色的函数并不在主队列中,它还在子线程中.如果在红色的代码块中又创建了同步任务,那么就会导致死锁.
*/
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"%@", [NSThread
currentThread]);
//赋值
[self._imageBtn setBackgroundImage:image
forState:UIControlStateNormal];
});
});
}
补充:
线程间通信:
1.线程间通信是通过队列完成的
2.子线程是放在线程池中的,主线程不放在线程池,所以主线程和子线程之间无法之间连接起来.只能通过队列.
总结:GCD中两个概念:用block封装好任务,把它放在队列中,它会自动执行.
1.队列:存放任务的
2.任务:用block来封装
*/
/**
* GCD作用:
延时操作:
下边四种方法都可以进行延时操作,但最后一种会卡死主线程,所以不推荐使用;
第一种,在什么队列中调用perform方法就在什么队列中执行,不能控制.
推荐使用GCD:既不会卡死主线程,而且可以控制在主线程还是子线程执行
①如果放主队列(dispatch_get_main_queue()),就在主队列中执行
dispatc_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(3 * NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
NSLog(@"这里的任务会在三秒中以后执行,并且在主队列中执行");
});
②如果放全局并发队列就在子线程中执行
1.1不会卡住主线程:在主线程执行
[self performSelector:@selector(download:)
withObject:@"http://baidu.com" afterDelay:3];
1.2不会卡主主线程:在主线程执行
dispatc_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(3 * NSEC_PER_SEC)),dispatch_get_main_queue(), ^{
NSLog(@"这里的任务会在三秒中以后执行,并且在主队列中执行");
});
1.3不会卡主主线程:在子线程执行,会创建子线程
dispatch_queue_t queue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatc_after(dispatch_time(DISPATCH_TIME_NOW,
(int64_t)(3 * NSEC_PER_SEC)),queue, ^{
NSLog(@"这里的任务会在三秒中以后执行,并且在子线程中执行");
});
1.4会卡住主线程:在主线程中执行
[NSThread
sleepForTimeInterval:3];
NSLog(@"这里的任务会在三秒中以后执行,在主线程中执行,并且会卡住主线程");
*/
- (void)viewDidLoad
{
[superviewDidLoad];
[selfsetupThread];
}
- (void)setupThread
{
dispatch_queue_tmainQueue =dispatch_get_main_queue();
dispatch_queue_tglobalQueue =dispatch_get_global_queue(0,0);
dispatch_sync(globalQueue, ^{
NSLog(@"2 ------%@", [NSThreadcurrentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"4------%@", [NSThreadcurrentThread]);
});
dispatch_async(globalQueue, ^{
NSLog(@"3------%@", [NSThreadcurrentThread]);
});
dispatch_async(globalQueue, ^{
NSLog(@"5------%@", [NSThreadcurrentThread]);
});
NSLog(@"1------%@", [NSThreadcurrentThread]);
dispatch_async(mainQueue, ^{
NSLog(@"5------%@", [NSThreadcurrentThread]);
});
dispatch_async(mainQueue, ^{
NSLog(@"6------%@", [NSThreadcurrentThread]);
});
[selfperformSelectorInBackground:@selector(run)withObject:nil];
NSLog(@"8------%@", [NSThreadcurrentThread]);
}
- (void)run{
NSLog(@"7------%@", [NSThreadcurrentThread]);
}
1.首先执行的是sync函数并发队列或者默认情况,这两种情况写在前面的先执行
2.接着执行async并发队列
3.最后执行async串行队列