语言恰说GCD

前不久每次听说协程很生气,心啊十分痒想清楚这到底是独什么事物,今天即花功夫看了看Boost库里的Coroutine。谁没想Boost库这么难行,等及如写代码时编译出错了。其实就为不克充分Boost,大部分Boost库都是以头文件的花样提供的,直接include就可了。但是Coroutine这个东西用了Context这么个需要编译的东西,偏偏机子上的VS2013于是之SDK版本少了有文件,编译不过。想想还是算了,了解的要紧目的及了不畏行。不过这里还是想念呕吐糟一句,Coroutine对VS版本的支撑真差!

称到iOS多线程,一般还会谈及四种植方式:pthread、NSThread、GCD和NSOperation。其中,苹果推荐吧是我们绝经常利用的无疑是GCD。对于身为开发者的我们的话,并发一直都挺棘手,如果对GCD的领悟不足够透彻,那么iOS开发之历程绝对免会见一帆风顺。这里,我会从几个角度浅谈自身对GCD的明。

协程(Coroutine)是什么

实在从名字上我们尽管可知见到端倪,我们这里分点儿点来对待。

,重点在程字上,它和线程、纤程颇有涉及。这三者都足以作为履单元,不同之凡层次各异:

  • 线程,系统级行单元。

  • 纤程,系统级实行单元,比线程更轻量,但是待开发者自己调度

  • 协程,语言级施行单元,实际上就是是言语层面的纤程,需要开发者自己调度。实际上Boost.Coroutine2内部的一律栽实现方式就是利用了Windows的纤程(Fiber)。

线程是抢占式的,可以给系统调度,从而实现真正意义上的产出,提高运行效率;然而纤程与协程需要开发者自己调度,换句话说系统非见面指向其进行调度,它们都运作于宿主线程上。原则达成说,如果协程或者纤程是盖函数形式落实之,那么一旦她是线程安全的,它们就能为不同的线程执行。

协程(Coroutine),重点以routine上。顾名思义,协程与函数(或者为例程,routine)是挺相似的。一开始自我还以为Coroutine有差不多神奇,看了Boost.Coroutine之后明白,实际上协程也是为函数为运行载体的。只不过相比函数,协程能够让挂起恢复

平、多线程背景

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的做事办法,使该所有不少优点(快、稳、准):

  • 及早,更快之内存效率,因为线程栈不暂时存于应用内存。
  • 稳,提供了电动的跟周全的线程池管理机制,稳定要方便。
  • 以,提供了直接以简单的调用接口,使用方便,准确。

协程有啊长处

协程的亮点多是参考线程来比的:

  • Context switch代价更粗。
  • 资源开发更不见。

除外,因此协程可以看切换调度,如果配合上包罗万象的语法,异步程序写起会爽的如出一辙逼近,具体示例可以参考C#的async/await.aspx)例子。目前为止似乎协程的采用场景都较单薄,如果协调本身不熟识协程这个定义的话,你区看一些语法复杂的协程库写成的代码就会见当的平脸蒙逼(例如C++的Coroutine)。当然主要理解该概念嘛。

老二、队列和职责

初学GCD的时段,肯定会纠结有看似很重要而可毫无意义的题目。比如:GCD和线程到底什么关系;异步任务到底在谁线程工作;队列到底是个什么事物;mian
queue和main
thread到底为什么名堂等等。现在,这些我们一直略过(最后拾遗中会谈一下),苹果既然推荐应用GCD,那么为什么还要纠结于线程呢?需要关注之独发一定量单概念:队列、任务。

1. 队列

调度班是一个对象,它见面因为first-in、first-out的主意管理而提交的任务。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将任务在队列。将异步任务在串行队列,会相继执行,并且不会见起死锁问题。将异步任务在并行队列,会并行执行多只任务,这也是我们最好常用之一律种方法。

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

使用场景
这种用法非常广,比如开一个异步的网要,待数额返回后归主队列刷新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内存。
  • ARC:ARC在编译时刻自动管理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

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图