1.队列
串行队列,串行队列将任务以先进先出(FIFO)的顺序来执行,所以串行队列经常用来做访问某些特定资源的同步处理。你可以也根据需要创建多个队列,而这些队列相对其他队列都是并发执行的。换句话说,如果你创建了4个串行队列,每一个队列在同一时间都只执行一个任务,对这四个任务来说,他们是相互独立且并发执行的。如果需要创建串行队列,一般用dispatch_queue_create这个方法来实现,并指定队列类型DISPATCH_QUEUE_SERIAL。
并发队列,并发队列虽然是能同时执行多个任务,但这些任务仍然是按照先到先执行(FIFO)的顺序来执行的。并发队列会基于系统负载来合适地选择并发执行这些任务。并发队列一般指的就是全局队列(Global queue),进程中存在四个全局队列:高、中(默认)、低、后台四个优先级队列,可以调用dispatch_get_global_queue函数传入优先级来访问队列。当然我们也可以用dispatch_queue_create,并指定队列类型DISPATCH_QUEUE_CONCURRENT,来自己创建一个并发队列。
主队列,与主线程功能相同。实际上,提交至main queue的任务会在主线程中执行。main queue可以调用dispatch_get_main_queue()来获得。因为main queue是与主线程相关的,所以这是一个串行队列。和其它串行队列一样,这个队列中的任务一次只能执行一个。它能保证所有的任务都在主线程执行,而主线程是唯一可用于更新 UI 的线程。
2.任务
同步任务,使用dispatch_sync将任务加入队列。将同步任务加入串行队列,会顺序执行,一般不这样做并且在一个任务未结束时调起其它同步任务会死锁。将同步任务加入并行队列,会顺序执行,但是也没什么意义。
异步任务,使用dispatch_async将任务加入队列。将异步任务加入串行队列,会顺序执行,并且不会出现死锁问题。将异步任务加入并行队列,会并行执行多个任务,这也是我们最常用的一种方式。
3.GCD常见的用法和应用场景
3.1 dispatch_async
(常见的应用场景是异步处理耗时的操作,然后耗时操作处理完毕后,使用主线程更新UI)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
// 一个异步的任务,例如网络请求,耗时的文件操作等等
...
dispatch_async(dispatch_get_main_queue(), ^{
// UI刷新
...
});
});
3.2 dispatch_after
(常用的应用场景是延时调用)
dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
// 在queue里面延迟执行的一段代码
...
});
3.3 dispatch_once
(常用于单例的创建,只创建一次)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只执行一次的任务
...
});
3.4 dispatch_group
(GCD组,把一组任务提交到队列中,多个请求完毕后才处理事情,如多个网络请求完毕会,才去更新UI)
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^{
// 异步任务1
});
dispatch_group_async(group, queue, ^{
// 异步任务2
});
// 等待group中多个异步任务执行完毕,做一些事情,介绍两种方式
// 方式1(不好,会卡住当前线程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
...
// 方式2(比较好)
dispatch_group_notify(group, mainQueue, ^{
// 任务完成后,在主队列中做一些操作
...
});
3.5 dispatch_barrier_async
(和dispatch_group类似,dispatch_barrier也是异步任务间的一种同步方式,可以在比如文件的读写操作时使用,保证读操作的准确性。另外,有一点需要注意,dispatch_barrier_sync和dispatch_barrier_async只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样)
// dispatch_barrier_async的作用可以用一个词概括--承上启下,它保证此前的任务都先于自己执行,此后的任务也迟于自己执行。本例中,任务4会在任务1、2、3都执行完之后执行,而任务5、6会等待任务4执行完后执行。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
// 任务1
...
});
dispatch_async(queue, ^{
// 任务2
...
});
dispatch_async(queue, ^{
// 任务3
...
});
dispatch_barrier_async(queue, ^{
// 任务4
...
});
dispatch_async(queue, ^{
// 任务5
...
});
dispatch_async(queue, ^{
// 任务6
...
});
3.6 dispatch_apply
(dispatch_apply有什么用呢,因为dispatch_apply并行的运行机制,效率一般快于for循环的类串行机制(在for一次循环中的处理任务很多时差距比较大)。比如这可以用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性,如果用for循环,耗时较多,并且每个表单的数据没有依赖关系,所以用dispatch_apply比较好)
// for循环做一些事情,输出0123456789
for (int i = 0; i < 10; i ++) {
NSLog(@"%d", i);
}
// dispatch_apply替换(当且仅当处理顺序对处理结果无影响环境),输出顺序不定,比如1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函数说明
*
* @brief dispatch_apply函数是dispatch_sync函数和Dispatch Group的关联API
* 该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等到全部的处理执行结束
*
* @param 10 指定重复次数 指定10次
* @param queue 追加对象的Dispatch Queue
* @param index 带有参数的Block, index的作用是为了按执行的顺序区分各个Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"%zu", index);
});
3.7 dispatch_suspend和dispatch_resume
(队列的暂停和恢复,已添加到队列中没有执行的任务不会执行,直至等到线程恢复才会继续执行)
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暂停队列queue
dispatch_resume(queue); //恢复队列queue
3.8 dispatch_semaphore_signal
dispatch_semaphore 信号量基于计数器的一种多线程同步机制。在多个线程访问共有资源时候,会因为多线程的特性而引发数据出错的问题。
// dispatch_semaphore_signal有两类用法:a、解决同步问题;b、解决有限资源访问(资源为1,即互斥)问题。
// dispatch_semaphore_wait,若semaphore计数为0则等待,大于0则使其减1。
// dispatch_semaphore_signal使semaphore计数加1。
// a、同步问题:输出肯定为1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);
dispatch_async(queue, ^{
// 任务1
dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
NSLog(@"1\n");
dispatch_semaphore_signal(semaphore2);
dispatch_semaphore_signal(semaphore1);
});
dispatch_async(queue, ^{
// 任务2
dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
NSLog(@"2\n");
dispatch_semaphore_signal(semaphore3);
dispatch_semaphore_signal(semaphore2);
});
dispatch_async(queue, ^{
// 任务3
dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
NSLog(@"3\n");
dispatch_semaphore_signal(semaphore3);
});
// b、有限资源访问问题:for循环看似能创建100个异步任务,实质由于信号限制,最多创建10个异步任务。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
// 任务
...
dispatch_semaphore_signal(semaphore);
});
}
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
如果semaphore计数大于等于1.计数-1,返回,程序继续运行。
如果计数为0,则等待。
这里设置的等待时间是一直等待。
dispatch_semaphore_signal(semaphore);
计数+1.
在这两句代码中间的执行代码,每次只会允许一个线程进入,这样就有效的保证了在多线程环境下,只能有一个线程进入。
3.9 dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f
(dispatch_set_context可以为队列添加上下文数据,但是因为GCD是C语言接口形式的,所以其context参数类型是“void *”。需使用上述abc三种方式创建context,并且一般结合dispatch_set_finalizer_f使用,回收context内存)
// dispatch_set_context、dispatch_get_context是为了向队列中传递上下文context服务的。
// dispatch_set_finalizer_f相当于dispatch_object_t的析构函数。
// 因为context的数据不是foundation对象,所以arc不会自动回收,一般在dispatch_set_finalizer_f中手动回收,所以一般讲上述三个方法绑定使用。
- (void)test
{
// 几种创建context的方式
// a、用C语言的malloc创建context数据。
// b、用C++的new创建类对象。
// c、用Objective-C的对象,但是要用__bridge等关键字转为Core Foundation对象。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
if (queue) {
// "123"即为传入的context
dispatch_set_context(queue, "123");
dispatch_set_finalizer_f(queue, &xigou);
}
dispatch_async(queue, ^{
char *string = dispatch_get_context(queue);
NSLog(@"%s", string);
});
}
// 该函数会在dispatch_object_t销毁时调用。
void xigou(void *context)
{
// 释放context的内存(对应上述abc)
// a、CFRelease(context);
// b、free(context);
// c、delete context;
}
4. 常见的死锁
4.1 dispatch_sync
// 假设这段代码执行于主队列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();
// 在主队列添加同步任务
dispatch_sync(mainQueue, ^{
// 任务
...
});
// 在串行队列添加同步任务
dispatch_sync(serialQueue, ^{
// 任务
...
dispatch_sync(serialQueue, ^{
// 任务
...
});
};
4.2 dispatch_apply
// 因为dispatch_apply会卡住当前线程,内部的dispatch_apply会等待外部,外部的等待内部,所以死锁。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
// 任务
...
dispatch_apply(10, queue, ^(size_t) {
// 任务
...
});
});
4.3 dispatch_barrier
dispatch_barrier_sync在串行队列和全局并行队列里面和dispatch_sync同样的效果,所以需考虑同dispatch_sync一样的死锁问题。