iOS基础 - GCD:同步 | 异步 | 串行 | 并发
▶ 前言
GCD 全称 Grand Central Dispatch,是纯 C 编写,提供了多且强大的函数。在编写 GCD 相关代码的时我们要记住面对的是函数,而不是方法!GCD 存在于 libdispatch.dylib 这个库中,这个调度库包含了 GCD 的所有的东西,任何 iOS 程序默认就加载了这个库
注:GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程),程序员只需要告诉 GCD 想要执行什么任务,不需要编写任何线程管理代码
▶ 队列
有两种任务派发方式
// 同步执行:完成了它预定的任务后才返回,阻塞当前线程 dispatch_sync(dispatch_queue_t queue, dispatch_block_t block); // 异步执行:会立即返回,预定的任务会完成但不会等它完成,不阻塞当前线程 dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
A. 有两种队列:Serial Dispatch Queue 和 Concurrent Dispatch Queue !遵循 FIFO 原则:先进先出,后进后出
串行队列:一次只执行一个任务,它通常用于同步访问特定的资源或数据。当你创建多个串行队列时它们各自内部是同步进行的,但队列和队列之间是并发执行的。获得串行有两种途径
// 方式一:使用 dispatch_queue_create dispatch_queue_create(const char *label, dispatch_queue_attr_t attr); // 方式二:使用主队列,它是跟主线程相关联的队列 dispatch_get_main_queue();
B. 并发队列:可以并发的执行多个任务,并发功能只有在异步函数下才有效。GCD 提供了全局并发队列用来供整个应用使用,不需要手动创建
// 全局队列 dispatch_queue_t queue = dispatch_get_global_queue(dispatch_queue_priority_t priority,unsigned long flags); // 全局并发队列的优先级 #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 // 后台
主队列 | 全局队列 | 自定义队列
- (void)viewDidLoad { [super viewDidLoad]; // 主队列: 是一个单例,意味着在不同地方获取均为同一个 // 它的执行方式是串行,其任务都是运行在主线程 dispatch_queue_t mainQueue = dispatch_get_main_queue(); NSLog(@"%@",mainQueue); // 全局队列: 并发执行,任务都是运行在子线程 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); NSLog(@"%@",globalQueue); // 自定义队列:运行在子线程,任务执行可以指定是串行还是并发,默认串行 // DISPATCH_QUEUE_CONCURRENT // DISPATCH_QUEUE_SERIAL dispatch_queue_t customQueue = dispatch_queue_create("Hello GCD", DISPATCH_QUEUE_CONCURRENT); NSLog(@"%@",customQueue); }
▶ 异步函数 | 同步函数
这里我们使用异步函数,往并发队列中添加任务,先来感受下实际效果
1 -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ 2 3 // BlockA 4 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 5 6 NSLog(@"异步+并发 = %@",[NSThread currentThread]); 7 8 // 测试一:不会阻塞UI 9 // [self performSelectorInBackground:@selector(crazyTextA) withObject:nil]; // 在新线程中执行 10 // [self performSelectorOnMainThread:@selector(updateUI) withObject:self waitUntilDone:YES]; 11 12 //----------------------- 13 14 // 测试二:会阻塞更新UI 15 [self crazyTextA]; // 要等待 crazyTextA 执行完后才能更新UI 16 [self performSelectorOnMainThread:@selector(updateUI) withObject:self waitUntilDone:YES]; 17 }); 18 19 // 不须等待 BlockA 中的代码执行完毕 20 NSLog(@"hello Block %@",[NSThread currentThread]); 21 22 // BlockB:同样不须等待 BlockA 中的代码执行完毕 23 dispatch_async(dispatch_get_global_queue(0, 0), ^{ 24 [self crazyTextB]; 25 }); 26 } 27 28 29 // 耗时操作A 30 -(void)crazyTextA{ 31 for (int i = 10; i > 0; i--) { 32 [NSThread sleepForTimeInterval:0.5]; 33 NSString *str = [NSString stringWithFormat:@"i = %d",i]; 34 NSLog(@"BlockA---%@", str); 35 } 36 } 37 38 // 耗时操作B 39 -(void)crazyTextB{ 40 for (int i = 20; i > 10; i--) { 41 [NSThread sleepForTimeInterval:0.5]; 42 NSString *str = [NSString stringWithFormat:@"i = %d",i]; 43 NSLog(@"BlockB---%@", str); 44 } 45 } 46 47 // 更新UI 48 -(void)updateUI{ 49 NSLog(@"更新UI"); 50 self.view.backgroundColor = [UIColor cyanColor]; 51 }
日志信息
接下来,我们进行详细测试,进一步了解其工作原理
测试一:使用异步函数,往并发队列中添加任务
- (void)viewDidLoad { [super viewDidLoad]; dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); // 添加任务到队列中,就可以执行任务 dispatch_async(globalQueue, ^{ NSLog(@"异步+并发1----%@",[NSThread currentThread]); // 11 }); dispatch_async(globalQueue, ^{ NSLog(@"异步+并发2----%@",[NSThread currentThread]); // 12 }); dispatch_async(globalQueue, ^{ NSLog(@"异步+并发3----%@",[NSThread currentThread]); // 9 }); NSLog(@"主线程----%@",[NSThread mainThread]); }
日志信息:会开辟三个新线程
测试二:使用异步函数,往串行队列中添加任务
- (void)viewDidLoad { [super viewDidLoad]; // 串行队列 dispatch_queue_t serialQueue = dispatch_queue_create("Hi GCD_A", DISPATCH_QUEUE_SERIAL); // 添加任务到队列中,就可以执行任务 dispatch_async(serialQueue, ^{ NSLog(@"异步+串行1----%@",[NSThread currentThread]); }); dispatch_async(serialQueue, ^{ NSLog(@"异步+串行2----%@",[NSThread currentThread]); }); dispatch_async(serialQueue, ^{ NSLog(@"异步+串行3----%@",[NSThread currentThread]); }); NSLog(@"主线程----%@",[NSThread mainThread]); }
日志信息:会开辟一个且只会开辟一个新线程
测试三:使用同步函数,往并发队列中添加任务
- (void)viewDidLoad { [super viewDidLoad]; // 全局队列 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0); dispatch_sync(globalQueue, ^{ NSLog(@"同步+并发1----%@",[NSThread currentThread]); }); dispatch_sync(globalQueue, ^{ NSLog(@"同步+并发2----%@",[NSThread currentThread]); }); dispatch_sync(globalQueue, ^{ NSLog(@"同步+并发3----%@",[NSThread currentThread]); }); NSLog(@"主线程----%@",[NSThread mainThread]); // 依次执行: 同步+并发1、同步+并发2、同步+并发3、主线程 }
日志信息:不会开辟新线程,均在当前线程(主线程)中执行,且并发队列失去了并发功能
测试四:使用同步函数,往串行队列中添加任务
- (void)viewDidLoad { [super viewDidLoad]; // 串行队列 dispatch_queue_t serialQueue = dispatch_queue_create("Hi GCD", DISPATCH_QUEUE_SERIAL); dispatch_sync(serialQueue, ^{ NSLog(@"同步+串行1----%@",[NSThread currentThread]); // 1 }); dispatch_sync(serialQueue, ^{ NSLog(@"同步+串行2----%@",[NSThread currentThread]); // 1 }); dispatch_sync(serialQueue, ^{ NSLog(@"同步+串行3----%@",[NSThread currentThread]); // 1 }); NSLog(@"主线程----%@",[NSThread mainThread]); }
日志信息:不会开辟新线程,同样是在当前线程中执行
▶ 结语
同步函数不具备开辟线程的能力,无论是什么队列都不会开辟线程
异步函数具备开辟线程的能力,并且开辟几条线程由什么样的队列决定:串行队列只会开辟一条新的线程,并发队列会开辟多条线程
主队列是和主线程相关联的队列,是 GCD 自带的一种特殊的串行队列:放在主队列中的任务都会在主线程中执行
A. 使用异步函数,往主队列中添加任务
- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"异步+主队列1--%@",[NSThread currentThread]); }); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"异步+主队列2--%@",[NSThread currentThread]); }); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"异步+主队列3--%@",[NSThread currentThread]); }); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"异步+主队列4--%@",[NSThread currentThread]); }); NSLog(@"主线程%@", [NSThread mainThread]); }
日志信息:不会开辟新线程
B. 使用同步函数,往主队列中添加任务,会产生死锁问题 crash
- (void)viewDidLoad{ [super viewDidLoad]; NSLog(@"主线程--%@", [NSThread mainThread]); // 主队列 dispatch_queue_t queue = dispatch_get_main_queue(); dispatch_sync(queue, ^{ NSLog(@"任务1--%@",[NSThread currentThread]);// crash }); }
C. 如图所示