什么是GCD:
Grand Central Dispath是异步执行任务的技术之一;
一般将应用程序中记述的线程管理的代码 在系统级中实现;
开发者只需要定义想要执行的任务 并追加到Dispath Queue中,GCD就能生成必要的线程并计划执行任务;
GCD的线程管理作为系统的一部分实现,提供了系统级的线程管理,比以前的线程效率更高;
示例场景:
对比如下两种实现,使用GCD和performSelector的方式相比,GCD更加简洁明了;
【Code-gcdTmp1】
/*
* GCD示例场景:在某线程中执行任务 任务执行完成后 z主线程处理结果
*/
-(void)gcdTmp1{
//执行后台线程
dispatch_queue_t dispatch = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(dispatch, ^{
//执行一段较耗时的操作
//处理结束后,主线程使用处理结果
dispatch_async(dispatch_get_main_queue(), ^{
//只在主线程可以进行的处理(例如用户界面更新)
});
});
}
【Code-cocoaTmp1】
/*
* Cocoa框架 NSObject类中提供的简单多线程实例方法performSelectorInBackground和performSelectorOnMainThread
*/
-(void)cocoaTmp2{
//执行后台线程
[self performSelectorInBackground:@selector(doSometiong) withObject:nil];
}
-(void)doSometiong{
@autoreleasepool {
//执行一段较耗时的操作
//处理结束后,主线程使用处理结果
[self performSelectorOnMainThread:@selector(doneSometiong) withObject:nil waitUntilDone:NO];
}
}
-(void)doneSometiong{
//只在主线程可以进行的处理(例如用户界面更新)
}
多线程:
我们知道Mac产品的操作系统启动应用后,需要将包含应用程序的CPU指令载入内存并执行;
一个CPU执行的CPU命令列为“一条无分叉路径” 即为线程;
这样的“无分叉路径”有多条时,即为多线程;
现在的CPU芯片都集成了许多个CPU(多核CPU);
上下文切换:
当发生操作系统事件时,如唤起系统调用等情况,内核会切换执行路径;
执行中路径的状态,如CPU寄存器等信息会保存到各自路径专用的内存块中;
从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令;
并行执行任务:
使用多线程,程序能在某个线程和其他线程之间反复多次进行上下文切换,这看上去好像是一个CPU核(就是一个CPU)能并列的执行多个任务;但实际上基本1个CPU核一次能执行的CPU命令始终为1(时间片轮转);这只是在一个CPU核上,如果有多个CPU核,就可以真正并行执行多线程了;
多线程编程:
一个线程是一个“无分叉路径”,一个CPU可以通过上下文切换使用多个线程完成任务,但某一时刻实际执行的还是一个线程,而多个CPU就可以并行执行多个线程了;这种利用多线程编程的技术就是 多线程编程;
多线程编程的问题:
数据竞争:多个线程同时更新资源而导致的数据不一致;
死锁:停止、等待事件的线程会导致多个线程相互持续等待;
而且,使用太多线程会消耗大量内存;
多线程编程最重要的优点:
保证应用程序的响应性能;
比如,应用程序启动,最先执行主线程,描绘界面、处理屏幕触摸事件等;
若,主线程执行了较为耗时的操作,如访问数据库,图像处理,就会阻塞主线程;
在iOS中,即妨碍主线程中被称为RunLoop的主循环的执行,从而导致界面卡顿;
我们这里介绍的GCD 大大简化了较为复杂的多线程编程;
GCD的使用:
定义想要执行的任务 并追加到Dispath Queue中,我们一开始已经写过了GCD的示例,通过block定义了想要执行的任务;
通过dispatch_async函数“追加”赋值在变量queue的Dispath Queue中,这样就可以使指定的Block在另一线程中执行;
Dispath Queue:
这是执行处理的等待队列,按照先入先出执行处理;
执行具体处理的时候,有两种Dispath Queue:
一种是 等待现在执行中处理的 Serial Dispath Queue;
另一种是 不等待现在执行中处理的 Concurrent Dispath Queue;
Serial Dispath Queue:
因为需要等待现在执行中的处理结束,所以会依次执行bkl0-6;
同时执行的处理只有一个,按照顺序执行;
【Code-serialTmp】
/*
* serial dispatch queue
*/
-(void)serialTmp{
void (^blk0)(void) = ^{NSLog(@"blk0");};
void (^blk1)(void) = ^{NSLog(@"blk1");};
void (^blk2)(void) = ^{NSLog(@"blk2");};
void (^blk3)(void) = ^{NSLog(@"blk3");};
void (^blk4)(void) = ^{NSLog(@"blk4");};
void (^blk5)(void) = ^{NSLog(@"blk5");};
void (^blk6)(void) = ^{NSLog(@"blk6");};
dispatch_queue_t dispatch = dispatch_queue_create("com.example.MySerialQueue", NULL);
dispatch_async(dispatch, blk0);
dispatch_async(dispatch, blk1);
dispatch_async(dispatch, blk2);
dispatch_async(dispatch, blk3);
dispatch_async(dispatch, blk4);
dispatch_async(dispatch, blk5);
dispatch_async(dispatch, blk6);
// dispatch_release(dispatch);
}
Concurrent Dispath Queue:
不用等待现在执行中的处理结束,所以首先执行bkl0,不管bkl0是否结束,都开始执行后面的bkl1;
不管bkl1是否结束都开始执行后边的bkl2…,如此进行;
由于使用了多个线程,这样可以并行的执行多个处理,并行处理的数量取决于当前系统;
【Code-concurrentTmp】
/*
* Concurrent dispatch queue
*/
-(void)concurrentTmp{
void (^blk0)(void) = ^{NSLog(@"blk0");};
void (^blk1)(void) = ^{NSLog(@"blk1");};
void (^blk2)(void) = ^{NSLog(@"blk2");};
void (^blk3)(void) = ^{NSLog(@"blk3");};
void (^blk4)(void) = ^{NSLog(@"blk4");};
void (^blk5)(void) = ^{NSLog(@"blk5");};
void (^blk6)(void) = ^{NSLog(@"blk6");};
// dispatch_queue_t dispatch = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t dispatch = dispatch_queue_create("com.example.MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(dispatch, blk0);
dispatch_async(dispatch, blk1);
dispatch_async(dispatch, blk2);
dispatch_async(dispatch, blk3);
dispatch_async(dispatch, blk4);
dispatch_async(dispatch, blk5);
dispatch_async(dispatch, blk6);
// dispatch_release(dispatch);
}
这里所谓的并行执行 就是使用多个线程同时执行多个处理;并行执行多个处理 使用的线程数由XNU内核决定,只生成所需线程,还会结束不再需要的线程;
Concurrent Dispath Queue执行的顺序可能是这样的:
线程0 | 线程1 | 线程2 | 线程3 |
bkl0——1 | bkl1(执行时间较长)——2 | bkl2——3 | bkl3——4 |
bkl4——5 | bkl6——7 | bkl5——6 |
|
dispatch_queue_create:
这是GCD生成Dispath Queue的API;我们在上边的示例中已经看到了它;
生成Serial Dispatch Queue:
dispatch_queue_t dispatch = dispatch_queue_create("com.example.MySerialQueue", NULL);
生成Concurrent Dispatch Queue:
dispatch_queue_t dispatch = dispatch_queue_create("com.example.MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
Serial Dispatch Queue:
对于生成的Serial Dispatch Queue虽然受系统资源限制,但理论上可以生成任意多个;
而且,虽然一个Serial Dispatch Queue中同时只能执行一个追加处理,但是如果将处理分别放入4个Serial Dispatch Queue中,即为同时执行4个处理——即多个Serial Dispatch Queue可并行执行;
一旦生成Serial Dispatch Queue并追加处理,系统对于一个Serial Dispatch Queue只能生成并使用一个线程(如果有2000个Serial Dispatch Queue,就会生成2000个线程);
多线程的问题就是会消耗大量内存,引起上下文切换,大幅降低响应性能;
Serial Dispatch Queue的使用场景:
多个线程更新相同资源导致数据竞争时Serial Dispatch Queue,这样就只有一个线程操作数据,数据就安全了;
如操作数据库,操作文件等;
Concurrent Dispatch Queue:
介绍完Serial Dispatch Queue,我们知道应该生成仅所需数量的Serial Dispatch Queue;
当想并行执行且不会发生数据竞争等问题的处理时,使用Concurrent Dispatch Queue;
对于Concurrent Dispatch Queue来说,由于内核只使用有效管理的线程,因此不会发生Serial Dispatch Queue的生成过多线程的问题;
再看dispatch_queue_create函数:
函数的第一个参数指定Dispatch Queue的名称;
该命名推荐使用逆序全程域名的方式;且会出现在程序中作为Dispatch Queue的标识;
第二个参数设置为NULL,如果是生成Concurrent Dispatch Queue,则替换该参数,使用DISPATCH_QUEUE_CONCURRENT;
该函数的返回值是dispatch_queue_t类型,所有的queue均为此类型变量;
关于Dispatch Queue的释放:
Dispatch Queue必须通过程序员进行释放;
通过dispatch_queue_create函数生成的Dispatch Queue在使用结束后通过dispatch_release函数释放;
代码:dispatch_release(dispatch);
有了dispatch_release(),相应的就有dispatch_retain()函数:即Dispatch Queue也像OC对象的引用计数内存管理一样;
需要通过这两个函数进行内存管理;
相应的通过dispatch_queue_create生成的queue也需要释放;
queue追加的block会通过dispatch_retain函数持有Dispatch Queue,即便刚追加完Block就dispatch_release()也是可以的,queue会在Block执行完之后释放;
在iOS6.0之后的版本,Dispatch Queue的内存管理已经纳入了ARC的管理范畴,此时GCD对象就如同普通的OC对象一样,不再需要dispatch_retain和dispatch_release了;
Main Dispatch Queue和Global Dispatch Queue:
对于获取dispatch_queue_t类型的变量的方式,我们已经学了一种:通过dispatch_queue_create函数;
另外一种就是可以获取系统标准提供的Dispatch Queue;
也就是说,即便不用dispatch_queue_create生成,系统也会给我们提供几个Dispatch Queue:
即Main Dispatch Queue和Global Dispatch Queue;
Main Dispatch Queue:
这是主线程中执行的Dispatch Queue,主线程只有一个,它是一种Serial Dispatch Queue;
追加到Main Dispatch Queue中的处理 是在主线程的RunLoop中执行的,因此需要将用户更新界面的一些必须在主线程中追加的处理 追加到Main Dispatch Queue中使用;
这与之前我们介绍的NSObject类的performSelectorOnMainThread方法的执行是一致的;
通过dispatch_get_main_queue()获取;
Global Dispath Queue:
这是所有应用程序都能够使用的Concurrent Dispath Queue;
即可以不通过dispatch_queue_create函数生成Concurrent Dispath Queue,直接获取Global Dispath Queue使用即可;
我们可以通过dispatch_get_global_queue函数获取Global Dispath Queue:
示例:dispatch_queue_t dispatch = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一个参数表示Global Dispath Queue的执行优先级;
Global Dispath Queue的四种执行优先级:
高优先级:High Priority(参数 DISPATCH_QUEUE_PRIORITY_HIGH)
默认优先级:Default Priority,(参数DISPATCH_QUEUE_PRIORITY_DEFAULT)
低优先级:Low Priority,(参数DISPATCH_QUEUE_PRIORITY_LOW)
后台优先级:Background Priority,(参数DISPATCH_QUEUE_PRIORITY_BACKGROUND)
通过内核管理的用于Global Dispath Queue的线程,将使用相应的Global Dispath Queue优先级作为线程的执行优先级进行使用;追加处理时,需要选用与之对应的执行优先级的Global Dispath Queue;(注意这个优先级只能作程度上的划分,并不具有实时性)
相比于生成的Dispath Queue,获取的Dispath Queue要更加容易使用;