是异步执行任务的技术之一。它将应用程序中线程管理的代码放在系统级中实现。开发者只须要定义想执行的任务并追加到适当的Dispatch Queue中,至于具体是哪一个线程执行、如何执行该任务,开发者不需管,交由GCD来管理。编程
所以GCD优于其余异步技术的点:线程管理交由系统管理,效率更高。bash
源码通过编译连接后生成二进制目标代码(CPU命令列),CPU顺序执行这些命令列。 一个CPU核执行CPU命令列是一条无分叉的路径,即为线程。当一个线程被挂起时,当前CPU的寄存器等信息会保存到各自路径专用的内存块中(保存上下文),线程恢复继续执行,则从内存块中取出数据,复原CPU寄存器等信息,继续执行切换路径的CPU命令列(上下文切换)。多线程
多线程编程容易发生的问题:并发
多线程的优势:app
总览图: 异步
Dispatch Queue:任务队列,存储等待执行的任务。它会按照追加顺序(FIFO)执行任务async
串行队列缺点:每一个串行队列对应一个线程,当同时建立多个串行队列时,对应多个线程。此时可能会发生数据竞争。 而一个并行队列虽然同时会有多个线程,可是无论生成多少线程,都会有XNU内核来管理,因此不需担忧串行队列的问题。函数
// DISPATCH_QUEUE_SERIAL/NULL/0 串行队列
// DISPATCH_QUEUE_CONCURRENT 并行队列
// ARC状况不须要手动释放
dispatch_queue_t queue = dispatch_queue_create("queueIdentifier", 0);
dispatch_async(queue, ^{
NSLog(@"执行任务");
});
复制代码
主队列:oop
dispatch_queue_t mainQueue = dispatch_get_main_queue();
复制代码
主队列是在main()被调用前,自动被系统建立的串行队列,和主线程相关联。(但这并不表明主队列中的任务只能被主线程执行)。ui
若是想要(其余线程)执行主队列中的任务,只能用如下三种方式中的一种:
全局并行队列:Global Dispatch Queue
/*
#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
*/
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
复制代码
注意:使用函数本身建立的队列的优先级和globalQueue中默认优先级是同等水平的。若是想要改变队列的优先级须要使用函数dispatch_set_target_queue。
dispatch_set_target_queue(要改变的对象obj, 目标队列tq)
复制代码
给一个队列设置目标队列会改变这个队列的某些行为:
注意:设置目标队列是,不要造成继承循环。
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", NULL);
dispatch_queue_t sQ2 = dispatch_queue_create("serialQ2", NULL);
dispatch_queue_t sQ3 = dispatch_queue_create("serialQ3", NULL);
dispatch_async(serialQ, ^{
NSLog(@"serialQ_1---------->");
});
dispatch_async(sQ3, ^{
NSLog(@"serialQ_3----------->");
});
dispatch_async(sQ2, ^{
NSLog(@"serialQ_2----------->");
});
此时结果是不定的,由于三个serialQueue是并行的。
复制代码
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t serialQ = dispatch_queue_create("serialQ", NULL);
dispatch_queue_t sQ2 = dispatch_queue_create("serialQ2", NULL);
dispatch_queue_t sQ3 = dispatch_queue_create("serialQ3", NULL);
dispatch_set_target_queue(serialQ, globalQ);
dispatch_set_target_queue(sQ2, serialQ);
dispatch_set_target_queue(sQ3, serialQ);
dispatch_async(serialQ, ^{
NSLog(@"serialQ_1---------->");
});
dispatch_async(sQ3, ^{
NSLog(@"serialQ_3----------->");
});
dispatch_async(sQ2, ^{
NSLog(@"serialQ_2----------->");
});
此时结果是肯定的 一、三、2。而且使用的是同一个线程
复制代码
正常状况下:不管是串行仍是并发队列,只要设置了目标队列,以后任务都会向上传到它的上一级目标队列中。所以任务其实最后都传到根上去了。因此如今这条链上的队列应该都是公用一个线程池的。所以若是要获取当前队列,即获取执行当前代码所关联的队列,结果是不定的。
设置必定时间后,将任务异步添加到队列中(而不是将任务加到队列中,必定时间后执行),所以时间可能不够准确。
函数直接返回。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"执行任务");
});
复制代码
- (dispatch_time_t)getDispatchTimeByDate:(NSDate *)date {
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second); // 取浮点数的小数部分和整数部分
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC; // 小数部分转纳秒
milestone = dispatch_walltime(&time, 0);
return milestone;
}
复制代码
当但愿知道加入到队列中的任务什么时候所有执行完成时,有两种方式:1. 将任务加入到串行队列,结束的任务加到最后便可。2. 任务加到多个队列中或者加到并行队列中,须要用到group。
代码解析:
// 将任务block异步加到队列中,而且将block关联到group群组中,即block持有group
dispatch_group_async(group, globalQ, ^{
NSLog(@"global_blk0---->%@",[NSThread currentThread]);
});
// 当持有group的全部任务都执行完成后,会将任务加入到队列中去执行。若是group中无任务,则当即将任务加到指定队列中
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done---->%@",[NSThread currentThread]);
});
复制代码
访问数据时,容易出现数据竞争的问题。 写操做不容许其余读写操做并行执行,读操做可与其余读操做并行执行。
须要dispatch_barrier_async为须要单独执行的操做加入栅栏。
代码:
相似于前面的dispatch_wait,线程执行到该函数,会进入等待状态,直到同步的任务结束,才会返回,继续执行。
容易形成的问题:死锁
// 线程进入等待状态,直到barrierBlock执行完成才会返回
dispatch_barrier_sync(serialQ, ^{
});
复制代码
dispatch_apply函数结合了dispatch_sync和dispatch group的功能。该函数按照指定次数将任务block添加到队列中,并等待所有任务执行完成,再返回。
并发队列:
应用:(不关心数据顺序时)
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(global, ^{
dispatch_apply([array count], global, ^(size_t i) {
NSLog(@"%@", array[i]);
});
});
复制代码
注意:
① 1,2必须是一对一的,由于执行1,dispatch_object_t的suspension count会+1;执行2会减一。当count>0时,会一直处于挂起状态。
② 当要暂停一个队列时,上述函数仅对本身手动建立的队列有效,对于系统队列(global、main)无效的。
思想相似停车场:初始有n个车位,每来一辆车,先查看n是否大于0,若是n>0,车辆进入使用车位,n--。每辆车离开停车场n++。 当初始值设置为1时,能够做为锁来使用。
dispatch_once函数保证在整个应用程序执行中,只执行一次指定任务,即便在多线程环境下。
应用:单例模式
static NSObject *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[NSObject alloc] init];
});
return instance;
复制代码
读取较大文件时,可将其分割成合适大小并发读取,提升读取速度。