浅谈多线程——GCD
在实际开发过程中,我们有时需要使用到多线程进行处理数据解析、加载或者是后台执行的任务。因为在主线程中,如果一个任务无法处理或耗时过长,可能引发主界面卡死,造成阻塞。在Objective-C中,我们可以使用GCD处理多线程问题。
——————————————————————————————
在进行多线程编程之前,需要了解一些概念:
进程:进程是指一个正在运行中的程序,尤其只有一个进程。
线程:线程指进程中的正在执行的任务,可以理解为一个任务队列,子程序,一个进程中有且至少一个线程(主线程)。
阻塞:指一个正在执行的任务未能完成或耗时过长,抢占资源,导致后续任务无法处理;在实现登录操作中,需要使用阻塞处理;但在数据解析、加载或后台任务处理时,我们需要避免阻塞。
——————————————————————————————
GCD,是苹果为开发者提供的一类简单处理多线程的方法,用以替代NSThread等多线程方法。使用GCD需要了解与多线程相关的一些GCD方法,诸如同步、并行异步,串行异步等,我们可以通过下图初步了解这些方法的大致实现:
★同步:使用同步方法,则会将子任务都放入主线程队列中;并遵循FIFO原则,等待上一任务完成后在执行下一任务;当然也会对主线程形成阻塞。
★串行异步:使用串行异步方法,则会开辟出一条子线程,而子任务都处于该子线程队列中;遵循FIFO原则,等待上一任务完成后再执行下一任务;而主线程任务的执行不受子线程干扰。
★并行异步:使用并行异步方法,则会根据子任务的数量开辟出多条子线程并发;各个子线程之间没有优先顺序;子线程任务不会干扰主线程任务的执行。
现在,通过图示大致了解了这三类GCD方法,我们可以通过代码进行验证:
1 #import "ViewController.h" 2 3 typedef NS_ENUM(NSInteger, EThreadType) { 4 kAsync, // 异步 5 kSync, // 同步 6 }; 7 8 @interface ViewController () 9 { 10 dispatch_queue_t sQueue; // 串行队列 11 dispatch_queue_t cQueue; // 并行队列 12 dispatch_queue_t mQueue; // 主线程队列 13 dispatch_queue_t gQueue; // 全局队列 14 } 15 @end 16 17 @implementation ViewController 18 19 - (void)viewDidLoad { 20 [super viewDidLoad]; 21 22 sQueue = dispatch_queue_create("串行", DISPATCH_QUEUE_SERIAL); 23 cQueue = dispatch_queue_create("并行", DISPATCH_QUEUE_CONCURRENT); 24 mQueue = dispatch_get_main_queue(); 25 gQueue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0); 26 27 NSLog(@"开始"); 28 29 [self threadWithType:kAsync mainType:kSync queue:gQueue doTimes:5]; 30 31 NSLog(@"结束"); 32 } 33 34 - (void)threadWithType:(EThreadType)type mainType:(EThreadType)mainType queue:(dispatch_queue_t)queue doTimes:(int)time{ 35 for(int i = 0; i < time; i++){ 36 switch (type) { 37 case kAsync:{ 38 dispatch_async(queue, ^{ 39 if([queue isEqual:sQueue]) 40 NSLog(@"异步 串行 + %d:%@", i, [NSThread currentThread]); 41 if([queue isEqual:cQueue]) 42 NSLog(@"异步 并行 + %d:%@", i, [NSThread currentThread]); 43 if([queue isEqual:gQueue]) 44 NSLog(@"异步 全局队列 + %d:%@", i, [NSThread currentThread]); 45 46 [self mainThreadWithType:mainType]; 47 sleep(1); 48 }); 49 break; 50 } 51 case kSync:{ 52 dispatch_sync(queue, ^{ 53 if([queue isEqual:sQueue]) 54 NSLog(@"同步 串行 + %d:%@", i, [NSThread currentThread]); 55 if([queue isEqual:cQueue]) 56 NSLog(@"同步 并行 + %d:%@", i, [NSThread currentThread]); 57 if([queue isEqual:gQueue]) 58 NSLog(@"同步 全局队列 + %d:%@", i, [NSThread currentThread]); 59 60 [self mainThreadWithType:mainType]; 61 sleep(1); 62 }); 63 break; 64 } 65 default: 66 break; 67 } 68 } 69 } 70 71 - (void)mainThreadWithType:(EThreadType)type{ 72 switch (type) { 73 case kAsync: 74 dispatch_async(dispatch_get_main_queue(), ^{ 75 NSLog(@"Async测试 %@", [NSThread currentThread]); 76 }); 77 break; 78 case kSync: 79 dispatch_sync(dispatch_get_main_queue(), ^{ 80 NSLog(@"Sync测试 %@", [NSThread currentThread]); 81 }); 82 break; 83 default: 84 break; 85 } 86 } 87 88 @end
我们所需要验证的是代码中的方法:- (void)threadWithType:(EThreadType)type mainType:(EThreadType)mainType queue:(dispatch_queue_t)queue doTimes:(int)time
type是GCD方法的同步异步参数;mainType是CGD返回主线程方法的同步异步参数;queue则是使用的队列类型参数;time则是使用GCD方法开辟线程的次数。这里,我们主要设置type和queue:
// ①并行异步
[self threadWithType:kAsync mainType:kSync queue:cQueue doTimes:10];
// ②全局队列异步 [self threadWithType:kAsync mainType:kSync queue:gQueue doTimes:10];
并行异步和全局队列异步使用方法类似(全局队列不需要命名,可直接使用),都是产生多个子线程并发,各线程无优先顺序,不干扰主线程任务。
// ③串行异步 [self threadWithType:kAsync mainType:kSync queue:sQueue doTimes:10];
串行异步的模式就如同上面的图示一样,GCD方法开辟了一个子线程2,将全部子任务放在了子线程2的队列中,遵循FIFO原则,但不干扰主线程的任务。
// ④串行同步 [self threadWithType:kSync mainType:kSync queue:sQueue doTimes:10]; // ⑤并行同步 [self threadWithType:kSync mainType:kSync queue:cQueue doTimes:10]; // ⑥全局队列同步 [self threadWithType:kSync mainType:kSync queue:gQueue doTimes:10];
因为是同步,所以你所创建的任务无论是并行队列或串行队列还是全局队列都处于主线程队列中,故子任务都遵循FIFO原则,并对主线程任务阻塞。
现在,已经大致了解了GCD中与线程相关的三种模式,可以简单地使用表格归纳一下:
多线程模式 | 优先顺序 | 是否阻塞 | |
串行异步 | 开辟一个子线程 | 无 | 不阻塞 |
并行异步 | 多线程并发 | FIFO原则 | 不阻塞 |
同步 | 主线程队列 | FIFO原则 | 阻塞 |
GCD中的其他其他常用方法:
a.主线程队列方法:常用与子线程数据加载完成,返回主线程进行页面刷新
dispatch_sync(dispatch_get_main_queue(), ^{ // 代码 });
b.线程唯一方法:保证线程内方法只执行一次,常用于线程安全的单例模式
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 代码 });
c.执行延迟操作:使队列中的任务在不能立即执行,需要延迟执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#(double)秒数#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 代码 });