GCD 凭借其高性能和易用性,得到了众多开发人员的青睐。在工作中涉及到多线程时,采用的往往也是 GCD。正确使用的前提是理解任务
和队列
这两个概念。所以,iOS 面试中考察基本知识时,往往会涉及到这方面内容。下面就这两个概念,详细介绍。
1 任务
任务是 CPU 进行执行的基本单元,在 iOS 中可以简单地理解为一个函数实现。在GCD中可以认为任务等价于 block
。比如,下面的函数体 test 就是一个 task 。
-(void)test {
NSLog(@"task1");
}
2 队列
队列是任务的集合,强调的是一种静态表示。GCD 中队列分为 2 种: 顺序队列和并发队列。
2.1 顺序队列
如其名,顺序队列里面的任务有先后关系,符合先进先出的基本原则,只有排序在前面的任务执行完成,后面的任务才可以执行。比如下面的两个任务 task1
和 task2
在主线程顺序执行,输出结果如下:
/*
task1
task2
*/
- (void)test_queue_1 {
dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_SERIAL);
dispatch_sync(s1, ^{
NSLog(@"task1");
});
dispatch_sync(s1, ^{
NSLog(@"task2");
});
}
2.2 并发队列
并发队列里面的任务,没有先后关系,同时抛出,供线程进行调度。比如,下面在主线程,执行并发队列 c1 中任务 task1、task2。
/*
task1
task2
*/
- (void)test_queue_2 {
dispatch_queue_t c1 = dispatch_queue_create("concurrentOne", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(c1, ^{
NSLog(@"task1");
});
dispatch_sync(c1, ^{
NSLog(@"task2");
});
}
3 执行
执行是对任务的调度,强调动态。执行是通过线程调度来完成的。根据线程调度的方式不同,分为异步和同步。异步是先执行当前线程任务,新添加的任务延迟执行。同步是停止当前线程任务,立即执行新添加的任务。比如,下面在主线程异步执行主队列的任务 task2, 输出结果如下:
/*
task1
task3
task2
*/
-(void)test_asyn_1 {
NSLog(@"task1");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task2");
});
NSLog(@"task3");
}
关于执行,有以下 3 个特点:
3.1 主队列任务一定在主线程
/*
task1-<NSThread: 0x600002b72d40>{number = 1, name = main}
task2-<NSThread: 0x600002b72d40>{number = 1, name = main}
*/
-(void)test_asyn_2 {
NSLog(@"task1-%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task2-%@",[NSThread currentThread]);
});
}
3.2 非主队列任务
对顺序队列,同步时不开辟新线程;对顺序队列,异步时开辟新线程,如下所示:
/*
task1-<NSThread: 0x600003ff20c0>{number = 1, name = main}
task2-<NSThread: 0x600003ff20c0>{number = 1, name = main}
task3-<NSThread: 0x600003ff5200>{number = 3, name = (null)}
*/
- (void)test_asyn_3 {
dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_SERIAL);
NSLog(@"task1-%@",[NSThread currentThread]);
dispatch_sync(s1, ^{
NSLog(@"task2-%@",[NSThread currentThread]);
});
dispatch_async(s1, ^{
NSLog(@"task3-%@",[NSThread currentThread]);
});
}
对并发队列,同步时不开辟新线程;对并发队列,异步时开辟新线程,如下所示:
/*
task1-<NSThread: 0x60000068ed00>{number = 1, name = main}
task2-<NSThread: 0x6000006e6240>{number = 6, name = (null)}
task3-<NSThread: 0x6000006e6240>{number = 6, name = (null)}
*/
- (void)test_asyn_4 {
dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"task1-%@",[NSThread currentThread]);
dispatch_async(s1, ^{
NSLog(@"task2-%@",[NSThread currentThread]);
dispatch_sync(s1, ^{
NSLog(@"task3-%@",[NSThread currentThread]);
});
});
}
由上:非主队列,是否开辟新线程要看执行者:也就是同步还是异步。同步时,不会开辟新线程;异步时,为了方便任务的及时执行,会开辟新线程;
4 练习
明白了以上概念,下面做两个小练习:
4.1 练习一
-(void)test3_1 {
NSLog(@"task1-%@",[NSThread currentThread]);
dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_SERIAL);
dispatch_sync(s1, ^{
NSLog(@"task2-%@",[NSThread currentThread]);
});
NSLog(@"task3-%@",[NSThread currentThread]);
dispatch_async(s1, ^{
NSLog(@"task4-%@",[NSThread currentThread]);
});
NSLog(@"task5-%@",[NSThread currentThread]);
}
请从打印顺序和是否新开线程,两个角度去思考。思考后,请看
答案:
/*
task1-<NSThread: 0x600001c42140>{number = 1, name = main}
task2-<NSThread: 0x600001c42140>{number = 1, name = main}
task3-<NSThread: 0x600001c42140>{number = 1, name = main}
task5-<NSThread: 0x600001c42140>{number = 1, name = main}
task4-<NSThread: 0x600001c2e440>{number = 5, name = (null)}
*/
4.1 练习二
-(void)test3_2 {
NSLog(@"task1-%@",[NSThread currentThread]);
dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(s1, ^{
NSLog(@"task2-%@",[NSThread currentThread]);
});
NSLog(@"task3-%@",[NSThread currentThread]);
dispatch_async(s1, ^{
NSLog(@"task4-%@",[NSThread currentThread]);
});
NSLog(@"task5-%@",[NSThread currentThread]);
}
答案二:
/*
task1-<NSThread: 0x600001248c00>{number = 1, name = main}
task2-<NSThread: 0x600001248c00>{number = 1, name = main}
task3-<NSThread: 0x600001248c00>{number = 1, name = main}
task5-<NSThread: 0x600001248c00>{number = 1, name = main}
task4-<NSThread: 0x600001217d00>{number = 3, name = (null)}
*/
4.1 练习三
将 4.2 中的同步队列,改成并发队列,会有什么结果呢?
-(void)test3_3 {
NSLog(@"task1-%@",[NSThread currentThread]);
dispatch_queue_t s1 = dispatch_queue_create("serialOne", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(s1, ^{
NSLog(@"task2-%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task3-%@",[NSThread currentThread]);
});
});
NSLog(@"task4-%@",[NSThread currentThread]);
dispatch_async(s1, ^{
NSLog(@"task5-%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task6-%@",[NSThread currentThread]);
});
});
NSLog(@"task7-%@",[NSThread currentThread]);
}
答案三:
/*
task1-<NSThread: 0x600000145fc0>{number = 1, name = main}
task2-<NSThread: 0x600000145fc0>{number = 1, name = main}
task4-<NSThread: 0x600000145fc0>{number = 1, name = main}
task7-<NSThread: 0x600000145fc0>{number = 1, name = main}
task5-<NSThread: 0x60000012ea80>{number = 7, name = (null)}
task3-<NSThread: 0x600000145fc0>{number = 1, name = main}
task6-<NSThread: 0x600000145fc0>{number = 1, name = main}
*/
4.1 练习四
如果充分理解了以上 3 题,最后一个的题应该就很轻松了:
-(void)test3_4 {
NSLog(@"task1-%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task2-%@",[NSThread currentThread]);
});
NSLog(@"task3-%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"task4-%@",[NSThread currentThread]);
});
NSLog(@"task5-%@",[NSThread currentThread]);
}
答案四:
/*
task1-<NSThread: 0x6000027f6d80>{number = 1, name = main}
task3-<NSThread: 0x6000027f6d80>{number = 1, name = main}
task5-<NSThread: 0x6000027f6d80>{number = 1, name = main}
task2-<NSThread: 0x6000027f6d80>{number = 1, name = main}
task4-<NSThread: 0x6000027f6d80>{number = 1, name = main}
*/
以上是面试过程中遇到的基本知识题,口头表述一般都没问题。遇到像这种做题式的提问,还是需要对其执行有准确的认识,才可以快速得到结果。(完)
网友评论