OS X 10.8或iOS 6以及之后版本中使用,Dispatch Queue将会由ARC自动管理,不需要手动释放
队列
- 将多个任务提交给串行队列,多个任务只能按顺序执行,前一个任务执行完,才能开始下一个任务
- 将多个任务交给并发队列,并发队列可以按FIFO的顺序启动多个任务,任务完成顺序按任务和系统决定
获取队列:
- dispatch_get_main_queue()
- 获取主线程关联串行队列
- dispatch_get_current_queue()
- 获取当前执行代码所在队列
- dispatch_get_global_queue(long identifier, unsigned long flags)
- 获取系统的全局并发队列
- 第一个参数接受一下四个优先级
- DISPATCH_QUEUE_PRIORITY_HIGH:
- DISPATCH_QUEUE_PRIORITY_DEFAULT:
- DISPATCH_QUEUE_PRIORITY_LOW:
- DISPATCH_QUEUE_PRIORITY_BACKGROUND:
- 第二个参数一般传入0
创建队列:
- dispatch_queue_create(const char *label, dispatch_queue_attr_t attr)
- 第一个参数表示队列对应字符串标签
- 第二个参数指定队列类型,分为:
- DISPATCH_QUEUE_SERIAL 串行
- DISPATCH_QUEUE_CONCURRENT 并发
获取队列相关信息
- dispatch_queue_get_label(dispatch_queue_t queue);
- 获取队列对应的标签
提交任务
- dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
- 将代码块以异步方式提交给指定队列
- dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
- 将代码块以同步方式提交给指定队列
- 先后提交的两个代码块(即使提交给并发队列),前一个执行完才会执行下一个
- dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
- 将代码块以异步方式提交给指定队列,并在dispatch_time指定的时间开始执行
- dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
- 将代码块以异步方式提交给指定队列,重复执行代码
- 第一个参数指定重复几次
- 第三个参数 block代码块的 size_t表示当前正在执行第几次
- dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);
- 在任务提交给队列,在应用的某个生命周期内只执行一次
- 第一个参数表示代码块是否已经执行过
取消任务
dispatch_block_cancel
。 iOS8之后可以调用dispatch_block_cancel来取消(需要注意必须用dispatch_block_create创建dispatch_block_t)
代码示例:
1 | - (void)gcdBlockCancel{ |
dispatch_group
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);- group中所有代码块执行完之后执行
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
- 返回值表示经过指定的等待时间,属于这个group的任务是否已经全部执行完,如果是则返回0,否则返回非0。
- 第一个参数表示等待的group
- 第二个参数则表示等待时间,有两个特殊值
- DISPATCH_TIME_NOW 表示立刻检查属于这个group的任务是否已经完成
- DISPATCH_TIME_FOREVER 表示一直等到属于这个group的任务全部完成。
例子:
1 | let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) |
输出的顺序与添加进队列的顺序无关,因为队列是Concurrent Dispatch Queue,但“completed”的输出一定是在最后的
1 | let globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) |
暂停和恢复
这些函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复
- dispatch_suspend(queue) //暂停某个队列
- dispatch_resume(queue) //恢复某个队列
dispatch barrier async
我们知道数据在写入时,不能在其他线程读取或写入。但是多个线程同时读取数据是没有问题的。所以我们可以把读取任务放入并行队列,把写入任务放入串行队列,并且保证写入任务执行过程中没有读取任务可以执行。
这样的需求比较常见,GCD提供了一个非常简单的解决办法——dispatch_barrier_async
假设我们有四个读取任务,在第二三个任务之间有一个写入任务,代码大概是这样:
1 | let queue = dispatch_queue_create("com.gcd.kt", DISPATCH_QUEUE_CONCURRENT) |
如果代码这样写,由于这几个block是并发执行,就有可能在前两个block中读取到已经修改了的数据。如果是有多写入任务,那问题更严重,可能会有数据竞争。
如果使用dispatch_barrier_async函数,代码就可以这么写:
1 | dispatch_async(queue, block1_for_reading) |
dispatch_barrier_async会把并行队列的运行周期分为这三个过程:
- 首先等目前追加到并行队列中所有任务都执行完成
- 开始执行dispatch_barrier_async中的任务,这时候即使向并行队列提交任务,也不会执行
- dispatch_barrier_async中的任务执行完成后,并行队列恢复正常。
总的来说,dispatch_barrier_async起到了“承上启下”的作用。它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。正如barrier的含义一样,它起到了一个栅栏、或是分水岭的作用。
这样一来,使用并行队列和dispatc_barrier_async方法,就可以高效的进行数据和文件读写了。
信号量
dispatch_semaphore
首先介绍一下信号量(semaphore)的概念。信号量是持有计数的信号,不过这么解释等于没解释。我们举个生活中的例子来看看。
假设有一个房子,它对应进程的概念,房子里的人就对应着线程。一个进程可以包括多个线程。这个房子(进程)有很多资源,比如花园、客厅等,是所有人(线程)共享的。
但是有些地方,比如卧室,最多只有两个人能进去睡觉。怎么办呢,在卧室门口挂上两把钥匙。进去的人(线程)拿着钥匙进去,没有钥匙就不能进去,出来的时候把钥匙放回门口。
这时候,门口的钥匙数量就称为信号量(Semaphore)。很明显,信号量为0时需要等待,信号量不为零时,减去1而且不等待。
dispatch_semaphore 与他相关的共有三个函数,分别是
- dispatch_semaphore_create,
- dispatch_semaphore_signal,
- dispatch_semaphore_wait。
下面我们逐一介绍三个函数:
dispatch_semaphore_create的声明为:
1 | dispatch_semaphore_t dispatch_semaphore_create(long value); |
传入的参数为long,输出一个dispatch_semaphore_t类型且值为value的信号量。
值得注意的是,这里的传入的参数value必须大于或等于0,否则dispatch_semaphore_create会返回NULL。
dispatch_semaphore_signal的声明为:
1 | long dispatch_semaphore_signal(dispatch_semaphore_t dsema) |
这个函数会使传入的信号量dsema的值加1;
dispatch_semaphore_wait的声明为:
1 | long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout); |
这个函数会使传入的信号量dsema的值减1;这个函数的作用是这样的,如果dsema信号量的值大于0,该函数所处线程就继续执行下面的语句,并且将信号量的值减1;
如果desema的值为0,那么这个函数就阻塞当前线程等待timeout(注意timeout的类型为dispatch_time_t,不能直接传入整形或float型数),如果等待的期间desema的值被dispatch_semaphore_signal函数加1了,且该函数(即dispatch_semaphore_wait)所处线程获得了信号量,那么就继续向下执行并将信号量减1。
如果等待期间没有获取到信号量或者信号量的值一直为0,那么等到timeout时,其所处线程自动执行其后语句。
返回值
dispatch_semaphore_signal的返回值为long类型,当返回值为0时表示当前并没有线程等待其处理的信号量,其处理的信号量的值加1即可。当返回值不为0时,表示其当前有(一个或多个)线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级时,唤醒优先级最高的线程;否则随机唤醒)。
dispatch_semaphore_wait的返回值也为long型。当其返回0时表示在timeout之前,该函数所处的线程被成功唤醒。当其返回不为0时,表示timeout发生。
设置timeout时,
比较有用的两个宏:
- DISPATCH_TIME_NOW 表示当前;
- DISPATCH_TIME_FOREVER 表示遥远的未来;
一般可以直接设置timeout为这两个宏其中的一个,或者自己创建一个dispatch_time_t类型的变量。
dispatch_time的声明如下:
1 | dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta); |
创建dispatch_time_t类型的变量有两种方法,dispatch_time和dispatch_walltime。其参数when需传入一个dispatch_time_t类型的变量,和一个delta值。表示when加delta时间就是timeout的时间。
例如:dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 110001000*1000);表示当前时间向后延时一秒为timeout的时间。
我们来看一个完整的例子:
1 | var semaphore = dispatch_semaphore_create(1) |
计时器
1 |
|