GCD底层原理

谈到iOS多线程,一般都会谈到四种方式:pthread、NSThread、GCD和NSOperation。


串行队列和并行队列

Main Dispatch Queue/Global Dispatch Queue  主队列特殊的串行、全局的并行队列

全局的并行队列dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

NSLog(@"并行队列任务1");

主队列特殊的串行dispatch_sync(dispatch_get_main_queue(), ^{

/**

*  主线程执行

*/

});

});

//串行队列

dispatch_queue_t queue = dispatch_queue_create("com.example.MyQueue", DISPATCH_QUEUE_SERIAL);

//并行队列

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

dispatch_suspend并不会立即暂停正在运行的block,而是在当前block执行完成后,暂停后续的block执行。

dispatch_after是延迟提交,不是延迟运行 就是入队,指的就是将一个Block在特定的延时以后,加入到指定的队列中,不是在特定的时间后立即运行!。

dispatch_queue_t queue = dispatch_queue_create("me.tutuge.test.gcd", DISPATCH_QUEUE_SERIAL);

//提交第一个block,延时5秒打印。

dispatch_async(queue, ^{

[NSThread sleepForTimeInterval:5];

NSLog(@"After 5 seconds...");

});

灵活使用dispatch_group

很多时候我们需要等待一系列任务(block)执行完成,然后再做一些收尾的工作。

使用dispatch_barrier_async,dispatch_barrier_sync的注意事项

dispatch_barrier_async的作用就是向某个队列插入一个block,当目前正在执行的block运行完成后,阻塞这个block后面添加的block,只运行这个block直到完成,然后再继续后续的任务,有点“唯我独尊”的感觉=。=

值得注意的是:

dispatchbarrier\(a)sync只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。

既然在串行队列上跟dispatch_(a)sync效果一样,那就要小心别死锁!

dispatch_set_context与dispatch_set_finalizer_f的配合使用

dispatch_set_context可以为队列添加上下文数据,但是因为GCD是C语言接口形式的,所以其context参数类型是“void *”。也就是说,我们创建context时有如下几种选择:

用C语言的malloc创建context数据。

用C++的new创建类对象。

用Objective-C的对象,但是要用__bridge等关键字转为Core Foundation对象。

以上所有创建context的方法都有一个必须的要求,就是都要释放内存!,无论是用free、delete还是CF的CFRelease,我们都要确保在队列不用的时候,释放context的内存,否则就会造成内存泄露。

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有四个优先级


会引起锁死   该段代码在主线程中执行指定的Block,并等待其执行结束。而其实主线程正在执行此段代码,所以无法执行追加到Mian Dispatch Queue中的Block。

对于锁死的简单理解:一般在串行队列中 如主线程mian  在主线程在执行一个sync同步线程 主队列在执行  当前的代码  无法添加这个同步block到Queue中 

在main线程使用“同步”方法提交Block,必定会死锁。

dispatch_sync(dispatch_get_main_queue(), ^{

NSLog(@"I am block...");

});

GCD有三种队列类型:

串行队列,串行队列将任务以先进先出(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. 任务

linux内核中的任务的定义是描述进程的一种结构体,而GCD中的任务只是一个代码块,它可以指一个block或者函数指针。根据这个代码块添加进入队列的方式,将任务分为同步任务和异步任务:

同步任务,使用dispatch_sync将任务加入队列。将同步任务加入串行队列,会顺序执行,一般不这样做并且在一个任务未结束时调起其它同步任务会死锁。将同步任务加入并行队列,会顺序执行,但是也没什么意义。

异步任务,使用dispatch_async将任务加入队列。将异步任务加入串行队列,会顺序执行,并且不会出现死锁问题。将异步任务加入并行队列,会并行执行多个任务,这也是我们最常用的一种方式。

三、GCD常见用法和应用场景

非常喜欢一句话:Talk is cheap, show me the code.接下来对GCD的使用,我会通过代码展示。

1. dispatch_async

一般用法

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(globalQueue, ^{

// 一个异步的任务,例如网络请求,耗时的文件操作等等

...

dispatch_async(dispatch_get_main_queue(), ^{

// UI刷新

...

});

});

应用场景

这种用法非常常见,比如开启一个异步的网络请求,待数据返回后返回主队列刷新UI;又比如请求图片,待图片返回刷新UI等等。

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里面延迟执行的一段代码

...

});

应用场景

这为我们提供了一个简单的延迟执行的方式,比如在view加载结束延迟执行一个动画等等。

3. dispatch_once

一般用法

static dispatch_once_t onceToken;

dispatch_once(&onceToken, ^{

// 只执行一次的任务

...

});

应用场景

可以使用其创建一个单例,也可以做一些其他只执行一次的代码,比如做一个只能点一次的button(好像没啥用)。

4. 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, ^{

// 异步任务1

});

dispatch_group_async(group, queue, ^{

// 异步任务2

});

// 等待group中多个异步任务执行完毕,做一些事情,介绍两种方式

// 方式1(不好,会卡住当前线程)

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

...

// 方式2(比较好)

dispatch_group_notify(group, mainQueue, ^{

// 任务完成后,在主队列中做一些操作

...

});

应用场景

上述的一种方式,可以适用于自己维护的一些异步任务的同步问题;但是对于已经封装好的一些库,比如AFNetworking等,我们不获取其异步任务的队列,这里可以通过一种计数的方式控制任务间同步,下面为解决单界面多接口的一种方式。

// 两个请求和参数为我项目里面的不用在意。

// 计数+1

dispatch_group_enter(group);

[JDApiService getActivityDetailWithActivityId:self.activityId Location:stockAddressId SuccessBlock:^(NSDictionary *userInfo) {

// 数据返回后一些处理

...

// 计数-1

dispatch_group_leave(group);

} FailureBlock:^(NSError *error) {

// 数据返回后一些处理

...

// 计数-1

dispatch_group_leave(group);

}];

// 计数+1

dispatch_group_enter(group);

[JDApiService getAllCommentWithActivityId:self.activityId PageSize:3 PageNum:self.commentCurrentPage SuccessBlock:^(NSDictionary *userInfo) {

// 数据返回后一些处理

...

// 计数-1

dispatch_group_leave(group);

} FailureBlock:^(NSError *error) {

// 数据返回后一些处理

...

// 计数-1

dispatch_group_leave(group);

}];

// 其实用计数的说法可能不太对,但是就这么理解吧。会在计数为0的时候执行dispatch_group_notify的任务。

dispatch_group_notify(group, mainQueue, ^{

// 一般为回主队列刷新UI

...

});

5. dispatch_barrier_async

一般用法

// 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

...

});

应用场景

和dispatch_group类似,dispatch_barrier也是异步任务间的一种同步方式,可以在比如文件的读写操作时使用,保证读操作的准确性。另外,有一点需要注意,dispatch_barrier_sync和dispatch_barrier_async只在自己创建的并发队列上有效,在全局(Global)并发队列、串行队列上,效果跟dispatch_(a)sync效果一样。

6. 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);

});

应用场景

那么,dispatch_apply有什么用呢,因为dispatch_apply并行的运行机制,效率一般快于for循环的类串行机制(在for一次循环中的处理任务很多时差距比较大)。比如这可以用来拉取网络数据后提前算出各个控件的大小,防止绘制时计算,提高表单滑动流畅性,如果用for循环,耗时较多,并且每个表单的数据没有依赖关系,所以用dispatch_apply比较好。

7. dispatch_suspend和dispatch_resume

一般用法

dispatch_queue_t queue = dispatch_get_main_queue();

dispatch_suspend(queue); //暂停队列queue

dispatch_resume(queue);  //恢复队列queue

应用场景

这种用法我还没有尝试过,不过其中有个需要注意的点。这两个函数不会影响到队列中已经执行的任务,队列暂停后,已经添加到队列中但还没有执行的任务不会执行,直到队列被恢复。

8. dispatch_semaphore_signal

一般用法

// 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_t,并没有看到太多应用和资料解释,我只能参照自己对linux信号量的理解写了两个用法,经测试确实相似。这里,就不对一些死锁问题进行讨论了。

9.  dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f

一般用法

// 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;

}

你可能感兴趣的:(GCD底层原理)