Dispatch Queue
首先回顾一下苹果GCD的说明
开发者要做的只是定义想执行的任务并追加到适当的Dispatch Queue中。
这句话的用源代码表示如下:
dispatch_asyn(queue, ^{
//想要执行的任务
});
该源代码使用block语法定义想要执行的任务,通过dispatch_async函数追加赋值变量queue的“Dispatch Queue”中,仅这样就可以使指定的block在另一线程中执行。
Dispatch Queue 是什么,是执行处理的等待队列,应用程序编程人员通过dispatch_any等函数,在block中记述想要执行等处理并将其追加到Dispatch Queue中,Dispatch Queue按照追加的顺序执行处理。
两种Dispatch Queue
Dispatch Queue种类 | 说明 |
---|---|
Serial Dispatch Queue | 等待现在执行中处理结束 |
Concurrent Dispatch Queue | 不等待现在执行中处理 |
Serial Dispatch Queue: 假设把A B C D四个任务依次加入到一个Serial Dispatch Queue中,这个Serial Dispatch Queue会创建一个线程,依次执行这四个任务。
Concurrent Dispatch Queue: 假设把A B C D四个任务依次加入到一个Concurrent Dispatch Queue中,这个Concurrent Dispatch Queue会创建多个线程,并发执行这四个任务。
具体执行效果参看这个Demo
dispatch_queue_create
通过dispatch_queue_create函数可生成Dispatch Queue。
创建Serail Queue:
dispatch_queue_t mySerailQueue = dispatch_queue_create("com.river.dispatchSerailQueue", NULL)
;
第一个参数是指定Serial dispatch queue的名称。该名称在Xcode和Instruments的调试器中作为Dispatch Queue名称表示。
第二个参数是生成Dispatch Queue的类型,如果是NULL生成Serial dispatch queue,指定为DISPATCH_QUEUE_CONCURRENT生成Concurrent Dispatch Queue。
创建Concurrent Queue:
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.river.dispatchConcurrentQueue", DISPATCH_QUEUE_CONCURRENT)
;
dispatch_queue_create函数的返回值为表示Dispatch Queue的“dispatch_queue_t”类型。
注意:Serial Dispatch Queue只能同时执行一个追加处理。使用dispatch_queue_create可以生成多个dispatch queue。当生成多个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然一个Serial Dispatch Queue同时只能执行一个追加处理,但是如果将处理(任务)追加到4个Serial Dispatch Queue,各个Serial Dispatch Queue执行1个,即为同时执行4个处理。但是,要警惕的是,如果生成太多的Serial Dispatch Queue会消耗大量内存。为了避免多线程造成的数据竞争,可以使用Serial Dispatch Queue。
ARC下dispatch_release使用
在iOS6之前GCD未加入到ARC模式下,所以如果应用程序要支持iOS6.0之前对版本,则需要使用dispatch_release。可以做如下处理。
//当前系统支持的最小版本
#if __IPHONE_OS_VERSION_MIN_REQUIRED >= 60000
#define NEEDS_DISPATCH_RETAIN_RELEASE 0
#else // iOS 5.X or earlier
#define NEEDS_DISPATCH_RETAIN_RELEASE 1
#endif
/* implementation */
#if NEEDS_DISPATCH_RETAIN_RELEASE
dispatch_release(self.reachabilitySerialQueue);
#endif
Main Dispatch Queue/Global Dispatch Queue
获取系统标准提供的Dispatch Queue。
Main Dispatch Queue是在主线程执行的dispatch queue, Main Dispatch Queue是一个Serail Dispatch Queue。追加到Main Dispatch Queue的处理在主线程的RunLoop中执行。一般将用户界面更新等必需要在主线程中执行的处理追加到Main Dispatch Queue中。
Global Dispatch Queue是所有应用程序都能过使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_create函数逐个创建Concurrent Dispatch Queue,只要获取Global Dispatch Queue使用即可。Global Dispatch Queue有四个优先级
名称 | Dispatch Queue的种类 | 说明 |
---|---|---|
Main Dispatch Queue | Serial Dispatch Queue | 主线程执行 |
Global Dispatch Queue(High Priority) | Concurrent Dispatch queue | 执行优先级:高(最高) |
Global Dispatch Queue(Default Priority) | Concurrent Dispatch queue | 执行优先级:默认 |
Global Dispatch Queue(Low Priority) | Concurrent Dispatch queue | 执行优先级:低 |
Global Dispatch Queue(Background Priority) | Concurrent Dispatch queue | 执行优先级:后台 |
获取Dispatch Queue方法
ispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
使用Main Dispatch Queue 和 Global Dispatch Queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/**
* 可并行处理的任务TODO
*/
dispatch_async(dispatch_get_main_queue(), ^{
/**
* 主线程执行
*/
});
});
dispatch_set_target_queue
dispatch_queue_create函数生成的Dispatch_Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。而变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数。
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.river.myserialdispatchqueue", NULL);
dispatch_queue_t globalDispatchQueueBackGround = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueueBackGround);
第一个参数:要变更执行优先级的Dispatch Queue
第二个参数:要使用的执行优先级相同的Global Dispatch Queue。
如果指定第一个参数为Main Dispatch Queue 和 Global Dispatch Queue则不知道会发生什么情况。
如果在多个Serial Dispatch Queue 中将一个Serial Dispatch Queue设置为第二个参数,那么原本应该并行执行的Serial Dispatch Queue,在目标Serial Dispatch Queue上只能串行执行。可参考Demo
dispatch_after
dispatch_after表示在指定的时间之后追加处理到Dispatch Queue。并不是指定时间后执行处理。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
NSLog(@"等待3秒后执行");
});
虽然在有严格时间到要求下使用时会出现问题,但在大致延迟执行处理时,该函数还是有效的。
dispatch_time函数通常用于计算相对时间,而dispatch_walltime函数用于计算绝对时间。
NSDate转成dispatch_time_t
- (dispatch_time_t) getDispatchDateByDate:(NSDate *)date{
NSTimeInterval interval;
double second, subsecond;
struct timespec time;
dispatch_time_t milestone;
//以当前时间(Now)为基准时间,返回实例保存的时间与当前时间(Now)的时间间隔,秒
interval = [date timeIntervalSince1970];
//拆分interval值,返回它的小数部分,second指向整数部分
subsecond = modf(interval, &second);
time.tv_sec = second; //秒
time.tv_nsec = subsecond *NSEC_PER_SEC; //纳秒 1秒=1000000000纳秒
milestone = dispatch_walltime(&time, 0);
return milestone;
}
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();
dispatch_group_async(group, queue, ^{
for (int i=0; i<2000; i++) {
NSLog(@"111111");
}
});
dispatch_group_async(group, queue, ^{
for (int i=0; i<2000; i++) {
NSLog(@"2222222");
}
});
dispatch_group_async(group, queue, ^{
for (int i=0; i<2000; i++) {
NSLog(@"33333");
}
});
//最后执行4444
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"44444");
});
NSLog(@"555555");
另外,在Dispatch Group中也可以使用dispatch_group_wait
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, ^{
for (int i=0; i<2000; i++) {
NSLog(@"111111");
}
});
dispatch_group_async(group, queue, ^{
for (int i=0; i<2000; i++) {
NSLog(@"2222222");
}
});
dispatch_group_async(group, queue, ^{
for (int i=0; i<2000; i++) {
NSLog(@"33333");
}
});
//DISPATCH_TIME_FOREVER永久等待
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"44444");
dispatch_group_wait函数的第二个参数指定为等待的时间,它属于dispatch_time_t类型的值。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
long result = dispatch_group_wait(group, time);
if (result == 0) {
/**
*属于dispatch group的任务全部执行完毕
*/
}else {
/**
*属于dispatch group的任务没有全部执行完毕
*/
}
dispatch_group_wait等待的意思:一旦调用dispatch_group_wait,该函数就处于调用状态而不返回,执行dispatch_group_wait函数的线程停止。知道dispatch_group_wait返回结果。
long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
注意推荐使用dispatch_group_notify;
dispatch_barrier_async
dispatch_barrier_async函数会等待追加到Concurrent Dispatch Queue上对并行执行的处理全部结束之后,再将指定的处理追加到该Concurrent Dispatch Queue中,然后在由dispatch_barrier_async函数追加的处理执行完毕之后,Concurrent Dispatch Queue才会恢复一般动作。
dispatch_async(queue, ^{
//去文件
});
dispatch_async(queue, ^{
//去文件
});
dispatch_async(queue, ^{
//去文件
});
//使用dispatch_barrier_async避免数据竞争
dispatch_barrier_async(queue, ^{
//写文件
});
dispatch_async(queue, ^{
//去文件
});
dispatch_async(queue, ^{
//去文件
});
dispatch_async(queue, ^{
//去文件
});
dispatch_barrier_async执行过程
dispatch_sync
dispatch_async函数中的async意味着非同步。就是将指定的Block非同步地追加到指定的Dispatch Queue中,dispatch_async函数不做任何等待。
dispatch_sync函数意味着同步,也就是将指定的Block同步追加到Dispatch Queue中,再追加结束之前dispatch_sync函数会一直等待。一旦调用dispatch_sync函数,那么指定的处理执行结束之前,该函数不会返回。
dispatch_sync函数容易引起问题,死锁
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
NSLog(@"11111");
});
该段代码在主线程中执行指定的Block,并等待其执行结束。而其实主线程正在执行此段代码,所以无法执行追加到Mian Dispatch Queue中的Block。
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"11111");
});
});
这段代码同样会引起错误。同样下面代码也会引起错误
dispatch_queue_t queue = dispatch_queue_create("com.river.queue", NULL);
dispatch_async(queue, ^{
dispatch_sync(queue, ^{
NSLog(@"11111");
});
})
大家使用dispatch_sync时候要好好考虑。
dispatch_apply
dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的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(@"11");
对数组的快速处理:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply([array count], queue, ^(size_t index) {
NSLog(@"%zu:%@", index, [array objectAtIndex:index]);
});
推荐dispatch_async函数非同步地执行dispatch_apply函数
dispatch_async(queue, ^{
dispatch_apply([array count], queue, ^(size_t index) {
/**
* 并行执行
*/
NSLog(@"%zu:%@", index, [array objectAtIndex:index]);
});
/**
* 等待dispatch_apply全部处理完
*/
dispatch_async(dispatch_get_main_queue(), ^{
/**
* 在main dispatch queue中执行,用户界面更新等
*/
NSLog(@"done");
});
});
dispatch_suspend/dispatch_resume
当追加大量处理到Dispatch Queue时,在追加处理到过程中,有时希望不执行已追加的处理。在这种情况下只要挂起Dispatch Queue即可。
dispatch_suspend 函数挂起指定的Dispatch Queue
dispatch_resume 函数恢复指定的Dispatch Queue
这些函数对已经执行的处理没有影响,挂起后,追加到Dispatch Queue中处理在此之后暂停执行,而恢复使得这些处理继续执行。
Dispatch Semaphore
当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可避免这类问题,但有必要进行更细粒度的排他控制。
Dispatch Semaphore 是持有计数的信号,该计数是多线程编程中的计数类型信号,所谓信号,类似于过马路时常用的手旗。可以通过时举起手旗,而在Dispatch Semaphore 中,使用计数来实现该功能。计数为0时等待,计数为1或大于1时,减去1而不等待。
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
参数表示计数的初始值。
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_semaphore_wait函数等待Dispatch Semaphore 的计数值达到大于或等于1。当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull*NSEC_PER_SEC);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
long result = dispatch_semaphore_wait(semaphore, time);
if (result == 0) {
/**
* 由于Dispatch Semaphore 的计数值达到大于等于1
或者待机中指定时间内,Dispatch Semaphore 的计数值
达到大于等于1
所以 Dispatch Semaphore 的计数值减去1
可执行需要进行排他控制的处理
*/
} else {
/**
* 由于Dispatch Semaphore 的计数值为 0
因此在达到指定时间为待机。
*/
}
dispatch_semaphore_wait函数返回0时,可安全地执行需要进行排他控制的处理。该处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**
* 生成Dispatch Semaphore
* Dispatch Semaphore 的计数初始值设定为1
* 保证可访问NSMutableArray类对象的线程同时只有一个
*/
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [[NSMutableArray alloc] init];
for (int i=0; i<10000; i++) {
dispatch_async(queue, ^{
/**
* 等待Dispatch Semaphore
一直等待,直到Dispatch Semaphore 的计数的值达到大于等于1
*/
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
/**
* 由于Dispatch Semaphore 的计数达到大于等于1
所以Dispatch Semaphore 的计数值减去1
dispatch_semaphore_wait函数执行返回
即执行到此时Dispatch Semaphore的计数值恒为00
由于可访问NSMutableArray类对象的线程数只有1个
因此可安全的进行更新
*/
[array addObject:[NSNumber numberWithInt:i]];
/**
* 排他控制处理结束
所以通过dispatch_semaphore_signal函数
将DispatchSemaphore的计数值加1
如果有通过dispatch_semaphore_wait函数
等待Dispatch Semaphore的计数值增加的线程
就由最先等待的线程执行。
*/
dispatch_semaphore_signal(semaphore);
});
}
dispatch_once
dispatch_once 函数保证在应用程序执行中只执行一次指定处理的API。下面这种经常出现的用来初始化的源代码可通过dispatch_oce函数简化
static int initialized = NO;
if(initialized == NO){
initialized = YES;
}
//使用dispatch_once
static disptach_once_t pred;
dispatch_once(&pred, ^{
//初始化
});
通过dispatch_once函数,该代码再多线程环境下也是百分之百安全。
Dispatch I/O
在读取大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取速度快不少。实现这一功能可使用Dispatch I/O和Dispatch Data。
通过dispatch I/O读写文件时,使用Global Dispatch Queue将一个文件按某个大笑read/write.
dispatch_async(queue, ^{
/**
* 读取 0~1001字节
*/
});
dispatch_async(queue, ^{
/**
* 读取 1002~2001字节
*/
});
dispatch_async(queue, ^{
/**
* 读取 2002~3001字节
*/
});
dispatch_async(queue, ^{
/**
* 读取 3002~4001字节
*/
});
demo
pipe_q = dispatch_queue_create("PipeQ", NULL);
pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){
close(fd);
});
*out_fd = fdpair[1];
dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){
if (err == 0)
{
size_t len = dispatch_data_get_size(pipedata);
if (len > 0)
{
const char *bytes = NULL;
char *encoded;
uint32_t eval;
dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len);
encoded = asl_core_encode_buffer(bytes, len);
asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded);
free(encoded);
eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1);
_asl_send_message(NULL, eval, aux, NULL);
asl_msg_release(aux);
dispatch_release(md);
}
}
if (done)
{
dispatch_semaphore_signal(sem);
dispatch_release(pipe_channel);
dispatch_release(pipe_q);
}
});