GCD的优势:
苹果公司为多核的并行运算提出的解决方案
会自动利用更多的CPU内核(比如双核,四核)
自动管理线程的生命周期(创建线程,调度任务,销毁线程)
只需要告诉GCD执行什么任务,不需要编写任何线程管理代码来告诉GCD去开多少线程之类的.
两个核心概念:
- 任务:以block块的形式封装任务
- 队列:用来存放任务
GCD使用的步骤:
定制任务
确定想做的事
将任务添加到队列中
GCD自动将队列中的任务取出,放到对应的线程中执行
任务的取出遵循队列的FIFO原则,先进先出,后进后出
两个常用来执行任务的函数:
同步函数:
dispatch_sync(dispatch_queue_t queue, ^(void)block)
异步函数:
dispatch_async(dispatch_queue_t queue, ^(void)block)
参数queue决定任务在哪个队列执行,参数block块用来封装任务代码
同步和异步的区别:
- 同步:只能在当前线程中执行任务,不具备开启新线程的能力
- 异步:可以在新的线程中执行任务,具备开启新线程的能力,但是不一定开启新线程
GCD的队列分为两大类型:
- 并发队列:可以让多个任务并发(同时)执行
- 串行队列:让任务一个接着一个执行(一个任务执行完毕才能执行下一个任务)
获得队列方法:
- 创建队列:
dispatch_queue_create(const char *label ,dispatch_queue_attr_t attr);
//第一个参数:标签,队列的名称,是C语言字符串,一般标明队列是干嘛的
//第二个参数:决定队列的类型(DISPATCH_QUEUE_CONCURRENT:并行队列; DISPATCH_QUEUE_SERIAL:串行队列)
- 获得全局并发队列:(默认就存在四种优先级的全局并发队列)
dispatch_get_global_queue(long identifier, unsigned long flags)
//第一个参数:优先级(四种优先级),一般使用默认优先级,传0即可
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
//第二个参数:留给未来使用,直接传0即可
获得主队列:
dispatch_get_main_queue()
//主队列中的任务都会放在主线程执行
//主队列中的任务在执行之前都会先检查主线程状态,如果发现主线程当前正在执行任务,会暂停队列中任务的调度.
使用create函数创建的并发队列和全局并发队列的主要区别:
- 全局并发队列在整个应用程序中本身是默认存在的并且对应有高优先级、默认优先级、低优先级和后台优先级一共四个并发队列,我们只是选择其中的一个直接拿来用。而Create函数是实打实的从头开始去创建一个队列。
- 在iOS6.0之前,在GCD中凡是使用了带Create和retain的函数在最后都需要做一次release操作。而主队列和全局并发队列不需要我们手动release。当然了,在iOS6.0之后GCD已经被纳入到了ARC的内存管理范畴中,即便是使用retain或者create函数创建的对象也不再需要开发人员手动释放,我们像对待普通OC对象一样对待GCD就OK。
- 在使用栅栏函数的时候,苹果官方明确规定栅栏函数只有在和使用create函数自己的创建的并发队列一起使用的时候才有效
- 其它区别涉及到XNU内核的系统级线程编程,不一一列举。
给出一些参考资料(可以自行研究):
- GCD API: https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD_libdispatch_Ref/index.html#//apple_ref/c/func/dispatch_queue_create
- Libdispatch 版本源码:http://www.opensource.apple.com/source/libdispatch/libdispatch-187.5/
- GCD开源:
http://libdispatch.macosforge.org
队列组:(dispatch_group_t)
- 可以控制里面任务的执行顺序:
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group,queue, ^{
NSLog(@"任务一");
});
dispatch_group_async(group,queue, ^{
NSLog(@"任务二");
});
dispatch_group_notify(group,queue, ^{//queue决定了在哪个线程调用
//当任务一和任务二执行完毕,也就是group队列组中的任务执行完毕后,才会来到这里
//这里是在子线程中执行的,因为queue是个全局并发队列
});
其他方法:
- 另一个异步函数:
void dispatch_async_f(dispatch_queue_t queue,void *context,dispatch_function_t work);
//第一个参数:队列
//第二个参数:传给第三个函数的参数
//第三个参数:封装任务的函数的指针 从定义可以看出来:typedef void (*dispatch_function_t)(void *);
被调用的C函数形式:
//根据函数指针dispatch_function_t的定义:typedef void (*dispatch_function_t)(void *);—>需要定义一个返回值void类型,参数为 void* 的函数
void run(void* param)
{
}
- 延迟提交:
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, ^(void)block)
//第一个参数:开始时间
//第二个参数:指定任务执行的队列(只要不传主队列,都是在子线程调用block)
//第三个参数:任务代码
dispatch_time(dispatch_time_t when, int64_t delta)
//第一个参数:开始时间
//第二个参数:在第一个时间上延迟多久的时间,GCD的时间是以纳秒为单位 (例:(int64_t)(2.0*NSEC_PER_SEC)代表2秒—>2*10^9就是2秒)
- 相关枚举:
DISPATCH_TIME_NOW 代表现在
DISPATCH_TIME_FOREVER 代表无限久以后
NSEC_PER_SEC 秒
NSEC_PER_MSEC 毫秒
NSEC:纳秒。
USEC:微妙。
SEC:秒
PER:每
注意:dispatch_after是延迟提交,不是延迟运行
官方文档的说明:
Enqueue a block for execution at the specified time.
Enqueue,就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行!
NSLog(@"Begin add block...");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"After...");
});
for (int i=0; i<1000000000; i++) {
}
for (int i=0; i<1000000000; i++) {
}
for (int i=0; i<1000000000; i++) {
}
for (int i=0; i<1000000000; i++) {
}
for (int i=0; i<1000000000; i++) {
}
打印结果:
**2016-06-18 20:27:46.881** **多线程**[**2005:279538**] **Begin add block...**
**2016-06-18 20:27:58.803** **多线程**[**2005:279538**] **After...**
从结果看出打印After...是在开始执行dispatch_after后的12秒,因此这里dispatch_after只是延时5秒提交block,并不是延时5秒后立即执行。因为后面的几个空转让已经提交的block延迟执行了.(因为这里都是在主线程中,后面空循环导致了主线程一直没空去执行已经提交的block,因此应该开其他线程来执行block,而不应该跟dispatch_after函数在同一个线程)
- 栅栏函数
void dispatch_barrier_async( dispatch_queue_t queue ,dispatch_block_t block);
//这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
void dispatch_barrier_sync( dispatch_queue_t queue ,dispatch_block_t block);
//同上,除了它是同步返回函数,也就是必须等block执行完毕才会执行后面的代码
dispatch_barrier_(a)sync只在自己创建的,并且是并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样
既然在串行队列上跟dispatch_(a)sync效果一样,要小心别死锁
dispatch_barrier_sync提交block在当前队列会造成死锁
貌似栅栏函数只是阻塞同一个队列的任务.这样只有前面的任务跟栅栏函数同一个队列才能保证栅栏函数在前面任务后执行.但是栅栏后面的任务都是在栅栏函数之后才执行
- 一次性代码(用来实现单例)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
- 快速迭代
dispatch_apply(size_t iterations, dispatch_queue_t queue, ^(size_t)block)
//第一个参数:迭代次数
//第二个参数:队列
//第三个参数:用来迭代的block
主线程也会参与
不能传主队列,因为主线程也会参与,否则死锁
传串行队列就跟for循环一样作用
传并行队列那么任务会并发执行
使用快速迭代剪切文件:
-(void)moveFile
{
//1.确定文件路径
NSString *sourcePath = @"/Users/gsy/Desktop/from";
//2.确定文件应该剪切到哪个目录'
NSString *targetPath = @"/Users/gaosiyang/Desktop/to";
//3.得到所有的文件
NSArray *subpaths = [[NSFileManager defaultManager] subpathsAtPath:sourcePath];
NSLog(@"%@",subpaths);
//4.执行迭代.把所有的文件都剪切到指定的地方
dispatch_apply(subpaths.count, dispatch_get_global_queue(0, 0), ^(size_t index) {
NSString *fileName = subpaths[index];
//4.1 拼接文件的全路径
//stringByAppendingPathComponent /
NSString *sourceFullpath = [sourcePath stringByAppendingPathComponent:fileName];
//4.2 目的地
NSString *targetFullpath = [targetPath stringByAppendingPathComponent:fileName];
//4.3 执行剪切操作
[[NSFileManager defaultManager] moveItemAtPath:sourceFullpath toPath:targetFullpath error:nil];
NSLog(@"%@---%@--%@",sourceFullpath,targetFullpath,[NSThread currentThread]);
});
}