GCD函数与队列&其他详尽总结
简介
概念
1.全称是 Grand Central Dispatch,纯 C 语言,提供了非常多强大的函数
1.Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。
2.官方文档介绍:https://developer.apple.com/documentation/dispatch
2.将任务添加到队列,并且指定执行任务的函数
优势
1.GCD是apple为多核的并行运算提出的解决方案。
2.GCD能较好的利用CPU内核资源。
3.GCD不需要开发者去管理线程的生命周期。
4.使用简便,开发者只需要告诉GCD执行什么任务,并不需要编写任何线程管理代码。
任务和队列
任务
就是需要执行的操作,是 GCD 中放在 block 中在线程中执行的那段代码
先看一下下面的代码我们将其进行拆分,还原最基础的写法。
- (void)syncTest{
// 把任务添加到队列 --> 函数
// 任务 _t ref c对象
dispatch_block_t block = ^{
NSLog(@"hello GCD");
};
//串行队列
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
// 函数
dispatch_async(queue, block);
// 函数 队列
// 函数队列
// block () - GCD 下层封装
}
再去想上面的总结【将任务添加到队列,并且指定执行任务的函数】然后我们就能明白 所谓的任务就是就是 block里的执行。
分析:
- 任务 使用block封装
- 任务 的block没有参数也没有返回值
函数
执行任务的函数,将任务放入队列中,下层进行任务与队列的关系处理。
- 异步函数
dispatch_async(queue, block);
- 不用等待当前语句执行完毕,就可以执行下一条语句。
- 会开启线程执行block的任务(除了和主队列配合)
- 异步是多线程的代名词
- 同步函数
dispatch_sync(queue, block);
- 必须等带当前语句执行完毕,才会执行下一条语句。
- 不会开启线程
- 在当前执行block的任务
队列
下面所示就是如何创建队列
- 串行队列
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", NULL);
- 上面第二个参数 传NULL 或 DISPATCH_QUEUE_SERIAL 即为串行队列。因为它是一个宏 【 #define DISPATCH_QUEUE_SERIAL NULL】
- 串行队列就可以想象为 一个,一次只能通过一辆汽车单行车道的山体隧道。 比较窄,同一时间内只能出来一辆车
- 并发队列
dispatch_queue_t queue = dispatch_queue_create("com.lg.cn", DISPATCH_QUEUE_CONCURRENT);
- 上面第二个传如 DISPATCH_QUEUE_CONCURRENT 即为并发队列。
- 并发队列就可以想象为 一个, 多行车道可以通过多辆汽车的山体隧道。比较宽,同一时间可同时有多个车冒头。
除了上面的两种队列 还有下面两种较特殊的队列写法
- 主队列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
- 专门用来在主线程上调度任务的串行队列
- 不会开启线程
- 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度;
- 全局队列
dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0)
- 为了方便程序员的使用,苹果提供了全局队列 dispatch_get_global_queue(0, 0)
- 全局队列是一个并发队列 在使用多线程开发时,如果对队列没有特殊需求,在执行异步任务时,可以直接使用全局队列
注意:
1.任务资源有大有小(函数内部复杂度不同,执行耗时也不同)
2.队列不可执行任务,只是一个FIFO(先进先出)的存储容器,任务是由cpu调度的线程来执行
3.出队列不是立马执行,而是等待cpu调度到可执行的线程中
函数和队列组合
1、同步函数串行队列
- (void)serialSyncTest{
//1:创建串行队列
dispatch_queue_t queue = dispatch_queue_create("Cooci", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<13; i++) {
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
}
运行:
函数与队列[7013:320867] 0-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 1-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 2-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 3-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 4-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 5-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 6-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 7-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 8-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 9-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 10-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 11-<NSThread: 0x6000020a8300>{number = 1, name = main}
函数与队列[7013:320867] 12-<NSThread: 0x6000020a8300>{number = 1, name = main}
- 不会开启线程,在当前线程执行任务。
- 任务串行执行,任务一个接着一个
- 会产生堵塞
2、同步函数并发队列
///同步 +并发队列
-(void)concurrectSyncTest{
//创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.hh.jj", DISPATCH_QUEUE_CONCURRENT);
for(int i = 0;i<10; i++){
///同步函数
dispatch_sync(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行
函数与队列[7422:360463] 0-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 1-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 2-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 3-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 4-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 5-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 6-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 7-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 8-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] 9-<NSThread: 0x600002838840>{number = 1, name = main}
函数与队列[7422:360463] hello queue
- 不会开启线程,在当前线程执行任务;
- 任务一个接着一个
3、异步函数串行队列
///串行队列 + 异步函数
-(void)serialAsyncTest{
//1.创建串行队列
dispatch_queue_t queue = dispatch_queue_create("com.hh.jj", DISPATCH_QUEUE_SERIAL);
for (int i = 0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行
函数与队列[7529:367173] hello queue
函数与队列[7529:367346] 0-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 1-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 2-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 3-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 4-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 5-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 6-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 7-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 8-<NSThread: 0x600001136440>{number = 6, name = (null)}
函数与队列[7529:367346] 9-<NSThread: 0x600001136440>{number = 6, name = (null)}
- 只会开启一条新线程
- 任务顺序执行 一个 接一个。
- 分析:这里可以看到主线程的任务( NSLog(@"hello queue");)并没有等待 在新线程的串行队列执行完而执行。这里不是绝对的,因为这里的同步函数是一条新的子线程上运行的任务 他们在串行队列中是有序的 。和主线程 不是一个线程他们之间的任务执行互不干扰 ,这里和任务的复杂度及cpu的调度有关,说白了 就是 hello queue 可能在 0前或者0 - 9 或 9 后 任意一个位置打印。
4、异步函数并发队列
///并发队列 + 异步函数
-(void)concurrentAsyncTest{
///1.创建并发队列
dispatch_queue_t queue = dispatch_queue_create("com.hh.jj", DISPATCH_QUEUE_CONCURRENT);
for (int i = 0; i<10 ; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
}
运行
函数与队列[7666:379427] hello queue
函数与队列[7666:379501] 0-<NSThread: 0x6000008d1980>{number = 6, name = (null)}
函数与队列[7666:379506] 2-<NSThread: 0x6000008d5a80>{number = 3, name = (null)}
函数与队列[7666:379502] 3-<NSThread: 0x6000008d89c0>{number = 7, name = (null)}
函数与队列[7666:379503] 1-<NSThread: 0x6000008d5b40>{number = 4, name = (null)}
函数与队列[7666:379501] 4-<NSThread: 0x6000008d1980>{number = 6, name = (null)}
函数与队列[7666:379506] 5-<NSThread: 0x6000008d5a80>{number = 3, name = (null)}
函数与队列[7666:379505] 6-<NSThread: 0x6000008ae480>{number = 8, name = (null)}
函数与队列[7666:379502] 7-<NSThread: 0x6000008d89c0>{number = 7, name = (null)}
函数与队列[7666:379501] 8-<NSThread: 0x6000008d1980>{number = 6, name = (null)}
函数与队列[7666:379503] 9-<NSThread: 0x6000008d5b40>{number = 4, name = (null)}
- 会开启多条线程,在每条新线程执行任务。
- 任务异步执行,没有顺序,和CPU调度有关
5、同步函数主队列
2251862-60f29c3661eccc63.png
- 会发生死锁。
- 分析 :因为主队列也是串行队列 ,test05方法在串行队列中执行任务, 而此时同步函数 dispatch_sync 将任务block 放入到了 主队列中 ,而 串行队列 只能执行完一个在进行执行下一个,此时 block 等待着test05完成。而 test05又在等待着 block 任务完成才算自己完成任务。所以这就造成了死锁。
6、异步函数主队列。
/**
主队列异步
不会开线程,并且 永远优先执行 异步函数任务 之外的 任务无论任务多复杂。在顺序执行异步函数任务
*/
- (void)mainAsyncTest{
dispatch_queue_t queue = dispatch_get_main_queue();
for (int i = 0; i<10; i++) {
dispatch_async(queue, ^{
NSLog(@"%d-%@",i,[NSThread currentThread]);
});
}
NSLog(@"hello queue");
NSLog(@"hello queue");
NSLog(@"hello queue");
}
运行
函数与队列[8762:452443] hello queue
函数与队列[8762:452443] hello queue
函数与队列[8762:452443] hello queue
函数与队列[8762:452443] 0-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 1-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 2-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 3-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 4-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 5-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 6-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 7-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 8-<NSThread: 0x600002db0380>{number = 1, name = main}
函数与队列[8762:452443] 9-<NSThread: 0x600002db0380>{number = 1, name = main}
- 不会开启新线程。
- 并且异步函数任务之外的任务 永远在其之前完成无论任务多么得复杂。
常见相关面试题
面试题 -1
- (void)textDemo{
dispatch_queue_t queue = dispatch_queue_create("cooci", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"1");
// 耗时
dispatch_async(queue, ^{
NSLog(@"2");
dispatch_async(queue, ^{
NSLog(@"3");
});
NSLog(@"4");
});
NSLog(@"5");
}
分析
- 首先当前任务量大小是为一致的前提分析此题答案
-
当前是 一个 并发队列,此时并发队列 里填入了一个 异步函数任务,异步函数执行任务内又有一个异步函数;异步函数+上并发队列会开启新的线程,然后在看一下结构 NSLog(@"1"); 优先执行,然后打印 NSLog(@"5");然后外层异步函数在新线程上执行任务块block ,block任务 先执行NSLog(@"2") ,又来到 异步函数,异步函数会在此队列关系上会开启新线程,所以当前线程,将block任务丢给其他线程处理, 继续向后执行,NSLog(@"4"), 然后 其他线程上的任务block执行完毕。下面画个图可能更明白。
面试-1.jpg
-
由此我们得知答案为打印 15 2 4 3 可是真的是这样吗?其实不然。why?
答: 一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入输出操作时,就会让出cpu并进入暂时中止状态,拿上面案例来说,如果 当前线程 在开启新线程1和抛出5数字之间线程发生了导致阻塞的事件,此时线程会让出cpu进入暂时中止状态。而 新线程1的任务在异步执行,运行到 NSLog函数将2抛出来了, 此时 当前线程阻塞事件解除,线程进入就绪状态,重新到就绪队列中排队。这时候被cpu调度选中之后会从原来停止的位置继续执行。并打印出5.所以此时为 为 12 5 ... ,可是它在哪个时间段解除阻塞,时机不确定,所以5的打印 有可能 在 3 后 ,4 后。 除了阻塞问题 跟 当前任务的大小也有关系,不过当前(任务量相当都是NSLog()); -
在主线程调用 -(void)textDemo 运行 多试几次。在子线程 中调用 -(void)textDemo 运行并多试几次。 你就会发现上面所说的问题。
面试题 -2
- (void)wbinterDemo{//
dispatch_queue_t queue = dispatch_queue_create("com.lg.cooci.cn", DISPATCH_QUEUE_CONCURRENT);
// 1 2 3
// 0 (7 8 9)
dispatch_async(queue, ^{ // 耗时
NSLog(@"1");
});
dispatch_async(queue, ^{
NSLog(@"2");
});
// 堵塞哪一行
dispatch_sync(queue, ^{
NSLog(@"3");
});
NSLog(@"0");
dispatch_async(queue, ^{
NSLog(@"7");
});
dispatch_async(queue, ^{
NSLog(@"8");
});
dispatch_async(queue, ^{
NSLog(@"9");
});
分析:首先是一个并发队列。并发队列可以同一时刻执行多个任务。就是比较宽。的数据结构。然后12任务的执行函数为异步。异步函数+并发队列可开启多条线程 。3任务的执行函数为同步,同步函数+并发队列不会开启新的线程。会阻塞当前线程,直到它自己的任务完成。 0在当前线程上。所以 3和0处在一条线程上,并且3一定在0 的前边。7 8 9 任务依旧是由异步函数执行的。所以会开启多线程。这里请注意并不是每写一个异步函数 +添加到队列 就会向对应得开启一条新的线程。下面画图表示
面试-2.jpg- 由此我们可以看出 此答案 为 3 一定在 0 的前面 。任务1 任务2 异步没顺序 任务7 8 9 异步没顺序。由于 12 并没有使用当前线程 。而3 是同步函数并发队列。还是走的当前线程。在某些情况下 3 也可能在 1 前面 或后面 或2 前面 或2 后面 所以 123 无序。但是一定不会跑到 789 区域。因为 789 的 线程是在 当前任务 0 完成之后 cpu 重新调度 或 开启新的线程的。
扩展 线程的5种状态
1、新生状态: 当我们用NSTherad构造方法创建一个新线程时。如
NSThread * th = [[NSThread alloc]initWithTarget:self selector:@selector(wbinterDemo) object:nil];
- 该线程就是创建状态,此时它已经有了相应的内存空间和其它资源,但是还没有开始执行。
2、就绪状态: 新建线程对象后,调用该线程的 start()方法就可以启动线程。
[th start];
- 新建线程对象后,调用该线程的 start()方法就可以启动线程。当线程启动时,线程进入就绪状态(runnable)。由于还没有分配CPU,线程将进入线程队列排队,等待 CPU 服务,这表明它已经具备了运行条件。当系统挑选一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态。系统挑选的动作称之为“CPU调度"。
3、运行状态: 当就绪状态的线程被调用并获得处理器资源时,线程就进入了运行状态。
4、阻塞状态:一个正在执行的线程在某些特殊情况下,如被人为挂起或需要执行耗时的输入输出操作时,将让出 CPU 并暂时中止自己的执行,进入堵塞状态。
- 堵塞时,线程不能进入排队队列,只有当引起堵塞的原因被消除后,线程转入就绪状态。重新到就绪队列中排队等待,这时被CPU调度选中后会从原来停止的位置开始继续执行。
- 记住:阻塞被消除后是回到就绪状态,不是运行状态。
5、死亡状态: 线程主动取消,自然死亡。线程即处于死亡状态。处于死亡状态的线程不具有继续运行的能力。(苹果主线程自动加入runloop,子线程需要手动加入)