GCD(Grand Central Dispatch)是异步执行任务的技术之一。是基于C语言
的框架,可以充分利用多核,是苹果推荐的多线程技术。(iOS4.0+才能使用)
GCD的API
-
Dispatch Queue
苹果官方对GCD的说明中有一句:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
源代码:
dispatch_async(queue, ^{
/**
* 想执行的任务
**/
});
在Block语法中定义想执行的任务,通过dispatch_async
函数追加赋值在变量queue
的Dispatch Queue
中,仅这样就可使指定的Block在另一个线程中执行。
Dispatch Queue
是执行处理的等待队列。按照追加的顺序(先进先出)执行处理。它又分为两种队列:
Serial Dispatch Queue:等待现在执行中处理结束,即同时只能执行一个追加处理(但当我们生成多个Serial Dispatch Queue
并追加处理时,各个Serial Dispatch Queue
将并行执行,然后一个Serial Dispatch Queue
生成一个线程,那么其有多少就能生成多少线程,这样一来就会消耗大量的内存,从而影响系统性能。)。可以用于产生数据竞争的地方。
Concurrent Dispatch Queue:不等待现在执行中处理结束,即可以并行执行多个追加处理。其不管生成多少,XNU
内核只使用有效管理的线程。
接下来通过代码来看一下区别:
a)、 Serial Dispatch Queue示例:使用dispatch_queue_create
创建线程时,设置DISPATCH_QUEUE_SERIAL
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
// 队列中执行异步任务
dispatch_async(queue, ^{
NSLog(@"任务一:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务二:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务三:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务四:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务五:%@",[NSThread currentThread]);
});
执行结果:
2019-04-19 14:07:30.065139+0800 StudyProject[78110:1751519] 任务一:<NSThread: 0x600002ccf240>{number = 3, name = (null)}
2019-04-19 14:07:30.065408+0800 StudyProject[78110:1751519] 任务二:<NSThread: 0x600002ccf240>{number = 3, name = (null)}
2019-04-19 14:07:30.065679+0800 StudyProject[78110:1751519] 任务三:<NSThread: 0x600002ccf240>{number = 3, name = (null)}
2019-04-19 14:07:30.065825+0800 StudyProject[78110:1751519] 任务四:<NSThread: 0x600002ccf240>{number = 3, name = (null)}
2019-04-19 14:07:30.065986+0800 StudyProject[78110:1751519] 任务五:<NSThread: 0x600002ccf240>{number = 3, name = (null)}
根据上述代码输出结果可以看到其是按顺序等待正在执行的处理结束后,再执行下一个的。
b)、 Concurrent Dispatch Queue示例:使用dispatch_queue_create
创建线程时,设置DISPATCH_QUEUE_CONCURRENT
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
// 队列中执行异步任务
dispatch_async(queue, ^{
NSLog(@"任务一:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务二:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务三:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务四:%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"任务五:%@",[NSThread currentThread]);
});
执行结果:
2019-04-19 14:22:01.140819+0800 StudyProject[79235:1760541] 任务四:<NSThread: 0x6000003d9380>{number = 6, name = (null)}
2019-04-19 14:22:01.140819+0800 StudyProject[79235:1760540] 任务二:<NSThread: 0x6000003246c0>{number = 5, name = (null)}
2019-04-19 14:22:01.140819+0800 StudyProject[79235:1760539] 任务三:<NSThread: 0x6000003244c0>{number = 4, name = (null)}
2019-04-19 14:22:01.140865+0800 StudyProject[79235:1760536] 任务一:<NSThread: 0x6000003dc200>{number = 3, name = (null)}
2019-04-19 14:22:01.141013+0800 StudyProject[79235:1760541] 任务五:<NSThread: 0x6000003d9380>{number = 6, name = (null)}
修改代码后,可以看到输出结果不再是按照顺序输出了,其每次运行输出的结果都不一样。
这里需要注意一点,虽然
Concurrent Dispatch Queue
可以并行执行多个处理,但其处理的数量要取决于当前系统的状态(iOS和OS X基于Dispatch Queue中的处理数、CPU核数以及CPU负荷等)。由XNU
内核决定应当使用的线程数,并只生成所需的线程执行处理。当处理结束,应当执行的处理数减少时,XNU
内核会结束不再需要的线程。(XNU
内核为iOS和OS X的核心)
-
dispatch_queue_create
dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)
第一个参数为"队列名称",第二个参数可以按照你的需求设置DISPATCH_QUEUE_SERIAL
或者DISPATCH_QUEUE_CONCURRENT
,如果写NULL
则默认为DISPATCH_QUEUE_SERIAL
。返回值为dispatch_queue_t
。
-
Main Dispatch Queue/Global Dispatch Queue
系统给我们提供了两个获取queue
的方法
a)、Main Dispatch Queue:获取主线程,为Serial Dispatch Queue
dispatch_queue_t queue = dispatch_get_main_queue();
回到主线程,可以更新UI等
b)、Global Dispatch Queue:获取全局队列,为Concurrent Dispatch Queue
//DISPATCH_QUEUE_PRIORITY_DEFAULT为默认优先级
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
Global Dispatch Queue中有四个执行优先级:
DISPATCH_QUEUE_PRIORITY_HIGH
:高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT
:默认优先级
DISPATCH_QUEUE_PRIORITY_LOW
:低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND
:后台优先级
看一个示例:(示例1-1
)
//获取全局队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 异步async执行,并发执行
for (UIImageView *imageView in self.imageViewSet) {
dispatch_async(queue, ^{
// 处理图片
NSLog(@"GCD--%@",[NSThread currentThread]);
NSInteger num = arc4random_uniform(17) + 1;
NSString *imageName = [NSString stringWithFormat:@"NatGeo%02ld",(long)num];
// 回到主线程设置UI
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = [UIImage imageNamed:imageName];
});
});
}
-
dispatch_sync/dispatch_async
dispatch_sync:同步,等待指定的处理执行结束再执行
dispatch_async:异步,不做任何等待即可执行
示例一:
NSLog(@"1---%@",[NSThread currentThread]);
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2----%@",[NSThread currentThread]);
});
输出结果:
2019-04-19 15:52:14.213630+0800 StudyProject[86257:1817894] 1---<NSThread: 0x600001d9e940>{number = 1, name = main}
2019-04-19 15:52:14.213992+0800 StudyProject[86257:1817894] 2----<NSThread: 0x600001d9e940>{number = 1, name = main}
如示例一在主线程中,使用dispatch_async
执行dispatch_get_main_queue()
处理,不等待dispatch_get_main_queue
执行完毕就可以执行自己的Block,所以运行正常。
示例二:
NSLog(@"1---%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"2----%@",[NSThread currentThread]);
});
输出结果:
2019-04-19 16:03:34.533595+0800 StudyProject[87043:1824144] 1---<NSThread: 0x600000786900>{number = 1, name = main}
然后崩溃,崩溃原因见下图
如示例二,将示例一的dispatch_async
修改为dispatch_sync
,即异步修改为同步,运行结果如下图:
可见,输出1以后,程序崩溃。这是因为我们使用的
dispatch_sync
为同步,需要等待dispatch_get_main_queue
即主线程执行完毕后才能执行Block的内容,但是dispatch_sync
又是在主线程中执行的,主线程需要等待dispatch_sync
执行完毕,所以就造成了死锁。所以在项目使用中,我们需要注意这个问题,想好我们要实现的效果,再去决定用什么,比如上面的
示例1-1
,我们使用dispatch_sync
就是希望等图片处理完成后,再回到主线程更新UI。
-
Dispatch Group
队列组:我们想要等多个处理都执行完毕后再结束处理。
示例:需要根据多个url异步加载多张图片,然后全部下载完成后,将其合成一整张图,这样的需求我们就可以创建一个队列组,具体代码如下:
/**
* 全局队列
* 全局调度任务是有系统执行的,开发时不用考虑并发线程数量问题
**/
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**
* 创建一个组
**/
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
//下载图片1
});
dispatch_group_async(group, queue, ^{
//下载图片2
});
dispatch_group_async(group, queue, ^{
//下载图片3
});
dispatch_group_async(group, queue, ^{
//下载图片4
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
//合成图片
//更新UI
});
-
dispatch_once
保证在应用程序执行中只执行一次指定处理的API。可用于单例。
示例:
static SingleInstance *_instance;
+(SingleInstance *)shareInstance{
static dispatch_once_t _onceToken;
dispatch_once(&_onceToken, ^{
_instance = [[SingleInstance alloc] init];
});
return _instance;
}
-
dispatch_after
延迟函数:想在指定时间后执行处理。
示例:执行示例后可以发现在输出结果1
后,并没有立刻输出延迟5秒
,而是等待五秒后才输出。
NSLog(@"1");
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC);
dispatch_after(time, queue, ^{
NSLog(@"延迟5秒");
});
需要注意的一点,
dispatch_after
函数并不是说在指定时间后执行处理,而是在指定时间后追加处理到Dispatch Queue
中。其次呢,这个延迟时间并不是非常精准的,只是一个大致的延迟执行处理。(原因是Main Dispatch Queue
在主线程的Runloop
中执行,而Runloop
比如说每隔1/60
秒执行一次,那么Block最快在5秒后执行,最慢在5+1/60
秒后执行,并且在Main Dispatch Queue
中可能有大量处理追加或者主线程处理本身有延迟时,时间会更长)
-
Dispatch Semaphore
信号量:持有计数的信号。当计数为0时等待,计数为1或者大于1时,执行减1。并从dispatch_semaphore_wait
函数返回。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_create(1)
这个1
是指计数的初始值。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait
等待信号量。第二个参数为等待时间。当其返回值为0
时,函数会一直等待,即阻塞当前线程,直到其等待的信号量的值大于或等于1
。当其判断出信号量的值大于或等于1
时,会对信号量执行-1
。
dispatch_semaphore_signal(semaphore);
dispatch_semaphore_signal
发送信号量,可以对信号量的值进行+1
示例:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
// 计数初始值设为1
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i = 0; i < 10000; i++) {
dispatch_async(queue, ^{
// 等待计数值信号大于等于1
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
// 计数值达到大于等于1,所以计数值-1,直到dispatch_semaphore_wait执行返回0。访问对象线程
[array addObject:[NSNumber numberWithInt:i]];
// 处理结束,将计数+1
dispatch_semaphore_signal(semaphore);
});
}
-
dispatch_barrier_async
栅栏函数:等待dispatch_barrier_async
之前的处理执行完毕,再执行它之后的处理。用于避免数据竞争。可用于数据库访问或者文件访问。
示例:
dispatch_barrier_async(queue, ^{
NSLog(@"我是栅栏函数");
});
GCD的串行、并行、同步、异步各种组合
参考:
书籍:Objective-C高级编程(iOS与OS X多线程和内存管理)