GCD用法
GCD
Dispatch Queue介绍
苹果官方对GCD的说明:开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
dispatch_async(queue, ^{
//要执行的任务
});
多线程编程定义
1个CPU执行的CPU命令列为一条无分叉的路径,即为”线程“。目前,基本上一个CPU核一次只能执行一个CPU命令,那么怎样才能在多条路径中执行CPU命令呢?
OS X和iOS的核心XNU内核在发生操作系统事件时会切换执行路径。执行中路径的状态,例如CPU的寄存器等信息保存到保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原CPU寄存器等信息,继续执行切换路径的CPU命令列。这被称为“上下文切换”。
由于使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去就好像1个CPU核能够并列执行多个线程一样。在多个CPU核的情况下就真的事提供了多个CPU核并行执行多个线程的技术。
这种使用多线程编程的技术被称为多“多线程编程”。
=
但是多线程编程实际上是一种已发生各种问题的编程技术。比如多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互等待(死锁)、使用太多线程会消耗大量内存等。
=
要回避这些问题有很多办法,但程序都偏于复杂。
尽管极易发生各种问题,也应该使用多线程编程。这是因为多线程编程可以保证应用程序响应性能。
1 GCD的API
1.1 dispatch_queue_create
dispatch_queue_create函数可以生成Dispatch Queue
//生成Serial Dispatch
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.myway.gcd.serial", NULL);
//生成Concurrent Dispatch
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.myway.gcd.Concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_create使用注意事项
- 虽然Serial Queue和Concurrent Dispatch Queue受到系统资源的限制,但dispatch_queue_create可生成任意多个Dispatch Queue。
- 生成多个Serial Dispatch Queue时,多个Serial Dispatch Queue将并行执行。一旦生成Serial Dispatch Queue并追加处理,系统就对于一个Serial Dispatch Queue就生成并使用一个线程。如果过多使用多线程,就会消耗大量内存,引起大量上下文切换,大幅度降低系统的响应性能。
- 只在为了避免多线程编程问题之一 ----- 多个线程更新相同资源导致数据竞争时使用Serial Dispatch Queue。但是Serial Dispatch Queue的生成数量应当仅限所必须的数量,虽然Serial Dispatch Queue比Concurrent能生成更多的线程,但绝不能激动之下生成大量的Serial Queue。当想并行执行不发生数据竞争等问题的处理时,使用Concurrent Dispatch Queue。而且对于Concurrent Dispatch Queue来说,不管生成多少,由于XNU内核只使用有效管理的线程,因此不会发生Serial Dispatch Queue的那些问题。
1.2 Main Dispatch Queue 和 Global Dispatch Queue
- Main Dispatch Queue 是串行队列。 通过dispatch_get_main_queue()获取
- Global Dispatch Queue 是并行队列。 通过dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)获取
Global Dispatch Queue有4个优先级,用于Global Dispatch Queue管理的线程,将各自使用Global Dispatch Queue的优先级作为线程的优先级使用。
但是Global Dispatch Queue的线程并不能保证实时性,因此执行优先级只是大致判断。
Main Dispatch Queue 和 Global Dispatch Queue执行dispatch_retain和dispatch_release并不会引起任何变化,也不会引起任何问题。
1.3 dispatch_set_target_queue
dispatch_queue_create生成的队列都是默认优先级的Global Dispatch Queue,可以用dispatch_set_target_queue变更优先级。Main Dispatch Queue 和 Global Dispatch Queue不能随意变更优先级,会引起不可预知的错误。
dispatch_set_target_queue不仅可以变更优先级,还可以设置Dispatch Queue的执行阶层。
1.4 dispatch_after
//ull 是C语言的数值字面量,是显示使用类型时使用的字符串(表示“unsigned long long”)。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"等待至少3秒后执行");
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"等待至少3秒后执行");
});
注意:dispatch_after不是在指定时间后执行处理,而是指定时间后追加处理到Dispatch Queue。
这段源代码是3秒后追加Block到Main Dispatch Queue。
因为Main Dispatch Queue在主线程的RunLoop中执行,所以在比如每隔1/60秒执行的RunLoop中,Block最快在3秒后执行最慢在3秒+1/60秒后执行,如果Main Dispatch Queue有大量处理追加或主线程本身延迟,这个时间会更长。
dispatch_time_t类型的值可以用 dispatch_time 或 dispatch_walltime函数作成。
//通过dispatch_walltime用来计算绝对时间
dispatch_time_t getDispatchTimeByDate(NSDate *date){
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
dispatch_time_t milestone;
interval = [date timeIntervalSince1970];
subsecond = modf(interval, &second);
time.tv_sec = second;
time.tv_nsec = subsecond * NSEC_PER_SEC;
milestone = dispatch_walltime(&time, 0);
return milestone;
}
1.5 dispatch_barrier_async
dispatch_barrier_async(queue, ^{
});
1.6 Dispatch Group
追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况经常出现。
如果是Serial Dispatch Queue只要将想执行的处理全部追加到Serial Dispatch Queue中并在最后追加结束处理,即可实现。但是使用Concurrent Dispatch Queue时或同时使用多个Dispatch Queue时代码就会变得复杂,这种情况下就要用到Dispatch Group。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
for (NSInteger index = 0; index<10; index++) {
__weak typeof(self) weakSelf = self;
dispatch_group_async(group, queue, ^{
[weakSelf setStringValue:[NSString stringWithFormat:@"blk%ld",(long)index]];
});
}
// dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// NSLog(@"done");
// });
long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
if (result == 0) {
//属于group的全部处理执行结束
NSLog(@"属于group的全部处理执行结束");
}else{
//属于group的处理未全部执行结束
NSLog(@"属于group的处理未全部执行结束");
}
dispatch_group_enter(_dispatchGroup);
dispatch_group_leave(_dispatchGroup);
dispatch_group_notify(_dispatchGroup, dispatch_get_main_queue(), ^{
});
1.7 dispatch_apply
dispatch_apply按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理执行结束。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");//done必定在最后的位置输出,因为dispatch_apply会等待全部处理执行结束
因为dispatch_apply会等待所有处理结束,所以推荐在dispatch_async中非同步执行dispatch_apply
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu",index);
});
NSLog(@"done");//done必定在最后的位置输出,因为dispatch_apply会等待全部处理执行结束
dispatch_async(dispatch_get_main_queue(), ^{
//main queue 处理UI等;
});
});
1.8 dispatch_suspend/dispatch_resume
```
dispatch_suspend(queue);
dispatch_resume(queue);
```
1.9 dispatch_semaphore_t
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore);
GCD总结
线程是魔鬼,应谨慎使用多线程,合理使用多线程可以保证程序运行流畅。如果不合理多线程,可能会引起很多问题,如:大量使用多线程可能会很快用尽线程栈内存。
Dispatch Queues有以下优点: 他们提供直接而简单的编程接口
- 他们提供自动和完全的线程池管理
- 他们提供协作组件的速度。
- 他们更加内存高效(因为线程栈不在应用的内存中,而在内核的内存中?)
- 他们不会导致内核负载过高
- 他们使用dispatch queue异步处理任务,不会让queue死锁
- 他们在竞争中保持平和。
- 串行dispatch queue提供了一个比锁和其他同步机制更加高效的选择
你提交给dispatch queue的任务必须封装在一个函数或者一个block 对象中。block对象是在ios4中的类似于函数指针但有有额外优点的c语言特性。与其定义blocks在他们自己的词法作用域,你一般定义blocks在他们能访问的一个函数或者方法内部。blocks可以移出他们原始的范围然后复制到堆上,这就是当你将他们提交到dispatch queue上发生的。所有这些语义使得使得少量代码就可以实现非常动态的任务成为可能。
Dispatch queues是GCD技术的一部分和C运行时的一部分。
Dispatch Sources
Dispatch Sources是基于C的机制处理指定类型的系统异步事件。一个dispatch source封装包括特定类型的系统事件的信息然后提交给一个指定的block对象或者函数给dispatch queue当事件发生时。你可以使用dispatch sources来监视以下系统事件
定时器(Timers)
信号处理(Signal handlers)
描述符相关的事件(Descriptor-related events)
进程相关事件(Process-related events)
端口事件(Mach port events)
你触发的用户事件(Custom events that you trigger)
Dispache sources是GCD技术的一部分。