关于GCD开发的一些事儿

作者:新葡京简介

图片 1在之前我们介绍过NSOperation的一些东西,这次我们来聊一聊另一个iOS开发最经常使用的技术之一

GCD,GCD将线程的管理移到系统级别,你只需要定义好要执行的任务,然后丢到合适的Dispatch queue,GCD会负责创建线程来执行你的代码,由于这部分是处于系统级别,所以执行的性能通常非常高。GCD这部分代码苹果已开源,有兴趣的可以去下载了解一下:地址在介绍GCD之前我们先了解一下Quality of Service:

文中较详细介绍GCD队列,各种GCD使用方法,实例如何使用Dispatch Source监听系统底层对象,分析不同锁的性能对比,实例GCD死锁情况。文中的Demo在这里 对着文章试着来调demo体会更深哦,细细嚼消化好:)

GCD是苹果为开发者提供的系统级别的线程管理api,开发过程中专门对多核心性能做了优化,所以在dispatch queue中执行任务性能很高。

1. 什么是GCD?

GCD 是 libdispatch 的市场名称,而 libdispatch 作为 Apple 的一个库,为并发代码在多核硬件(跑 iOS 或 OS X )上执行提供有力支持。它具有以下优点:

GCD 能通过推迟昂贵计算任务并在后台运行它们来改善你的应用的响应性能。
GCD 提供一个易于使用的并发模型而不仅仅只是锁和线程,以帮助我们避开并发陷阱。
GCD 具有在常见模式(例如单例)上用更高性能的原语优化你的代码的潜在能力。

Quality of Service

这是在iOS8之后提供的新功能,苹果提供了几个Quality of Service枚举来使用:user interactive, user initiated, utility 和 background,通过这告诉系统我们在进行什么样的工作,然后系统会通过合理的资源控制来最高效的执行任务代码,其中主要涉及到CPU调度的优先级、IO优先级、任务运行在哪个线程以及运行的顺序等等,我们通过一个抽象的Quality of Service参数来表明任务的意图以及类别。

  • NSQualityOfServiceUserInteractive与用户交互的任务,这些任务通常跟UI级别的刷新相关,比如动画,这些任务需要在一瞬间完成
  • NSQualityOfServiceUserInitiated由用户发起的并且需要立即得到结果的任务,比如滑动scroll view时去加载数据用于后续cell的显示,这些任务通常跟后续的用户交互相关,在几秒或者更短的时间内完成
  • NSQualityOfServiceUtility一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载的任务,这些任务可能花费几秒或者几分钟的时间
  • NSQualityOfServiceBackground这些任务对用户不可见,比如后台进行备份的操作,这些任务可能需要较长的时间,几分钟甚至几个小时
  • NSQualityOfServiceDefault优先级介于user-initiated 和 utility,当没有 QoS信息时默认使用,开发者不应该使用这个值来设置自己的任务

Qos可以跟GCD queue做个对照:

图片 2对照表

下面我们了解一下GCD的一些用法:

GCD属于系统级的线程管理,在Dispatch queue中执行需要执行的任务性能非常的高。GCD这块已经开源,地址 queue,用来保证先进来的任务先得到执行。

GCD概要

  • 和operation queue一样都是基于队列的并发编程API,底层使用的是线程池的技术,集中管理大家协同使用的线程池。
  • 系统提供五个不同的全局队列
    • main queue 主线程队列,也是UI更新的线程
    • 四个不同优先级的队列(High Priority Queue,Default Priority Queue,Low Priority Queue),以及一个优先级更低的后台队列Background Priority Queue
    • 可创建自定义队列:串行或并列队列。自定义队列一般放在Default Priority Queue和Main Queue里。
    • 操作是在多线程上还是单线程主要是看队列的类型和执行方法,并行队列异步执行才能在多线程,并行队列同步执行就只会在主线程执行了

2. 并行与并发

简单来说,若说两个任务A和B并发执行,则表示任务A和任务B在同一时间段里被执行(当计算机CPU只有一个核心在运行时,二者是交替执行的,即切换上下文,CPU在两个线程之间高速切换,让人感觉这两个任务是“同时”进行的);若说任务A和B并行执行,则表示任务A和任务B在同时被执行(这要求计算机CPU具有多核运算能力,即这两个任务分别在不同的核心上同时运行);
一句话:并行要求并发,但并发并不能保证并行。

Dispatch Queue

开发者将需要执行的任务添加到合适的Dispatch Queue中即可,Dispatch Queue会根据任务添加的顺序先到先执行,其中有以下几种队列:

  • main dispatch queue功能跟主线程一样,通过dispatch_get_main_queue()来获取,提交到main queue的任务实际上都是在主线程执行的,所以这是一个串行队列
  • global dispatch queues系统给每个应用提供四个全局的并发队列,这四个队列分别有不同的优先级:高、默认、低以及后台,用户不能去创建全局队列,只能根据优先级去获取:
dispatch_queue_t queue ; queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
  • user create queue用户可以通过dispatch_queue_create自己创建队列,该函数有两个参数,第一个是队列的名称,在debug的时候方便区分;第二个是队列的一些属性,NULL或者DISPATCH_QUEUE_SERIAL创建出来的队列是串行队列,如果传递DISPATCH_QUEUE_CONCURRENT则为并行队列。
//创建并行队列dispatch_queue_t queue;queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_CONCURRENT);
  • 队列优先级

dispatch_queue_create创建队列的优先级跟global dispatch queue的默认优先级一样,假如我们需要设置队列的优先级,可以通过dispatch_queue_attr_make_with_qos_class或者dispatch_set_target_queue方法;

//指定队列的QoS类别为QOS_CLASS_UTILITYdispatch_queue_attr_t queue_attr = dispatch_queue_attr_make_with_qos_class (DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY,-1);dispatch_queue_t queue = dispatch_queue_create("queue", queue_attr);

dispatch_set_target_queue的第一个参数为要设置优先级的queue,第二个参数是对应的优先级参照物

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.MyQueue",NULL); dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0); //serialQueue现在的优先级跟globalQueue的优先级一样dispatch_set_target_queue(serialQueue, globalQueue); 
  • dispatch_set_target_queuedispatch_set_target_queue除了能用来设置队列的优先级之外,还能够创建队列的层次体系,当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的target指向新创建的队列即可,比如

图片 3队列体系.png

 dispatch_queue_t targetQueue = dispatch_queue_create("target_queue", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_CONCURRENT); dispatch_set_target_queue(queue1, targetQueue); dispatch_set_target_queue(queue2, targetQueue); dispatch_async(queue1, ^{ NSLog(@"do job1"); [NSThread sleepForTimeInterval:3.f]; }); dispatch_async(queue2, ^{ NSLog(@"do job2"); [NSThread sleepForTimeInterval:2.f]; }); dispatch_async(queue2, ^{ NSLog(@"do job3"); [NSThread sleepForTimeInterval:1.f]; });

可以看到执行的结果如下,这些队列会同步的执行任务。

 GCDTests[13323:569147] do job1 GCDTests[13323:569147] do job2 GCDTests[13323:569147] do job3
  • dispatch_barrier_asyncdispatch_barrier_async用于等待前面的任务执行完毕后自己才执行,而它后面的任务需等待它完成之后才执行。一个典型的例子就是数据的读写,通常为了防止文件读写导致冲突,我们会创建一个串行的队列,所有的文件操作都是通过这个队列来执行,比如FMDB,这样就可以避免读写冲突。不过其实这样效率是有提升的空间的,当没有更新数据时,读操作其实是可以并行进行的,而写操作需要串行的执行,如何实现呢:
dispatch_queue_t queue = dispatch_queue_create("Database_Queue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"reading data1"); }); dispatch_async(queue, ^{ NSLog(@"reading data2"); }); dispatch_barrier_async(queue, ^{ NSLog(@"writing data1"); [NSThread sleepForTimeInterval:1]; }); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:1]; NSLog(@"reading data3"); });

执行结果如下:

GCDTests[13360:584316] reading data2GCDTests[13360:584317] reading data1GCDTests[13360:584317] writing data1GCDTests[13360:584317] reading data3

我们将写数据的操作放在dispatch_barrier_async中,这样能确保在写数据的时候会等待前面的读操作完成,而后续的读操作也会等到写操作完成后才能继续执行,提高文件读写的执行效率。

  • dispatch_queue_set_specific 、dispatch_get_specific

这两个API类似于objc_setAssociatedObject跟objc_getAssociatedObject,FMDB里就用到这个来防止死锁,来看看FMDB的部分源码

static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;//创建一个串行队列来执行数据库的所有操作 _queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL); //通过key标示队列,设置context为self dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

当要执行数据库操作时,如果在queue里面的block执行过程中,又调用了 indatabase方法,需要检查是不是同一个queue,因为同一个queue的话会产生死锁情况

- inDatabase:(FMDatabase *db))block { FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");}
  • dispatch_applydispatch_apply类似一个for循环,会在指定的dispatch queue中运行block任务n次,如果队列是并发队列,则会并发执行block任务,dispatch_apply是一个同步调用,block任务执行n次后才返回。简单的使用方法:
dispatch_queue_t queue = dispatch_queue_create("myqueue", DISPATCH_QUEUE_CONCURRENT);//并发的运行一个block任务5次dispatch_apply(5, queue, ^ { NSLog(@"do a job %zu times",i 1);});NSLog;

输出结果:

GCDTests[10029:760640] do a job 2 timesGCDTests[10029:760640] do a job 1 timesGCDTests[10029:760640] do a job 3 timesGCDTests[10029:760640] do a job 5 timesGCDTests[10029:760640] do a job 4 timesGCDTests[10029:760640] go on

在某些场景下使用dispatch_apply会对性能有很大的提升,比如你的代码需要以每个像素为基准来处理计算image图片。同时dispatch apply能够避免一些线程爆炸的情况发生

//危险,可能导致线程爆炸以及死锁for (int i = 0; i < 999; i  ){ dispatch_async(q, ^{...});}dispatch_barrier_sync;// 较优选择, GCD 会管理并发dispatch_apply(999, q, ^{...});

添加到gcd队列中执行的任务是以block的形式添加的,block封装了需要执行功能,block带来的开发效率提升就不说了,gcd跟block可以说是一对好基友,能够很好的配合使用。

  • 创建block我们可以自己创建block并添加到queue中去执行
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);//创建blockdispatch_block_t block = dispatch_block_create(0, ^{ NSLog(@"do something"); });dispatch_async(queue, block);

在创建block的时候我们也可以通过设置QoS,指定block对应的优先级,在dispatch_block_create_with_qos_class中指定QoS类别即可:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);dispatch_block_t block = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{ NSLog(@"do something with QoS"); });dispatch_async(queue, block);
  • dispatch_block_wait当需要等待前面的任务执行完毕时,我们可以使用dispatch_block_wait这个接口,设置等待时间DISPATCH_TIME_FOREVER会一直等待直到前面的任务完成:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);dispatch_block_t block = dispatch_block_create(0, ^{ NSLog(@"before sleep"); [NSThread sleepForTimeInterval:1]; NSLog(@"after sleep");});dispatch_async(queue, block);//等待前面的任务执行完毕dispatch_block_wait(block, DISPATCH_TIME_FOREVER);NSLog(@"coutinue");

程序运行结果:

GCDTests[16679:863641] before sleepGCDTests[16679:863641] after sleepGCDTests[16679:863529] coutinue
  • dispatch_block_notifydispatch_block_notify当观察的某个block执行结束之后立刻通知提交另一特定的block到指定的queue中执行,该函数有三个参数,第一参数是需要观察的block,第二个参数是被通知block提交执行的queue,第三参数是当需要被通知执行的block,函数的原型:
void dispatch_block_notify(dispatch_block_t block, dispatch_queue_t queue, dispatch_block_t notification_block);

具体使用的方法:

 dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL); dispatch_block_t previousBlock = dispatch_block_create(0, ^{ NSLog(@"previousBlock begin"); [NSThread sleepForTimeInterval:1]; NSLog(@"previousBlock done"); }); dispatch_async(queue, previousBlock); dispatch_block_t notifyBlock = dispatch_block_create(0, ^{ NSLog(@"notifyBlock"); }); //当previousBlock执行完毕后,提交notifyBlock到global queue中执行 dispatch_block_notify(previousBlock, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), notifyBlock);

运行结果:

GCDTests[17129:895673] previousBlock beginGCDTests[17129:895673] previousBlock doneGCDTests[17129:895673] notifyBlock
  • dispatch_block_cancel之前在介绍nsopreration的时候提到它的一个优点是可以取消某个operation,现在在iOS8之后,提交到gcd队列中的dispatch block也可取消了,只需要简单的调用dispatch_block_cancel传入想要取消的block即可:
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_SERIAL);dispatch_block_t block1 = dispatch_block_create(0, ^{ NSLog(@"block1 begin"); [NSThread sleepForTimeInterval:1]; NSLog(@"block1 done");});dispatch_block_t block2 = dispatch_block_create(0, ^{ NSLog(@"block2 ");});dispatch_async(queue, block1);dispatch_async(queue, block2);dispatch_block_cancel;

可以看到如下的执行结果,block2不再执行了。

GCDTests[17271:902981] block1 beginGCDTests[17271:902981] block1 done

当我们想在gcd queue中所有的任务执行完毕之后做些特定事情的时候,也就是队列的同步问题,如果队列是串行的话,那将该操作最后添加到队列中即可,但如果队列是并行队列的话,这时候就可以利用dispatch_group来实现了,dispatch_group能很方便的解决同步的问题。dispatch_group_create可以创建一个group对象,然后可以添加block到该组里面,下面看下它的一些用法:

  • dispatch_group_waitdispatch_group_wait会同步地等待group中所有的block执行完毕后才继续执行,类似于dispatch barrier
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);dispatch_group_t group = dispatch_group_create();//将任务异步地添加到group中去执行dispatch_group_async(group,queue,^{ NSLog(@"block1"); });dispatch_group_async(group,queue,^{ NSLog(@"block2"); });dispatch_group_wait(group,DISPATCH_TIME_FOREVER);NSLog;

执行结果如下,只有block1跟block2执行完毕后才会执行dispatch_group_wait后面的内容。

GCDTests[954:41031] block2GCDTests[954:41032] block1GCDTests[954:40847] go on
  • dispatch_group_notify功能与dispatch_group_wait类似,不过该过程是异步的,不会阻塞该线程,dispatch_group_notify有三个参数
void dispatch_group_notify(dispatch_group_t group, //要观察的group dispatch_queue_t queue, //block执行的队列 dispatch_block_t block); //当group中所有任务执行完毕之后要执行的block

简单的示意用法:

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);dispatch_group_t group = dispatch_group_create();dispatch_group_async(group,queue,^{ NSLog(@"block1"); });dispatch_group_async(group,queue,^{ NSLog(@"block2"); });dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog;});NSLog;

可以看到如下的执行结果

GCDTests[1046:45104] go onGCDTests[1046:45153] block1GCDTests[1046:45152] block2GCDTests[1046:45104] done
  • dispatch_group_enter dispatch_group_leave假如我们不想使用dispatch_group_async异步的将任务丢到group中去执行,这时候就需要用到dispatch_group_enter跟dispatch_group_leave方法,这两个方法要配对出现,以下这两种方法是等价的:
dispatch_group_async(group, queue, ^{ }); 

等价于

dispatch_group_enter;dispatch_async(queue, ^{ dispatch_group_leave;

简单的使用方法,可以自己试试没有写dispatch_group_leave会发生什么。

dispatch_group_t group = dispatch_group_create();for (int i =0 ; i<3; i  ) { dispatch_group_enter; NSLog(@"do block:%d",i); dispatch_group_leave;}//等待上面的任务完成dispatch_group_wait(group, DISPATCH_TIME_FOREVER);NSLog;

dispatch semaphore也是用来做解决一些同步的问题,dispatch_semaphore_create会创建一个信号量,该函数需要传递一个信号值,dispatch_semaphore_signal会使信号值加1,如果信号值的大小等于1,dispatch_semaphore_wait会使信号值减1,并继续往下走,如果信号值为0,则等待。

//创建一个信号量,初始值为0dispatch_semaphore_t sema = dispatch_semaphore_create;dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"do some job"); sleep; NSLog(@"increase the semaphore"); dispatch_semaphore_signal; //信号值加1});dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);//等待直到信号值大于等1NSLog;

执行结果如下:

GCDTests[1394:92383] do some jobGCDTests[1394:92383] increase the semaphoreGCDTests[1394:92326] go on

dispatch timer通常配合dispatch_after使用,完成一些延时的任务:

//延迟5秒后执行任务dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (5 * NSEC_PER_SEC));dispatch_after(time, dispatch_get_main_queue(), ^{ NSLog(@"do job afer 5 seconds");});

当我们要读取一份较大文件的时候,多个线程同时去读肯定比一个线程去读的速度要快,要实现这样的功能可以通过dispatch io跟dispatch data来实现,通过dispatch io去读文件时,会使用global dispatch queue将一个文件按照一个指定的分块大小同时去读取数据,类似于:

dispatch_async(queue, ^{/* 读取0-99字节 */});dispatch_async(queue, ^{/* 读取100-199字节 */});dispatch_async(queue, ^{/* 读取200-299字节 */});...

将文件分成一块一块并行的去读取,读取的数据通过Dispatch Data可以更为简单地进行结合和分割 。

  • dispatch_io_create生成Dispatch IO,指定发生错误时用来执行处理的Block,以及执行该Block的Dispatch Queue
  • dispatch_io_set_low_water设定一次读取的大小
  • dispatch_io_read使用Global Dispatch Queue开始并列读取,当每个分割的文件块读取完毕时,会将含有文件数据的dispatch data返回到dispatch_io_read设定的block,在block中需要分析传递过来的dispatch data进行合并处理

可以看下苹果的系统日志API(Libc-763.11 gen/asl.c)的源代码使用到了dispatch IO:源码地址

//dispatch_io_create出错时handler执行的队列pipe_q = dispatch_queue_create("PipeQ", NULL);pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^{ //出错时执行的handler close;*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  return; if  { //每次读取到数据进行数据的处理 size_t len = dispatch_data_get_size; 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; eval = _asl_evaluate_send(NULL, aux, -1); _asl_send_message(NULL, eval, aux, NULL); asl_msg_release; dispatch_release; } } if  { //并发读取完毕 dispatch_semaphore_signal; dispatch_release(pipe_channel); dispatch_release; }});

假如你的数据文件比较大,可以考虑采用dispatch IO的方式来提高读取的速率。

dispatch框架提供一套接口用于监听系统底层对象(如文件描述符、Mach端口、信号量等),当这些对象有事件产生时会自动把事件的处理block函数提交到dispatch队列中执行,这套接口就是Dispatch Source API,Dispatch Source其实就是对kqueue功能的封装,可以去查看dispatch_source的c源码实现(什么是kqueue?Google,什么是Mach端口? Google Again),Dispatch Source主要处理以下几种事件:

DISPATCH_SOURCE_TYPE_DATA_ADD 变量增加DISPATCH_SOURCE_TYPE_DATA_OR 变量ORDISPATCH_SOURCE_TYPE_MACH_SEND Mach端口发送DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口接收DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存压力情况变化DISPATCH_SOURCE_TYPE_PROC 与进程相关的事件DISPATCH_SOURCE_TYPE_READ 可读取文件映像DISPATCH_SOURCE_TYPE_SIGNAL 接收信号DISPATCH_SOURCE_TYPE_TIMER 定时器事件DISPATCH_SOURCE_TYPE_VNODE 文件系统变更DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像

当有事件发生时,dispatch source自动将一个block放入一个dispatch queue执行。

  • dispatch_source_create创建一个dispatch source,需要指定事件源的类型,handler的执行队列,dispatch source创建完之后将处于挂起状态。此时dispatch source会接收事件,但是不会进行处理,你需要设置事件处理的handler,并执行额外的配置;同时为了防止事件堆积到dispatch queue中,dispatch source还会对事件进行合并,如果新事件在上一个事件处理handler执行之前到达,dispatch source会根据事件的类型替换或者合并新旧事件。

  • dispatch_source_set_event_handler给指定的dispatch source设置事件发生的处理handler

  • dispatch_source_set_cancel_handler给指定的dispatch source设置一个取消处理handler,取消处理handler会在dispatch soruce释放之前做些清理工作,比如关闭文件描述符:

dispatch_source_set_cancel_handler(mySource, ^{ close; //关闭文件秒速符 }); 
  • dispatch_source_cancel异步地关闭dispatch source,这样后续的事件发生时不去调用对应的事件处理handler,但已经在执行的handler不会被取消。

很多第三方库会用到dispatch source的功能,比如著名的IM框架XMPPFramework在涉及到定时器的时候都采用这种方法,比如发送心跳包的时候(setupKeepAliveTimer)。一个简单的例子:

//如果dispatch source是本地变量,会被释放掉,需要这么声明@property (nonatomic)dispatch_source_t timerSource;//事件handler的处理队列dispatch_queue_t queue = dispatch_queue_create("myqueue", NULL);//_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);//定时器间隔时间uint64_t interval = 2 * NSEC_PER_SEC;//设置定时器信息dispatch_source_set_timer(_timerSource,DISPATCH_TIME_NOW, interval , 0);//设置事件的处理handlerdispatch_source_set_event_handler(_timerSource, ^{ NSLog(@"receive time event"); //if  // dispatch_source_cancel(_timerSource); });//开始处理定时器事件,dispatch_suspend暂停处理事件dispatch_resume(_timerSource);

定时器还可以通过NSTimer实现,不过NSTimer会跟runloop关联在一起,主线层默认有一个runloop,假如你nstimer是运行在子线程,就需要自己手动开启一个runloop,而且nstimer默认是在NSDefaultRunLoopMode模式下的,所以当runloop切换到其它模式nstimer就不会运行,需要手动将nstimer添加到NSRunLoopCommonModes模式下;而dispatch source timer不跟runloop关联,所以有些场景可以使用这种方法。

本文总结了GCD的一些用法,不过有些API可能iOS8之后才可以用,如有还有什么可以补充的,欢迎提出~

  • dispatch源码地址:
  • Building Responsive and Efficient Apps with GCD
  • Grand Central Dispatch Reference
  • Objective-C高级编程:iOS与OS X多线程和内存管理
  • 和operation queue一样都是基于队列的并发编程API,他们通过集中管理大家协同使用的线程池。
  • 公开的5个不同队列:运行在主线程中的main queue,3个不同优先级的后台队列(High Priority Queue,Default Priority Queue,Low Priority Queue),以及一个优先级更低的后台队列Background Priority Queue
  • 可创建自定义队列:串行或并列队列。自定义一般放在Default Priority Queue和Main Queue里。
  • 操作是在多线程上还是单线程主要是看队列的类型和执行方法,并行队列异步执行才能在多线程,并行队列同步执行就只会在主线程执行了

基本概念

  • 系统提供的两个标准队列
//全局队列,是一个并行队列,可以设置不同的优先级dispatch_get_globle_queue//主队列,主线程中的唯一队列,是一个串行队列dispatch_get_main_queue
  • 自定义队列
//串行队列dispatch_queue_create("com.fineway.serialqueue",DISPATCH_QUEUE_SERIAL);//并行队列dispatch_queue_create("com.fineway.coucurrent",DISPATCH_QUEUE_CONCURRENT);
  • 同步异步线程的创建
//同步线程dispatch_sync(..., ^//异步线程dispatch_async(..., ^
  • Serial:又叫private dispatch queues,同时只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。
  • Main dispatch queue:全局可用的serial queue,在应用程序主线程上执行任务。
  • Concurrent:又叫global dispatch queue,可以并发的执行多个任务,但执行完成顺序是随机的。系统提供四个全局并发队列,这四个队列有着对应的优先级,用户是不能够创建全局队列的,只能获取。
dispatch_queue_t queue;queue = dispatch_get_globle_queue( DISPATCH_QUEUE_PEIORITY_HIGH,0);
  • user create queue:创建自己定义的队列,可以用dispatch_queue_create函数,函数有两个参数,第一个自定义的队列名,第二个参数是队列类型,默认NULL或者DISPATCH_QUEUE_SERIAL的是串行,参数为DISPATCH_QUEUE_CONCURRENT为并行队列。
dispatch_queue_t queue ;queue = dispatch_queue_create("com.fineway.concurrent",DISPATCH_QUEUEU_CONCURRENT);
  • 自定义队列的优先级:可以通过dipatch_queue_attr_make_with_qos_class或dispatch_set_target_queue方法设置队列的优先级
//dipatch_queue_attr_make_with_qos_classdispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);dispatch_queue_t queue = dispatch_queue_create("com.fineway.serialqueue", attr);//dispatch_set_target_queuedispatch_queue_t queue = dispatch_queue_create("com.fineway.serialqueue",NULL); //需要设置优先级的queuedispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级dispatch_set_target_queue(queue, referQueue); //设置queue和referQueue的优先级一样
  • dispatch_set_target_queue:可以设置优先级,也可以设置队列层级体系,比如让多个串行和并行队列在统一一个串行队列里串行执行,如下
dispatch_queue_t serialQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_SERIAL);dispatch_queue_t firstQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_SERIAL);dispatch_queue_t secondQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_CONCURRENT);dispatch_set_target_queue(firstQueue, serialQueue);dispatch_set_target_queue(secondQueue, serialQueue); dispatch_async(firstQueue, ^{ NSLog; [NSThread sleepForTimeInterval:3.f];});dispatch_async(secondQueue, ^{ NSLog; [NSThread sleepForTimeInterval:2.f];});dispatch_async(secondQueue, ^{ NSLog; [NSThread sleepForTimeInterval:1.f];});

3. Dispatch Queues

Dispatch Queue是一个任务执行队列,可以让你异步或同步地执行多个Block或函数。Dispatch Queue是FIFO的,即先入队的任务总会先执行。目前有三种类型的Dispath Queue:

  • 串行队列(Serial dispatch queue)
  • 并行队列(Concurrent dispatch queue)
  • 主队列(Main dispatch queue)

队列类型

队列类型默认是串行的,如果创建队列的时候参数是NULL,队列是串行的,队列参数是DISPATCH_QUEUE_CONCURRENT时,队列是并行的,可以同时执行多个block。

  • 何时使用何种队列类型
    • 主队列:队列中有任务完成需要更新UI时,dispatch_after在这种类型中使用。
    • 并发队列:用来执行与UI无关的后台任务,dispatch_sync放在这里,方便等待任务完成进行后续处理或和dispatch barrier同步。dispatch groups放在这里也不错。
    • 自定义顺序队列:顺序执行后台任务并追踪它时。这样做同时只有一个任务在执行可以防止资源竞争。dipatch barriers解决读写锁问题的放在这里处理。dispatch groups也是放在这里。
3.1 串行队列(Serial dispatch queue)

serial dispatch queue中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。即每次从queue中取出一个task进行处理;用户可以根据需要创建任意多的serial dispatch queue,serial dispatch queue彼此之间是并发的,创建一个串行队列如下:

dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MySerialQueue", DISPATCH_QUEUE_SERIAL);
  • 系统标准两个队列

关于dispatch_once

使用dispatch_once创建单例时,要保证dispatch_once_t是全局或者static变量,保证只是创建一次。

  (AppDataSource *)shareAppDataSource{ static AppDataSource *shareAppDataSource = nil; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ shareAppDataSource = [[self alloc] init]; }); return shareAppDataSource;}
3.2 并行队列(Concurrent dispatch queue)

相对于Serial Dispatch Queue,Concurrent Dispatch Queue一次性并发执行一个或者多个task;和Serial Dispatch Queue不同,系统提供了四个global concurrent queue,使用dispatch_get_global_queue函数就可以获取这些global concurrent queue;
和Serial Dispatch Queue一样,用户也可以根据需要自己定义concurrent queue;创建concurrent dispatch queue也使用dispatch_queue_create方法,所不同的是需要指定其第二个参数为DISPATCH_QUEUE_CONCURRENT即可:

 dispatch_queue_t queue;
 queue = dispatch_queue_create("com.example.MyConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

关于dispatch_async

异步执行api,将任务切换到工作线程中执行,避免阻塞主线程。比如读取网络数据,大数据IO,还有大量数据的数据库读写,这时需要在另一个线程中处理,然后通知主线程更新界面,GCD使用起来比NSThread和NSOperation方法要简单方便。

//代码框架,异步执行,完成后主线程更新UIdispatch_async(dispatch_get_globle_queue(), ^{ // do actual processing here dispatch_async(dispatch_get_main_queue(), ^{ handler; });

下载图片示例

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"]; NSData * data = [[NSData alloc]initWithContentsOfURL:url]; UIImage *image = [[UIImage alloc]initWithData:data]; if (data != nil) { dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); }});
3.3 主队列(Main dispatch queue)

application的主要任务(譬如UI管理之类的)都在main dispatch queue中完成;根据文档的描述,main dispatch queue中的task都在一个thread中运行,即application’s main thread(thread 1)。

所以,如果想要更新UI,则必须在main dispatch queue中处理,获取main dispatch queue也很容易,调用dispatch_get_main_queue()函数即可。

//全局队列,一个并行的队列dispatch_get_global_queue//主队列,主线程中的唯一队列,一个串行队列dispatch_get_main_queue

关于dispatch_after延后执行

延时提交block,不等同与延时立即执行。介绍dispatch_after之前先了解一下dispatch time.

dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
  • 第一个参数表示从什么时间开始,DISPATCH_TIME_NOW表示从当前开始
  • 第二个参数表示延时的时间,单文是纳秒,一秒钟等于1000000000纳秒,系统提供一些宏定义来简化计算
#define NSEC_PER_SEC 1000000000ull //每秒有多少纳秒 #define USEC_PER_SEC 1000000ull //每秒有多少毫秒 #define NSEC_PER_USEC 1000ull //每毫秒有多少纳秒

如果想要延时一秒,可以这样写

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);

延时示例实现

double delayInSeconds = 2.0; //延时时间dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,  (delayInSeconds * NSEC_PER_SEC)); //从现在起延时delayInSeconds执行dispatch_after(popTime, dispatch_get_main_queue(), ^{ [self bar]; });//主线程延迟2s后执行bar方法。

4. dispatch_sync和dispatch_async 同步和异步

dispatch_sync 派发的block的执行线程和 dispatch_sync 上下文线程是同一个线程;
dispatch_async 派发的block的执行线程和 dispatch_async 上下文线程不是同一个线程,即主队列 下异步任务还是在主队列下执行;
对于serial dispatch queue中的tasks,无论是同步派发还是异步派发,其执行顺序都遵循FIFO;

为了方便地使用 GCD,苹果提供了一些方法方便我们将 block 放在主线程 或 后台线程执行,或者延后执行。使用的例子如下:

//  后台执行:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
     // something
});
// 主线程执行:
dispatch_async(dispatch_get_main_queue(), ^{
     // something
});
// 一次性执行:
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // code to be executed once
});
// 延迟 2 秒执行:
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    // code to be executed on the main queue after delay
});
  • 自定义队列

关于dispatch_barrier_async

Dispatch Barrier确保提交的闭包是指定队列中在特定时段唯一在执行的一个。在所有先于Dispatch Barrier的任务都完成的情况下这个闭包才开始执行。轮到这个闭包时barrier会执行这个闭包并且确保队列在此过程不会执行其它任务。闭包完成后队列恢复。需要注意dispatch_barrier_async只在自己创建的队列上有这种作用,在全局并发队列和串行队列上,效果和dispatch_sync一样。

赋值示例

self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);- setCount:(NSUInteger)count forKey:(NSString *)key{ key = [key copy]; //确保所有barrier都是async异步的 dispatch_barrier_async(self.isolationQueue, ^(){ if (count == 0) { [self.counts removeObjectForKey:key]; } else { self.counts[key] = @; } });}

//防止文件读写冲突,可以创建一个串行队列,操作都在这个队列中进行,没有更新数据读用并行,写用串行。 dispatch_queue_t dataQueue = dispatch_queue_create("com.fineway.dataqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(dataQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog(@"read data 1"); }); dispatch_async(dataQueue, ^{ NSLog(@"read data 2"); }); //等待前面的都完成,在执行barrier后面的 dispatch_barrier_async(dataQueue, ^{ NSLog(@"write data 1"); [NSThread sleepForTimeInterval:1]; }); dispatch_async(dataQueue, ^{ [NSThread sleepForTimeInterval:1.f]; NSLog(@"read data 3"); }); dispatch_async(dataQueue, ^{ NSLog(@"read data 4"); });

5. 使用GCD来替代performSelector的原因:

关于dispatch_apply

功能:把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用。

for (size_t y = 0; y < height;   y) { for (size_t x = 0; x < width;   x) { // Do something with x and y here }}//因为可以并行执行,所以使用dispatch_apply可以运行的更快- dispatchApplyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_apply(10, concurrentQueue, ^ { NSLog; }); NSLog(@"The end"); //这里有个需要注意的是,dispatch_apply这个是会阻塞当前线程的。这个log打印会在dispatch_apply都结束后才开始执行}

dispatch_apply能够避免线程爆炸,因为GCD能够进行线程优化

- dealWiththreadWithMaybeExplode:explode { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); if  { //有问题的情况,可能会死锁 for (int i = 0; i < 999 ; i  ) { dispatch_async(concurrentQueue, ^{ NSLog(@"wrong %d",i); //do something hard }); } } else { //会优化很多,能够利用GCD管理 dispatch_apply(999, concurrentQueue, ^{ NSLog(@"correct %zu",i); //do something hard }); }}
5.1 performSelector 会导致内存泄漏问题

用performSelector:调用了一个方法,编译器并不知道将要调用的selector是什么,因此,也就不了解其方法签名及返回值,甚至连是否有返回值都不清楚。而且,由于编译器不知道方法名,所以就没办法用ARC的内存管理规则来判定返回值是不是该释放。鉴于此,ARC采用了比较谨慎的做法,就是不添加释放操作。然而,这么做可能导致内存泄漏,因为方法在返回对象时已经将其保留了。

//串行队列dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL)//并行队列dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)

关于dispatch_group

dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。

当group里所有事件都完成GCD API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。

//dispatch_group_notify- dispatchGroupNotifyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, concurrentQueue, ^{ NSLog; dispatch_group_async(group, concurrentQueue, ^{ NSLog; dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog; }); NSLog(@"can continue"); }//dispatch_group_wait- dispatchGroupWaitDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create();//在group中添加队列的block dispatch_group_async(group, concurrentQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog; dispatch_group_async(group, concurrentQueue, ^{ NSLog; dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"can continue");}

dispatch io读取文件的方式类似于下面的方式,多个线程去读取文件的切片数据,对于大的数据文件这样会比单线程要快很多。

dispatch_async(queue,^{/*read 0-99 bytes*/});dispatch_async(queue,^{/*read 100-199 bytes*/});dispatch_async(queue,^{/*read 200-299 bytes*/});
  • dispatch_io_create:创建dispatch io
  • dispatch_io_set_low_water:指定切割文件大小
  • dispatch_io_read:读取切割的文件然后合并。苹果系统日志API里用到了这个技术,可以在这里查看:相关资料
pipe_q = dispatch_queue_create("PipeQ", NULL);//创建pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^{ close;*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  { size_t len = dispatch_data_get_size; 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; eval = _asl_evaluate_send(NULL, aux, -1); _asl_send_message(NULL, eval, aux, NULL); asl_msg_release; dispatch_release; } } if  { //semaphore  1使得不需要再等待继续执行下去。 dispatch_semaphore_signal; dispatch_release(pipe_channel); dispatch_release; }});

Dispatch Source用于监听系统的底层对象,比如文件描述符,Mach端口,信号量等。主要处理的事件如下表方法 说明DISPATCH_SOURCE_TYPE_DATA_ADD 数据增加DISPATCH_SOURCE_TYPE_DATA_OR 数据ORDISPATCH_SOURCE_TYPE_MACH_SEND Mach端口发送DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口接收DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存情况DISPATCH_SOURCE_TYPE_PROC 进程事件DISPATCH_SOURCE_TYPE_READ 读数据DISPATCH_SOURCE_TYPE_SIGNAL 信号DISPATCH_SOURCE_TYPE_TIMER 定时器DISPATCH_SOURCE_TYPE_VNODE 文件系统变化DISPATCH_SOURCE_TYPE_WRITE 文件写入

方法

  • dispatch_source_create:创建dispatch source,创建后会处于挂起状态进行事件接收,需要设置事件处理handler进行事件处理。
  • dispatch_source_set_event_handler:设置事件处理handler
  • dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source释放前做些清理的事。
  • dispatch_source_cancel:关闭dispatch source,设置的事件处理handler不会被执行,已经执行的事件handler不会取消。

另外一种保证同步的方法。使用dispatch_semaphore_signal加1dispatch_semaphore_wait减1,为0时等待的设置方式来达到线程同步的目的和同步锁一样能够解决资源抢占的问题。

 //创建semaphore dispatch_semaphore_t semaphore = dispatch_semaphore_create; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog; [NSThread sleepForTimeInterval:1.f]; NSLog(@"semaphore  1"); dispatch_semaphore_signal(semaphore); // 1 semaphore }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//知道signal发出后才执行 NSLog(@"continue");

只能挂起未执行的block,不能挂起已经执行或者正在执行的block

当串行队列里面同步执行该串行队列的时候,就会发生死锁,解决的方法就是将同步的串行队列放到另外一个线程就能够解决。

//示例1 NSLog; dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSLog; NSLog;//只打印1,2和3相互等待,不会打印,形成死锁//示例2 dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); NSLog; dispatch_async(serialQueue, ^{ NSLog; //串行队列里面同步一个串行队列就会死锁 dispatch_sync(serialQueue, ^{ NSLog; NSLog; NSLog;//只打印1 5 2,3与4不会打印//示例3 NSLog; dispatch_async(dispatch_get_global_queue, ^{ NSLog; //将同步的串行队列放到另外一个线程就能够解决 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog; NSLog; NSLog;//示例4 dispatch_async(dispatch_get_global_queue, ^{ NSLog; //回到主线程发现死循环后面就没法执行了 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog; NSLog; NSLog; //死循环 while  { // }
5.2 performSelector 返回值只能是void或对象类型(id类型)

如果想返回整数或浮点数等scalar类型值,那么就需要执行一些复杂的转换操作,而这种转换操作很容易出错。由于id类型表示指向任意Objective—C对象的指针,所以从技术上来讲,只要返回的大小和指针所占大小相同就行,也就是说,在32位架构的计算机上,可以返回任意32位大小的类型;而在64位架构的计算机上,则可以返回任意64位大小的类型。除此之外,还可以返回NSNumber进行转换…若返回的类型为C语言结构体,则不可使用performSelector方法。

  • 同步异步线程创建
5.3 performSelector 提供的方法局限性大
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;

具备延后功能的那些方法无法处理带有两个参数的情况。而能够指定执行线程的哪些方法,则与之类似,所以也不是特别通用。如果要用这些方法,就得把很多参数打包到字典中,然后在被调用的方法中将这些参数提取出来,这样会增加开销,同时也提高了产生bug的可能性。

6. GCD 中block的使用

//同步线程dispatch_sync(..., ^//异步线程dispatch_async(..., ^
6.1 GCD 会对添加的Block进行复制

Dispatchqueue对添加的Block会进行复制,在完成执行后自动释放。换句话说,你不需要在添加 Block 到 Queue 时显式地复制

  • Serial:又叫private dispatch queues,同时只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。
  • Main dispatch queue:全局可用的serial queue,在应用程序主线程上执行任务。
  • Concurrent:又叫global dispatch queue,可以并发的执行多个任务,但执行完成顺序是随机的。系统提供四个全局并发队列,这四个队列有这对应的优先级,用户是不能够创建全局队列的,只能获取。
6.2 GCD 中的autorelease pool

GCD dispatch queue 有自己的autorelease pool来管理内存对象,但是不保证在什么时候会进行回收,如果在block中创建了大量的对象,可以添加自己的autorelease pool来进行管理。

6.3 GCD 中再开新的线程执行任务不一定更快

如果对于工作量小的block切换线程的开销,比直接在原来线程上执行block的开销要大,那么这样的话,会导致开新的线程反而没有原来执行的快,也就是说谁开销大谁慢。

dipatch_queue_t queue;queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
6.4 GCD 的暂停和继续

1、dispatch_suspend 会暂停一个队列以阻止执行block对象,调用 dispatch_suspend 会增加queue的引用计数
2、dispatch_resume 会使得队列恢复继续执行block对象,调用 dispatch_resume 会减少queue的引用计数
挂起和继续是异步的,只在没有执行的block上生效,挂起一个block不会导致已经开始执行的block停止执行。

  • user create queue:创建自己定义的队列,可以用dispatch_queue_create函数,函数有两个参数,第一个自定义的队列名,第二个参数是队列类型,默认NULL或者DISPATCH_QUEUE_SERIAL的是串行,参数为DISPATCH_QUEUE_CONCURRENT为并行队列。

7. GCD中的信号量 Semaphore

7.1 信号量概念

停车场剩余4个车位,那么即使同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆需要等待。信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait函数就相当于来了一辆车,dispatch_semaphore_signal,就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(long value))调用一次dispatch_semaphore_signal,剩余的车位就增加一个;调用一次dispatch_semaphore_wait剩余车位就减少一个;当剩余车位为0时,再来车(即调用dispatch_semaphore_wait)就只能等待。有可能同时有几辆车等待一个停车位。有些车主没有耐心,给自己设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就要把车停在这,所以就一直等下去。

dispatch_queue_t queuequeue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue", DISPATCH_QUEUE_CONCURRENT);
7.2 信号量的创建和使用

1、创建 dispatch_semaphore_create

 /*!
 * @function dispatch_semaphore_create
   使用信号量来处理多个线程之间竞争资源的情况特别合适,在value等于0的时候进行等待,在value大于0的时候运行
 *
 * @param value
 * 初始化创建的信号量的个数
 *
 * @result
 * 当前创建的信号量
 */
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_MALLOC DISPATCH_RETURNS_RETAINED DISPATCH_WARN_RESULT
DISPATCH_NOTHROW
dispatch_semaphore_t
dispatch_semaphore_create(long value);

2、等待 dispatch_semaphore_wait

  /*!
 * @function dispatch_semaphore_wait
 *
 * @abstract
 * 等待一个信号量
 *
 * @discussion
 * 会对信号量进行-1,如果value小于0,接下来的方法会允许等待的时间里一直等待直到有其他线程有信号量产生即    value>1 才开始执行
 *
 * @param dsema
 * The semaphore. 不允许设置为NULL
 * @param timeout
 * 允许等待的超时时间
 * 一下两个是宏定义的时间
 * DISPATCH_TIME_NOW and DISPATCH_TIME_FOREVER constants.
 *
 */
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long
dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

3、产生 dispatch_semaphore_signal

/*!
 * @function dispatch_semaphore_signal
 *
 * @abstract
 * 对信号量增加1
 *
 * @discussion
 * 对信号量增加1,如果之前的value==0,那么这个操作会唤醒一个正在等待的线程
 * 
 * @param dsema The counting semaphore.
 * The semaphore. 不允许设置为NULL
 */
__OSX_AVAILABLE_STARTING(__MAC_10_6,__IPHONE_4_0)
DISPATCH_EXPORT DISPATCH_NONNULL_ALL DISPATCH_NOTHROW
long
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

注意:dispatch_semaphore_signal 和 dispatch_semaphore_wait 必须成对出现,并且在 dispatch_release(aSemaphore);之前,aSemaphore 的value需要恢复之前的数值,不然会导致 EXC_BAD_INSTRUCTION
在ARC情况下不需要使用dispatch_release来进行释放,有系统统一管理

  • 自定义队列的优先级:可以通过dipatch_queue_attr_make_with_qos_class或dispatch_set_target_queue方法设置队列的优先级
7.3 Dispatch Semaphore 的应用
1、控制并发线程数量
void dispatch_async_limit(dispatch_queue_t queue,NSUInteger limitSemaphoreCount, dispatch_block_t block) {
//控制并发数的信号量
static dispatch_semaphore_t limitSemaphore;
//专门控制并发等待的线程
static dispatch_queue_t receiverQueue;

//使用 dispatch_once而非 lazy 模式,防止可能的多线程抢占问题
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    limitSemaphore = dispatch_semaphore_create(limitSemaphoreCount);
    receiverQueue = dispatch_queue_create("receiver", DISPATCH_QUEUE_SERIAL);
});

dispatch_async(receiverQueue, ^{
    //有可用信号量后才能继续,否则等待
    dispatch_semaphore_wait(limitSemaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
        !block ? : block();
        //在该工作线程执行完成后释放信号量
        dispatch_semaphore_signal(limitSemaphore);
      });
  });
}
//dipatch_queue_attr_make_with_qos_classdispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);dispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.qosqueue", attr);//dispatch_set_target_queuedispatch_queue_t queue = dispatch_queue_create("com.starming.gcddemo.settargetqueue",NULL); //需要设置优先级的queuedispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级dispatch_set_target_queue(queue, referQueue); //设置queue和referQueue的优先级一样
2、等待某个网络回调完之后才执行后面的操作
dispatch_semaphore_t sema = dispatch_semaphore_create(0);  
[NetWorkkService queryCompletion:^(BOOL isSuccess) {  
    dispatch_semaphore_signal(sema);  
} onError:^(int errorCode, NSString *errorMessage) {  
    dispatch_semaphore_signal(sema);  
}];  

dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);  
// todo what you want to do after net callback
  • dispatch_set_target_queue:可以设置优先级,也可以设置队列层级体系,比如让多个串行和并行队列在统一一个串行队列里串行执行,如下
3、使用信号量来处理读写线程安全问题
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dispatch_queue_t queue = dispatch_queue_create("iKingsly", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    for (int i = 0; i < 1000; i  ) {
        dict[@(i)] = @(i);
    }
    dispatch_semaphore_signal(semaphore);
});

dispatch_async(queue, ^{
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    for (int i = 0; i < 1000; i  ) {
        NSLog(@"%@", dict[@(i)]);
    }
    dispatch_semaphore_signal(semaphore);
});

8. CGD Group

dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);dispatch_queue_t firstQueue = dispatch_queue_create("com.starming.gcddemo.firstqueue", DISPATCH_QUEUE_SERIAL);dispatch_queue_t secondQueue = dispatch_queue_create("com.starming.gcddemo.secondqueue", DISPATCH_QUEUE_CONCURRENT);dispatch_set_target_queue(firstQueue, serialQueue);dispatch_set_target_queue(secondQueue, serialQueue);dispatch_async(firstQueue, ^{ NSLog; [NSThread sleepForTimeInterval:3.f];});dispatch_async(secondQueue, ^{ NSLog; [NSThread sleepForTimeInterval:2.f];});dispatch_async(secondQueue, ^{ NSLog; [NSThread sleepForTimeInterval:1.f];});
8.1 用group wait来等待queue的一组任务

如果要等待queue中的一系列操作完成后再去执行一个相应的任务,除了用barrier之外,我们也可以通过group来进行处理,dispatch group wait会阻塞当前的线程,直到group中的任务完成才会停止阻塞,这样我们可以达到一个目的,直到前面的任务完成了,才执行后面的代码

dispatch_queue_t queue = dispatch_queue_create("abc", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue,^{
    NSLog(@"1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"2");
});

dispatch_group_async(group, queue, ^{

    NSLog(@"3");
});

dispatch_group_async(group, queue, ^{
    sleep(5);
    NSLog(@"4");
});

// 开启一个异步队列来等待
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
        NSLog(@"done");
    });
});

NSLog(@"主线程");

log打印如下:

2017-03-22 22:43:59.946 ForDemo[2196:421544] 1
2017-03-22 22:43:59.946 ForDemo[2196:421543] 2
2017-03-22 22:43:59.946 ForDemo[2196:421546] 3
2017-03-22 22:43:59.946 ForDemo[2196:421405] 主线程
2017-03-22 22:44:05.018 ForDemo[2196:421549] 4
2017-03-22 22:44:05.018 ForDemo[2196:421549] done

队列默认是串行的,如果设置改参数为NULL会按串行处理,只能执行一个单独的block,队列也可以是并行的,同一时间执行多个block

8.2 用group notify来实现等待queue的一组任务

用 dispatch_group_notify方法可以等待group中的任务,notify中的任务在原来group中的任务执行结束前不会执行

dispatch_queue_t queue = dispatch_queue_create("abc", DISPATCH_QUEUE_CONCURRENT);

dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue,^{
    NSLog(@"1");
});

dispatch_group_async(group, queue, ^{
    NSLog(@"2");
});

dispatch_group_async(group, queue, ^{

    NSLog(@"3");
});

dispatch_group_async(group, queue, ^{
    sleep(5);
    NSLog(@"4");
});

// 等待group中的任务执行完成
dispatch_group_notify(group, queue, ^{
    NSLog(@"done");
});

NSLog(@"主线程");

log打印如下:

2017-03-22 22:48:28.632 ForDemo[2280:446210] 2
2017-03-22 22:48:28.632 ForDemo[2280:446146] 主线程
2017-03-22 22:48:28.632 ForDemo[2280:446196] 1
2017-03-22 22:48:28.632 ForDemo[2280:446197] 3
2017-03-22 22:48:33.698 ForDemo[2280:446199] 4
2017-03-22 22:48:33.698 ForDemo[2280:446199] done
- init;{ self = [super init]; if (self != nil) { NSString *label = [NSString stringWithFormat:@"%@.isolation.%p", [self class], self]; self.isolationQueue = dispatch_queue_create([label UTF8String], 0); label = [NSString stringWithFormat:@"%@.work.%p", [self class], self]; self.workQueue = dispatch_queue_create([label UTF8String], 0); } return self;}
8.3 手动进入group

dispatch_group_enter 手动通知 Dispatch Group 任务已经开始。你必须保证 dispatch_group_enter 和 dispatch_group_leave 成对出现,否则你可能会遇到诡异的崩溃问题。

dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(5);
    NSLog(@"任务一完成");
    dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    sleep(8);
    NSLog(@"任务二完成");
    dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_global_queue(0, 0), ^{
    NSLog(@"任务完成");
});

log打印如下:

2017-03-22 23:04:31.093 ForDemo[2476:497726] 任务一完成
2017-03-22 23:04:34.086 ForDemo[2476:497725] 任务二完成
2017-03-22 23:04:34.086 ForDemo[2476:497725] 任务完成

5种队列,主队列(main queue),四种通用调度队列,自己定制的队列。四种通用调度队列为

9. Dispatch Source

  • QOS_CLASS_USER_INTERACTIVE:user interactive等级表示任务需要被立即执行提供好的体验,用来更新UI,响应事件等。这个等级最好保持小规模。
  • QOS_CLASS_USER_INITIATED:user initiated等级表示任务由UI发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
  • QOS_CLASS_UTILITY:utility等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
  • QOS_CLASS_BACKGROUND:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。
9.1 它有什么用?

dispatch source 的作用是负责监听事件,先看看它的构造函数。

dispatch_source_create(dispatch_source_type_t type,  
uintptr_t handle,  
unsigned long mask,  
dispatch_queue_t queue);  

第1个参数:要监听的事件类型
第2个参数:可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID
第3个参数:根据参数2,可以理解为描述,提供更详细的描述,让它知道具体要监听什么
第4个参数:当事件发生时,将block添加至哪个队列来执行

示例:后台加载显示图片

9.2 可监听事件的类型
    DISPATCH_SOURCE_TYPE_TIMER        定时响应
    DISPATCH_SOURCE_TYPE_SIGNAL      接收到UNIX信号时响应

    DISPATCH_SOURCE_TYPE_READ   IO操作,如对文件的操作、socket操作的读响应
    DISPATCH_SOURCE_TYPE_WRITE     IO操作,如对文件的操作、socket操作的写响应   
    DISPATCH_SOURCE_TYPE_VNODE    文件状态监听,文件被删除、移动、重命名
    DISPATCH_SOURCE_TYPE_PROC  进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号
    DISPATCH_SOURCE_TYPE_MACH_SEND
    DISPATCH_SOURCE_TYPE_MACH_RECV   上面2个都属于Mach相关事件响应
    DISPATCH_SOURCE_TYPE_DATA_ADD
    DISPATCH_SOURCE_TYPE_DATA_OR          上面2个都属于自定义的事件,并且也是有自己来触发
override func viewDidLoad() { super.viewDidLoad() dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) { // 将工作从主线程转移到全局队列中,这是dispatch_async调用,异步提交保证调用线程会继续执行下去,这样viewDidLoad在主线程上能够更早完成, let overlayImage = self.faceOverlayImageFromImage(self.image) dispatch_async(dispatch_get_main_queue { // 新图完成,把一个闭包加入主线程用来更新UIImageView,只有在主线程能操作UIKit。 self.fadeInNewImage(overlayImage) // 更新UI } }}
9.3 怎么使用

何时使用何种队列类型

1、ADD类型事件监听

自定义事件(DISPATCH_SOURCE_TYPE_DATA_ADD、DISPATCH_SOURCE_TYPE_DATA_OR),先看代码

dispatch_source_t source =dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue());  
dispatch_source_set_event_handler(source,^{  
       NSLog(@"监听函数:%lu",dispatch_source_get_data(source));  
});  
dispatch_resume(source);  

dispatch_queue_t myqueue =dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL);  
dispatch_async(myqueue, ^ {  
      int i;  
      for(i = 0;i < 4;i  ){  
         dispatch_source_merge_data(source,i);  
      }  
});  

首先使用dispatch_source_create函数创建 dispatchsource,第1个参数表示它是一个自定义的_ADD类型的监听,具体作用后面说,2、3参数这里面没有作用设置为0即可,第4个参数 表示一旦事件触发就将要执行的代码块添加到主队列中执行,接着我们使用dispatch_source_set_event_handler函数为这个监 听设置事件的源和响应体,第1个参数表示这个监听是响应用户自定义事件的,也就是我们上面定义的dispatchsource,第2个参数是负责响应的代 码块。很有意思的是当我们创建监听后,这个监听默认是挂起的,需要手动恢复,所以我们使用dispatch_resume函数恢复这个监听,为了测试这个监听,我们后面又通过for循环触发事件,触发事件的函数就是dispatch_source_merge_data,这个函数负责触发自定义事件,第1 个参数表示要触发哪个监听,第2个参数是向监听传入一个unsigned long 类型的值, 我们这里传入循环的索引,好了,整体来看这段程序,dispatch_source_merge_data函数会被执行4次,并分别传入0、1、2、3这 4个值,既然dispatch_source_merge_data负责触发事件,那么我们在监听里面的响应体应该会监听到,结果也确实监听到了,但是并不是我们想象的那样打印4次,而是只打印了一次,打印结果是4次传入值相加的和,也就是6,这就是 DISPATCH_SOURCE_TYPE_DATA_ADD参数的作用,这个监听在创建之初就被设置为自定义监听,并且会把监听结果相加,然后统一响 应。这里你应该会奇怪,既然结果会相加并统一响应,那跟触发的时候加好,然后触发一次有什么区别呢,好吧,我们把触发事件的for循环改一下,然后再运 行,看看会发生什么

for(i = 0;i < 4;i  ){  
    dispatch_source_merge_data(source,i);  
    [NSThread sleepForTimeInterval:0.0001];  
}  

我们在触发事件的地方加上0.0001秒的延迟,然后运行整个程序多次,你会发现奇怪 的现象,我们同样是触发4次事件,但是响应的次数变成不确定了,可能是1次,也可能是2次,如果你将延迟时间设置长点,甚至设置为0点几秒就能让响应的次 数变为固定的4次,为什么会这样呢,其实这就是这个自定义事件设计的初衷。如果同一时间同一个事件被触发的频率非常密集,那么 dispatchsource会将这些密集的响应相加统计做出响应,但是如果触发的相对零散,那么dispatch source会分别进行响应,这其实是在智能的控制UI的没必要的更新操作,因为那些几乎在同一时间更新进度条的操作完全可以统一进行更新,没有必要每次 都更新一下。这样做也会减少UI线程的负担,例如更新进度条的同时,你的UI可能还在同时响应用户的输入、触碰等工作。当然你可以选择实时更新,办法就是 直接使用使用dispatch_async直接更新界面。Dispatch source在统一响应完毕后计数变为0,后面再触发的会重新相加。DISPATCH_SOURCE_TYPE_DATA_OR会将所有监听到的值逻辑与操作,然后统一触发。貌似没有DISPATCH_SOURCE_TYPE_DATA_ADD常用

  • 主队列:队列中有任务完成需要更新UI时,dispatch_after在这种类型中使用。
  • 并发队列:用来执行与UI无关的后台任务,dispatch_sync放在这里,方便等待任务完成进行后续处理或和dispatch barrier同步。dispatch groups放在这里也不错。
  • 自定义顺序队列:顺序执行后台任务并追踪它时。这样做同时只有一个任务在执行可以防止资源竞争。dipatch barriers解决读写锁问题的放在这里处理。dispatch groups也是放在这里。
2、使用Dispatch Queue 来取代NSTimer

众所周知,定时器有NSTimer,但是NSTimer有如下弊端:

  • 必须保证有一个活跃的runloop,子线程的runloop是默认关闭的。这时如果不手动激活runloop,performSelector和scheduledTimerWithTimeInterval的调用将是无效的。

  • NSTimer的创建与撤销必须在同一个线程操作、performSelector的创建与撤销必须在同一个线程操作。

  • 内存管理有潜在泄露的风险会造成循环引用

所以我们可以使用 Dispatch Source 的 DISPATCH_SOURCE_TYPE_TIMER 来实现这个效果:

- (void) startGCDTimer{
    NSTimeInterval period = 1.0; //设置时间间隔
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
     _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒执行
    dispatch_source_set_event_handler(_timer, ^{
        //在这里执行事件
        NSLog(@"每秒执行test");
    });

    dispatch_resume(_timer);
}

-(void) pauseTimer{
    if(_timer){
        dispatch_suspend(_timer);
    }
}

-(void) resumeTimer{
    if(_timer){
        dispatch_resume(_timer);
    }
}

-(void) stopTimer{
    if(_timer){
        dispatch_source_cancel(_timer);
        _timer = nil;
    }
}

可以使用下面的方法简化QoS等级参数的写法

3、监控文件系统对象

设置DISPATCH_SOURCE_TYPE_VNODE 类型的Dispatch Source,可以从这个 Source 中接收文件删除、写入、重命名等通知。

int fd = open(filename, O_EVTONLY);
if (fd == -1)
    return NULL;
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
                                                   , DISPATCH_VNODE_RENAME, queue);
if (source)
{
  // 保持文件名
  int length = strlen(filename);
  char* newString = (char*)malloc(length   1);
  newString = strcpy(newString, filename);
  dispatch_set_context(source, newString);
  // 设置Source的handler 来对监测文件修改的处理
  dispatch_source_set_event_handler(source, ^{
  const char* oldFilename = (char*)dispatch_get_context(source);
  MyUpdateFileName(oldFilename, fd);
  });

// 做释放source之前的处理 关闭文件
  dispatch_source_set_cancel_handler(source, ^{
    char* fileStr = (char*)dispatch_get_context(source); free(fileStr);
    close(fd);
  });
// 开始执行start
dispatch_resume(source);
}
var GlobalMainQueue: dispatch_queue_t { return dispatch_get_main_queue()}var GlobalUserInteractiveQueue: dispatch_queue_t { return dispatch_get_global_queue(Int(QOS_CLASS_USER_INTERACTIVE.value), 0)}var GlobalUserInitiatedQueue: dispatch_queue_t { return dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)}var GlobalUtilityQueue: dispatch_queue_t { return dispatch_get_global_queue(Int(QOS_CLASS_UTILITY.value), 0)}var GlobalBackgroundQueue: dispatch_queue_t { return dispatch_get_global_queue(Int(QOS_CLASS_BACKGROUND.value), 0)}//使用起来就是这样,易读而且容易看出在使用哪个队列dispatch_async(GlobalUserInitiatedQueue) { let overlayImage = self.faceOverlayImageFromImage(self.image) dispatch_async(GlobalMainQueue) { self.fadeInNewImage(overlayImage) }}
4、监测进程的变化

进程 dispatch source 可以监控特定进程的行为,并适当地响应。父进程可以使用 dispatch source 来监控自己创建的所有子进程,例如监控子进程的死亡;类似地,子进程也可以使用 dispatch source 来监控父进程,例如在父进程退出时自己也退出。

NSRunningApplication *mail = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.mail"];
if (mail == nil) {
     return;
}
pid_t const pid = mail.processIdentifier;
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT);
dispatch_source_set_event_handler(self.source, ^(){
     NSLog(@"Mail quit.");
});
//在事件源传到你的事件处理前需要调用dispatch_resume()这个方法
dispatch_resume(self.source);

dispatch_once_t要是全局或static变量,保证dispatch_once_t只有一份实例

5、监视文件夹内文件变化
NSURL *directoryURL; // assume this is set to a directory
int const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);
if (fd < 0) {
     char buffer[80];
     strerror_r(errno, buffer, sizeof(buffer));
     NSLog(@"Unable to open "%@": %s (%d)", [directoryURL path], buffer, errno);
     return;
}
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);
  dispatch_source_set_event_handler(source, ^(){
   unsigned long const data = dispatch_source_get_data(source);
     if (data & DISPATCH_VNODE_WRITE) {
          NSLog(@"The directory changed.");
     }
     if (data & DISPATCH_VNODE_DELETE) {
          NSLog(@"The directory has been deleted.");
     }
});
dispatch_source_set_cancel_handler(source, ^(){
     close(fd);
});
self.source = source;
dispatch_resume(self.source);
//还要注意需要用DISPATCH_VNODE_DELETE 去检查监视的文件或文件夹是否被删除,如果删除了就停止监听
  (UIColor *)boringColor;{ static UIColor *color; //只运行一次 static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f]; }); return color;}

10. Dispatch Barrier

dispatch_barrier 最大的作用就是用来做阻塞,阻塞当前的线程,做到一个承上启下的作用,只有在它之前的任务全部执行完之后,它和它之后的任务才能进行。可以理解为成语一夫当关,万夫莫开,只有在它面前的任务“死掉了”(即执行完了)后面的任务才能继续进行下去。

使用dispatch_barrier 是用来阻断并行任务不能确定先后任务完成的问题,它必须使用在自定义并行队列上,否则没有意义,为什么说没意义呢,我们接下来分析为什么没意义:

如果运用在串行队列上,没有意义,因为串行队列本来就是先进先出的规则,用了栅栏跟没用没有区别
如果使用全局队列,也是没有意义,我们每次 dispatch_get_global_queue 获取到的队列都是不同的,我们任务前后执行不在同一个线程上,也就没有了截流之分。
系统中提供的可变对象都是线程不安全的,也就是在一个线程进行写入数据的时候,不允许其他线程访问,无论是读或者是写都是不允许的。使用dispatch_barrier来实现写入安全:

NSMutableDictionary *dict = [NSMutableDictionary dictionary];
dispatch_queue_t queue = dispatch_queue_create("iKingsly", DISPATCH_QUEUE_CONCURRENT);

dispatch_barrier_async(queue, ^{
    for (int i = 0; i < 1000; i  ) {//这边写入完成,下面才能开始读字典里的数据
        dict[@(i)] = @(i);
    }
});

dispatch_async(queue, ^{
    for (int i = 0; i < 1000; i  ) {
        NSLog(@"%@", dict[@(i)]);
    }
});

设计一个异步的API调用dispatch_async(),这个调用放在API的方法或函数中做。让API的使用者设置一个回调处理队列

11. GCD死锁

当前串行队列里面同步执行当前串行队列就会死锁,解决的方法就是将同步的串行队列放到另外一个线程就能够解决。

- (void)deadLockCase1 {
    NSLog(@"1");
    //主队列的同步线程,按照FIFO的原则(先入先出),2排在3后面会等3执行完,但因为同步线程,3又要等2执行完,相互等待成为死锁。
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");
}
- (void)deadLockCase2 {
    NSLog(@"1");
    //3会等2,因为2在全局并行队列里,不需要等待3,这样2执行完回到主队列,3就开始执行
      dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
      NSLog(@"2");
    });
    NSLog(@"3");
}
- (void)deadLockCase3 {
    dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    NSLog(@"1");
    dispatch_async(serialQueue, ^{
        NSLog(@"2");
        //串行队列里面同步一个串行队列就会死锁
        dispatch_sync(serialQueue, ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
    NSLog(@"5");
}
- (void)deadLockCase4 {
    NSLog(@"1");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"2");
        //将同步的串行队列放到另外一个线程就能够解决
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"3");
        });
        NSLog(@"4");
    });
      NSLog(@"5");
}
- (void)deadLockCase5 {
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1");
        //回到主线程发现死循环后面就没法执行了
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    });
    NSLog(@"4");
    //死循环
    while (1) {
        //
    }
}
- processImage:(UIImage *)image completionHandler:(BOOL success))handler;{ dispatch_async(self.isolationQueue, ^{ // do actual processing here dispatch_async(self.resultQueue, ^{ handler; });}

12. GCD实际使用

可以避免界面会被一些耗时的操作卡死,比如读取网络数据,大数据IO,还有大量数据的数据库读写,这时需要在另一个线程中处理,然后通知主线程更新界面,GCD使用起来比NSThread和NSOperation方法要简单方便。

1、FMDB如何使用dispatch_queue_set_specific和dispatch_get_specific来防止死锁,作用类似objc_setAssociatedObject跟objc_getAssociatedObject
static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;
//创建串行队列,所有数据库的操作都在这个队列里
_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);
//标记队列
dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);

//检查是否是同一个队列来避免死锁的方法
- (void)inDatabase:(void (^)(FMDatabase *db))block {
    FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey);
    assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");
}
//代码框架dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 耗时的操作 dispatch_async(dispatch_get_main_queue(), ^{ // 更新界面 });});//下载图片的示例dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"]; NSData * data = [[NSData alloc]initWithContentsOfURL:url]; UIImage *image = [[UIImage alloc]initWithData:data]; if (data != nil) { dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); }});
2、DTCoreText使用GCD加快解析速度

DTCoreText采用的是SAX解析,iOS自带了XML/HTML的解析引擎libxml,提供了两个解析接口,DOM解析和SAX解析,前者使用简单但是占用内存多,SAX解析由于不会返回一个dom树,采用的是查到一个标签比如回调startElement方法碰到内容就回调_characters碰到类似就回调endElement这样的方式。
根据这种解析方式DTCoreText使用多线程解析能够更快的解析,DTHTMLAttributedStringBuilder使用三个dispatch_queue
_dataParsingQueue:解析html的
_treeBuildingQueue:生成dom树的
_stringAssemblyQueue:组装NSAttributeString的 获取三个队列全部完成采用了dispatch_group的dispatch_group_wait这种阻塞同步方式来返回结果。

dispatch_after只是延时提交block,不是延时立刻执行。

- foo{ double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW,  (delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^{ [self bar]; });}

范例,实现一个推迟出现弹出框提示,比如说提示用户评价等功能。

func showOrHideNavPrompt() { let delayInSeconds = 1.0 let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64(delayInSeconds * Double(NSEC_PER_SEC))) // 在这里声明推迟的时间 dispatch_after(popTime, GlobalMainQueue) { // 等待delayInSeconds将闭包异步到主队列 let count = PhotoManager.sharedManager.photos.count if count > 0 { self.navigationItem.prompt = nil } else { self.navigationItem.prompt = "Add photos with faces to Googlyify them!" } }}

例子中的dispatch time的参数,可以先看看函数原型

dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );

第一个参数为DISPATCH_TIME_NOW表示当前。第二个参数的delta表示纳秒,一秒对应的纳秒为1000000000,系统提供了一些宏来简化

 #define NSEC_PER_SEC 1000000000ull //每秒有多少纳秒 #define USEC_PER_SEC 1000000ull //每秒有多少毫秒 #define NSEC_PER_USEC 1000ull //每毫秒有多少纳秒

这样如果要表示一秒就可以这样写

dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);

Dispatch Barrier确保提交的闭包是指定队列中在特定时段唯一在执行的一个。在所有先于Dispatch Barrier的任务都完成的情况下这个闭包才开始执行。轮到这个闭包时barrier会执行这个闭包并且确保队列在此过程不会执行其它任务。闭包完成后队列恢复。需要注意dispatch_barrier_async只在自己创建的队列上有这种作用,在全局并发队列和串行队列上,效果和dispatch_sync一样

//创建队列self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);//改变setter- setCount:(NSUInteger)count forKey:(NSString *)key{ key = [key copy]; //确保所有barrier都是async异步的 dispatch_barrier_async(self.isolationQueue, ^(){ if (count == 0) { [self.counts removeObjectForKey:key]; } else { self.counts[key] = @; } });}- dispatchBarrierAsyncDemo { //防止文件读写冲突,可以创建一个串行队列,操作都在这个队列中进行,没有更新数据读用并行,写用串行。 dispatch_queue_t dataQueue = dispatch_queue_create("com.starming.gcddemo.dataqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_async(dataQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog(@"read data 1"); }); dispatch_async(dataQueue, ^{ NSLog(@"read data 2"); }); //等待前面的都完成,在执行barrier后面的 dispatch_barrier_async(dataQueue, ^{ NSLog(@"write data 1"); [NSThread sleepForTimeInterval:1]; }); dispatch_async(dataQueue, ^{ [NSThread sleepForTimeInterval:1.f]; NSLog(@"read data 3"); }); dispatch_async(dataQueue, ^{ NSLog(@"read data 4"); });}

swift示例

//使用dispatch_queue_create初始化一个并发队列。第一个参数遵循反向DNS命名习惯,方便描述,第二个参数是指出是并发还是顺序。private let concurrentPhotoQueue = dispatch_queue_create("com.raywenderlich.GooglyPuff.photoQueue", DISPATCH_QUEUE_CONCURRENT)func addPhoto(photo: Photo) { dispatch_barrier_async(concurrentPhotoQueue) { // 将写操作加入到自定义的队列。开始执行时这个就是队列中唯一的一个在执行的任务。 self._photos.append // barrier能够保障不会和其他任务同时进行。 dispatch_async(GlobalMainQueue) { // 涉及到UI所以这个通知应该在主线程中,所以分派另一个异步任务到主队列中。 self.postContentAddedNotification() } }}//上面是解决了写可能发生死锁,下面是使用dispatch_sync解决读时可能会发生的死锁。var photos: [Photo] { var photosCopy: [Photo]! dispatch_sync(concurrentPhotoQueue) { // 同步调度到concurrentPhotoQueue队列执行读操作 photosCopy = self._photos // 保存 } return photosCopy}//这样读写问题都解决了。

都用异步处理避免死锁,异步的缺点在于调试不方便,但是比起同步容易产生死锁这个副作用还算小的。

类似for循环,但是在并发队列的情况下dispatch_apply会并发执行block任务。

for (size_t y = 0; y < height;   y) { for (size_t x = 0; x < width;   x) { // Do something with x and y here }}//因为可以并行执行,所以使用dispatch_apply可以运行的更快- dispatchApplyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue", DISPATCH_QUEUE_CONCURRENT); dispatch_apply(10, concurrentQueue, ^ { NSLog; }); NSLog(@"The end"); //这里有个需要注意的是,dispatch_apply这个是会阻塞主线程的。这个log打印会在dispatch_apply都结束后才开始执行}

dispatch_apply能避免线程爆炸,因为GCD会管理并发

- dealWiththreadWithMaybeExplode:explode { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); if  { //有问题的情况,可能会死锁 for (int i = 0; i < 999 ; i  ) { dispatch_async(concurrentQueue, ^{ NSLog(@"wrong %d",i); //do something hard }); } } else { //会优化很多,能够利用GCD管理 dispatch_apply(999, concurrentQueue, ^{ NSLog(@"correct %zu",i); //do something hard }); }}

示例:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { var storedError: NSError! var downloadGroup = dispatch_group_create() let addresses = [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] dispatch_apply(UInt(addresses.count), GlobalUserInitiatedQueue) { i in let index = Int let address = addresses[index] let url = NSURL(string: address) dispatch_group_enter(downloadGroup) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto } dispatch_group_notify(downloadGroup, GlobalMainQueue) { if let completion = completion { completion(error: storedError) } }}

dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。

当group里所有事件都完成GCD API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。

第一种使用dispatch_group_wait的swift的例子:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { dispatch_async(GlobalUserInitiatedQueue) { // 因为dispatch_group_wait会租塞当前进程,所以要使用dispatch_async将整个方法要放到后台队列才能够保证主线程不被阻塞 var storedError: NSError! var downloadGroup = dispatch_group_create() // 创建一个dispatch group for address in [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] { let url = NSURL(string: address) dispatch_group_enter(downloadGroup) // dispatch_group_enter是通知dispatch group任务开始了,dispatch_group_enter和dispatch_group_leave是成对调用,不然程序就崩溃了。 let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) // 保持和dispatch_group_enter配对。通知任务已经完成 } PhotoManager.sharedManager.addPhoto } dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER) // dispatch_group_wait等待所有任务都完成直到超时。如果任务完成前就超时了,函数会返回一个非零值,可以通过返回值判断是否超时。也可以用DISPATCH_TIME_FOREVER表示一直等。 dispatch_async(GlobalMainQueue) { // 这里可以保证所有图片任务都完成,然后在main queue里加入完成后要处理的闭包,会在main queue里执行。 if let completion = completion { // 执行闭包内容 completion(error: storedError) } } }}

oc例子

- dispatchGroupWaitDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); //在group中添加队列的block dispatch_group_async(group, concurrentQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog; dispatch_group_async(group, concurrentQueue, ^{ NSLog; dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog;}

第二种使用dispatch_group_notify的swift的例子:

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { // 不用加dispatch_async,因为没有阻塞主进程 var storedError: NSError! var downloadGroup = dispatch_group_create() for address in [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] { let url = NSURL(string: address) dispatch_group_enter(downloadGroup) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto } dispatch_group_notify(downloadGroup, GlobalMainQueue) { // dispatch_group_notify和dispatch_group_wait的区别就是是异步执行闭包的,当dispatch groups中没有剩余的任务时闭包才执行。这里是指明在主队列中执行。 if let completion = completion { completion(error: storedError) } }}

oc例子

//dispatch_group_notify- dispatchGroupNotifyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, concurrentQueue, ^{ NSLog; dispatch_group_async(group, concurrentQueue, ^{ NSLog; dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog; }); NSLog(@"can continue");}//dispatch_group_wait- dispatchGroupWaitDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); //在group中添加队列的block dispatch_group_async(group, concurrentQueue, ^{ [NSThread sleepForTimeInterval:2.f]; NSLog; dispatch_group_async(group, concurrentQueue, ^{ NSLog; dispatch_group_wait(group, DISPATCH_TIME_FOREVER); NSLog(@"can continue");}

如何对现有API使用dispatch_group_t

//给Core Data的-performBlock:添加groups。组合完成任务后使用dispatch_group_notify来运行一个block即可。- withGroup:(dispatch_group_t)group performBlock:(dispatch_block_t)block{ if (group == NULL) { [self performBlock:block]; } else { dispatch_group_enter; [self performBlock:^(){ block(); dispatch_group_leave; }]; }}//NSURLConnection也可以这样做  withGroup:(dispatch_group_t)group sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(NSURLResponse*, NSData*, NSError*))handler{ if (group == NULL) { [self sendAsynchronousRequest:request queue:queue completionHandler:handler]; } else { dispatch_group_enter; [self sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){ handler(response, data, error); dispatch_group_leave; }]; }}

注意事项

  • dispatch_group_async等价于dispatch_group_enter() 和 dispatch_group_leave()的组合。
  • dispatch_group_enter() 必须运行在 dispatch_group_leave() 之前。
  • dispatch_group_enter() 和 dispatch_group_leave() 需要成对出现的

队列执行任务都是block的方式,

  • 创建block
- createDispatchBlock { //normal way dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); dispatch_block_t block = dispatch_block_create(0, ^{ NSLog(@"run block"); }); dispatch_async(concurrentQueue, block); //QOS way dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{ NSLog(@"run qos block"); }); dispatch_async(concurrentQueue, qosBlock);}
  • dispatch_block_wait:可以根据dispatch block来设置等待时间,参数DISPATCH_TIME_FOREVER会一直等待block结束
- dispatchBlockWaitDemo { dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); dispatch_block_t block = dispatch_block_create(0, ^{ NSLog; [NSThread sleepForTimeInterval:5.f]; NSLog; }); dispatch_async(serialQueue, block); //设置DISPATCH_TIME_FOREVER会一直等到前面任务都完成 dispatch_block_wait(block, DISPATCH_TIME_FOREVER); NSLog(@"ok, now can go on");}
  • dispatch_block_notify:可以监视指定dispatch block结束,然后再加入一个block到队列中。三个参数分别为,第一个是需要监视的block,第二个参数是需要提交执行的队列,第三个是待加入到队列中的block
- dispatchBlockNotifyDemo { dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); dispatch_block_t firstBlock = dispatch_block_create(0, ^{ NSLog(@"first block start"); [NSThread sleepForTimeInterval:2.f]; NSLog(@"first block end"); }); dispatch_async(serialQueue, firstBlock); dispatch_block_t secondBlock = dispatch_block_create(0, ^{ NSLog(@"second block run"); }); //first block执行完才在serial queue中执行second block dispatch_block_notify(firstBlock, serialQueue, secondBlock);}
  • dispatch_block_cancel:iOS8后GCD支持对dispatch block的取消
- dispatchBlockCancelDemo { dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); dispatch_block_t firstBlock = dispatch_block_create(0, ^{ NSLog(@"first block start"); [NSThread sleepForTimeInterval:2.f]; NSLog(@"first block end"); }); dispatch_block_t secondBlock = dispatch_block_create(0, ^{ NSLog(@"second block run"); }); dispatch_async(serialQueue, firstBlock); dispatch_async(serialQueue, secondBlock); //取消secondBlock dispatch_block_cancel(secondBlock);}

dispatch block object可以为队列中的对象设置示例,下载图片中途进行取消

func downloadPhotosWithCompletion(completion: BatchPhotoDownloadingCompletionClosure?) { var storedError: NSError! let downloadGroup = dispatch_group_create() var addresses = [OverlyAttachedGirlfriendURLString, SuccessKidURLString, LotsOfFacesURLString] addresses  = addresses   addresses // 扩展address数组,复制3份 var blocks: [dispatch_block_t] = [] // 一个保存block的数组 for i in 0 ..< addresses.count { dispatch_group_enter(downloadGroup) let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) { // 创建一个block,block的标志是DISPATCH_BLOCK_INHERIT_QOS_CLASS let index = Int let address = addresses[index] let url = NSURL(string: address) let photo = DownloadPhoto(url: url!) { image, error in if let error = error { storedError = error } dispatch_group_leave(downloadGroup) } PhotoManager.sharedManager.addPhoto } blocks.append dispatch_async(GlobalMainQueue, block) // 把这个block放到GlobalMainQueue上异步调用。因为全局队列是一个顺序队列所以方便取消对象block,同时可以保证下载任务在downloadPhotosWithCompletion返回后才开始执行。 } for block in blocks[3 ..< blocks.count] { let cancel = arc4random_uniform // 随机返回一个整数,会返回0或1 if cancel == 1 { dispatch_block_cancel // 如果是1就取消block,这个只能发生在block还在队列中并没有开始的情况下。因为把block已经放到了GlobalMainQueue中,所以这个地方会先执行,执行完了才会执行block。 dispatch_group_leave(downloadGroup) // 因为已经dispatch_group_enter了,所以取消时也要将其都leave掉。 } } dispatch_group_notify(downloadGroup, GlobalMainQueue) { if let completion = completion { completion(error: storedError) } }}

dispatch io读取文件的方式类似于下面的方式,多个线程去读取文件的切片数据,对于大的数据文件这样会比单线程要快很多。

dispatch_async(queue,^{/*read 0-99 bytes*/});dispatch_async(queue,^{/*read 100-199 bytes*/});dispatch_async(queue,^{/*read 200-299 bytes*/});
  • dispatch_io_create:创建dispatch io
  • dispatch_io_set_low_water:指定切割文件大小
  • dispatch_io_read:读取切割的文件然后合并。

苹果系统日志API里用到了这个技术,可以在这里查看:

pipe_q = dispatch_queue_create("PipeQ", NULL);//创建pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^{ close;*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  { size_t len = dispatch_data_get_size; 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; eval = _asl_evaluate_send(NULL, aux, -1); _asl_send_message(NULL, eval, aux, NULL); asl_msg_release; dispatch_release; } } if  { //semaphore  1使得不需要再等待继续执行下去。 dispatch_semaphore_signal; dispatch_release(pipe_channel); dispatch_release; }});

Dispatch Source用于监听系统的底层对象,比如文件描述符,Mach端口,信号量等。主要处理的事件如下表

方法 说明
DISPATCH_SOURCE_TYPE_DATA_ADD 数据增加
DISPATCH_SOURCE_TYPE_DATA_OR 数据OR
DISPATCH_SOURCE_TYPE_MACH_SEND Mach端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV Mach端口接收
DISPATCH_SOURCE_TYPE_MEMORYPRESSURE 内存情况
DISPATCH_SOURCE_TYPE_PROC 进程事件
DISPATCH_SOURCE_TYPE_READ 读数据
DISPATCH_SOURCE_TYPE_SIGNAL 信号
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件系统变化
DISPATCH_SOURCE_TYPE_WRITE 文件写入

方法

  • dispatch_source_create:创建dispatch source,创建后会处于挂起状态进行事件接收,需要设置事件处理handler进行事件处理。
  • dispatch_source_set_event_handler:设置事件处理handler
  • dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source释放前做些清理的事。
  • dispatch_source_cancel:关闭dispatch source,设置的事件处理handler不会被执行,已经执行的事件handler不会取消。
NSRunningApplication *mail = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.apple.mail"];if (mail == nil) { return;}pid_t const pid = mail.processIdentifier;self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, pid, DISPATCH_PROC_EXIT, DISPATCH_TARGET_QUEUE_DEFAULT);dispatch_source_set_event_handler(self.source, ^(){ NSLog(@"Mail quit.");});//在事件源传到你的事件处理前需要调用dispatch_resume()这个方法dispatch_resume(self.source);

监视文件夹内文件变化

NSURL *directoryURL; // assume this is set to a directoryint const fd = open([[directoryURL path] fileSystemRepresentation], O_EVTONLY);if (fd < 0) { char buffer[80]; strerror_r(errno, buffer, sizeof; NSLog(@"Unable to open "%@": %s ", [directoryURL path], buffer, errno); return;}dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd,DISPATCH_VNODE_WRITE | DISPATCH_VNODE_DELETE, DISPATCH_TARGET_QUEUE_DEFAULT);dispatch_source_set_event_handler(source, ^(){ unsigned long const data = dispatch_source_get_data; if (data & DISPATCH_VNODE_WRITE) { NSLog(@"The directory changed."); } if (data & DISPATCH_VNODE_DELETE) { NSLog(@"The directory has been deleted."); }});dispatch_source_set_cancel_handler(source, ^(){ close;self.source = source;dispatch_resume(self.source);//还要注意需要用DISPATCH_VNODE_DELETE 去检查监视的文件或文件夹是否被删除,如果删除了就停止监听

NSTimer在主线程的runloop里会在runloop切换其它模式时停止,这时就需要手动在子线程开启一个模式为NSRunLoopCommonModes的runloop,如果不想开启一个新的runloop可以用不跟runloop关联的dispatch source timer,如下。

dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, DISPATCH_TARGET_QUEUE_DEFAULT);dispatch_source_set_event_handler(source, ^(){ NSLog(@"Time flies.");});dispatch_time_t startdispatch_source_set_timer(source, DISPATCH_TIME_NOW, 5ull * NSEC_PER_SEC,100ull * NSEC_PER_MSEC);self.source = source;dispatch_resume(self.source);

另外一种保证同步的方法。使用dispatch_semaphore_signal加1dispatch_semaphore_wait减1,为0时等待的设置方式来达到线程同步的目的和同步锁一样能够解决资源抢占的问题。

//dispatch semaphore- dispatchSemaphoreDemo { //创建semaphore dispatch_semaphore_t semaphore = dispatch_semaphore_create; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog; [NSThread sleepForTimeInterval:1.f]; NSLog(@"semaphore  1"); dispatch_semaphore_signal(semaphore); // 1 semaphore }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"continue");}

这里简单介绍下iOS中常用的各种锁和他们的性能。

  • NSRecursiveLock:递归锁,可以在一个线程中反复获取锁不会造成死锁,这个过程会记录获取锁和释放锁的次数来达到何时释放的作用。
  • NSDistributedLock:分布锁,基于文件方式的锁机制,可以跨进程访问。
  • NSConditionLock:条件锁,用户定义条件,确保一个线程可以获取满足一定条件的锁。因为线程间竞争会涉及到条件锁检测,系统调用上下切换频繁导致耗时是几个锁里最长的。
  • OSSpinLock:自旋锁,不进入内核,减少上下文切换,性能最高,但抢占多时会占用较多cpu,好点多,这时使用pthread_mutex较好。
  • pthread_mutex_t:同步锁基于C语言,底层api性能高,使用方法和其它的类似。
  • @synchronized:更加简单。

dispatch_suspend这里挂起不会暂停正在执行的block,只是能够暂停还没执行的block。

  • 缓冲区:dispatch_data_t基于零碎的内存区域,使用dispatch_data_apply来遍历,还可以用dispatch_data_create_subrange来创建一个不做任何拷贝的子区域
  • I/O调度:使用GCD提供的dispatch_io_read,dispatch_io_write和dispatch_io_close
  • 测试:使用dispatch_benchmark小工具
  • 原子操作: libkern/OSAtomic.h里可以查看那些函数,用于底层多线程编程。

当前串行队列里面同步执行当前串行队列就会死锁,解决的方法就是将同步的串行队列放到另外一个线程就能够解决。

- deadLockCase1 { NSLog; //主队列的同步线程,按照FIFO的原则,2排在3后面会等3执行完,但因为同步线程,3又要等2执行完,相互等待成为死锁。 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog; NSLog;}- deadLockCase2 { NSLog; //3会等2,因为2在全局并行队列里,不需要等待3,这样2执行完回到主队列,3就开始执行 dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ NSLog; NSLog;}- deadLockCase3 { dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL); NSLog; dispatch_async(serialQueue, ^{ NSLog; //串行队列里面同步一个串行队列就会死锁 dispatch_sync(serialQueue, ^{ NSLog; NSLog; NSLog;}- deadLockCase4 { NSLog; dispatch_async(dispatch_get_global_queue, ^{ NSLog; //将同步的串行队列放到另外一个线程就能够解决 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog; NSLog; NSLog;}- deadLockCase5 { dispatch_async(dispatch_get_global_queue, ^{ NSLog; //回到主线程发现死循环后面就没法执行了 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog; NSLog; NSLog; //死循环 while  { // }}

作用类似objc_setAssociatedObject跟objc_getAssociatedObject

static const void * const kDispatchQueueSpecificKey = &kDispatchQueueSpecificKey;//创建串行队列,所有数据库的操作都在这个队列里_queue = dispatch_queue_create([[NSString stringWithFormat:@"fmdb.%@", self] UTF8String], NULL);//标记队列dispatch_queue_set_specific(_queue, kDispatchQueueSpecificKey, (__bridge void *)self, NULL);//检查是否是同一个队列来避免死锁的方法- inDatabase:(FMDatabase *db))block { FMDatabaseQueue *currentSyncQueue = (__bridge id)dispatch_get_specific(kDispatchQueueSpecificKey); assert(currentSyncQueue != self && "inDatabase: was called reentrantly on the same queue, which would lead to a deadlock");}

iOS8新加了一个功能叫Quality of Service,里面提供了一下几个更容易理解的枚举名来使用user interactive,user initiated,utility和background。下面的表做了对比

Global queue Corresponding QoS class 说明
Main thread NSQualityOfServiceUserInteractive UI相关,交互等
DISPATCH_QUEUE_PRIORITY_HIGH NSQualityOfServiceUserInitiated 用户发起需要马上得到结果进行后续任务
DISPATCH_QUEUE_PRIORITY_DEFAULT NSQualityOfServiceDefault 默认的不应该使用这个设置任务
DISPATCH_QUEUE_PRIORITY_LOW NSQualityOfServiceUtility 花费时间稍多比如下载,需要几秒或几分钟的
DISPATCH_QUEUE_PRIORITY_BACKGROUND NSQualityOfServiceBackground 不可见在后台的操作可能需要好几分钟甚至几小时的
  • Building Responsive and Efficient Apps with GCD:
  • 官方文档:

本文由新葡京8455发布,转载请注明来源

关键词: