C++巧谈GCD

谈到iOS八线程,一般都会谈到三种方法:pthread、NSThread、GCD和NSOperation。其中,苹果推荐也是我们最平时应用的耳闻目睹是GCD。对于身为开发者的我们的话,并发一贯都很为难,假设对GCD的知晓不够透彻,那么iOS开发的进度相对不会顺遂。那里,小编会从多少个角度浅谈本人对GCD的了然。

一、四线程背景

Although threads have been around for many years and continue to have
their uses, they do not solve the general problem of executing
multiple tasks in a scalable way. With threads, the burden of creating
a scalable solution rests squarely on the shoulders of you, the
developer. You have to decide how many threads to create and adjust
that number dynamically as system conditions change. Another problem
is that your application assumes most of the costs associated with
creating and maintaining any threads it uses.

上述差不多说出了直白操纵线程达成三多线程的弊病:

  • 开发人士必须依据系统的更动动态调整线程的数额和景色,即对开发者的负担重。
  • 应用程序会在开立和护卫线程上消耗很多费用,即功效低。

相对的,GCD是一套低层级的C API,通过
GCD,开发者只需要向队列中添加一段代码块(block或C函数指针),而不要求向来和线程打交道。GCD在后端管理着三个线程池,它不但决定着你的代码块将在哪些线程被实施,还依照可用的系统财富对那几个线程举办管制。GCD的行事章程,使其有着不少优点(快、稳、准):

  • 快,更快的内存效用,因为线程栈不暂存于应用内存。
  • 稳,提供了自行的和健全的线程池管理机制,稳定而方便。
  • 准,提供了一贯并且不难的调用接口,使用方便,准确。

二、队列和天职

初学GCD的时候,肯定会纠结一些类似很重大但却毫无意义的难点。比如:GCD和线程到底怎么样关系;异步任务到底在哪个线程工作;队列到底是个什么东西;mian
queue和main
thread到底搞哪样名堂等等。将来,那么些大家直接略过(最后拾遗中会谈一下),苹果既然推荐使用GCD,那么为什么还要纠结于线程呢?须求关爱的唯有三个概念:队列、义务。

1. 队列

调度队列是2个对象,它会以first-in、first-out的章程管理您提交的职分。GCD有二种队列类型:

  • 串行队列,串行队列将职责以先进先出(FIFO)的相继来施行,所以串行队列平时用来做访问一些特定能源的一块处理。你可以也根据须要创建三个系列,而这几个队列相对其他队列都是出新执行的。换句话说,若是你创建了四个串行队列,每多少个队列在同一时半刻间都只举行贰个职分,对那多个职责以来,他们是互相独立且并发执行的。如若须求创设串行队列,一般用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中的义务只是3个代码块,它可以指三个block只怕函数指针。依照那么些代码块添加进去队列的章程,将职责分为同步职责和异步职责:

  • 联手义务,使用dispatch_sync将义务参预队列。将同步义务参加串行队列,会相继执行,一般不这么做而且在一个职务未竣事时调起此外同步任务会死锁。将联袂职务参与并行队列,会挨个执行,不过也没怎么意思。
  • 异步义务,使用dispatch_async将任务出席队列。将异步职分出席串行队列,会挨个执行,并且不会出现死锁难点。将异步任务参与并行队列,会并行执行多少个义务,那也是我们最常用的一种艺术。

3. 回顾利用

// 队列的创建,queue1:中(默认)优先级的全局并行队列、queue2:主队列、queue3:未指定type则为串行队列、queue4:指定串行队列、queue5:指定并行队列
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue2 = dispatch_get_main_queue();
dispatch_queue_t queue3 = dispatch_queue_create("queue3", NULL);
dispatch_queue_t queue4 = dispatch_queue_create("queue4", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue5 = dispatch_queue_create("queue5", DISPATCH_QUEUE_CONCURRENT);

// 队列中添加异步任务
dispatch_async(queue1, ^{
// 任务
...
});

// 队列中添加同步任务
dispatch_sync(queue1, ^{
// 任务
...
});

三、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刷新
        ...
    });
});

应用场景
那种用法非凡广泛,比如敞开3个异步的网络请求,待数额再次回到后回到主队列刷新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;
}

应用场景
dispatch_set_context可以为队列添加上下文数据,不过因为GCD是C语言接口情势的,所以其context参数类型是“void
*”。需使用上述abc三种办法创立context,并且一般结合dispatch_set_finalizer_f使用,回收context内存。

四、内存和七台河

有个别提一下呢,因为某个人纠结于dispatch的内存难点。
内存

  • MRC:用dispatch_retain和dispatch_release管理dispatch_object_t内存。
  • A福睿斯C:AOdysseyC在编译时刻自动管理dispatch_object_t内存,使用retain和release会报错。

安全
dispatch_queue是线程安全的,你可以随心所欲往里面添加职分。

五、拾遗

那边根本提一下GCD的一对坑和线程的一对标题。

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, ^{
        // 任务
        ...
    });
};

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) {
        // 任务
        ...
    });
});

dispatch_barrier
dispatch_barrier_sync在串行队列和全局并行队列之中和dispatch_sync同样的效应,所以需考虑同dispatch_sync一样的死锁难点。

2. dispatch_time_t

// dispatch_time_t一般在dispatch_after和dispatch_group_wait等方法里作为参数使用。这里最需要注意的是一些宏的含义。
// NSEC_PER_SEC,每秒有多少纳秒。
// USEC_PER_SEC,每秒有多少毫秒。
// NSEC_PER_USEC,每毫秒有多少纳秒。
// DISPATCH_TIME_NOW 从现在开始
// DISPATCH_TIME_FOREVE 永久

// time为1s的写法
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);

3. GCD和线程的涉嫌

若是你是新手,GCD和线程权且木有关系。
万一您是金牌,大家做恋人吗。

六、参考文献

1、https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html\#//apple\_ref/doc/uid/TP40008091-CH102-SW2
2、https://developer.apple.com/library/ios/documentation/Performance/Reference/GCD\_libdispatch\_Ref/
3、http://tutuge.me/2015/04/03/something-about-gcd/
4、http://www.jianshu.com/p/85b75c7a6286
5、http://www.jianshu.com/p/d56064507fb8