iOS二十四线程GCD简介(二)

在上一篇中,大家首要讲了Dispatch
Queue
连锁的情节。那篇主要讲一下部分和实在相关的施用实例,Dispatch
Groups和Dispatch Semaphore。

原文:https://www.jianshu.com/p/2d57c72016c6

dispatch_after

在我们开发进程中不时会用到在稍微秒后举办某个方法,平时大家会用这么些- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay函数。不过现在我们可以运用一个新的法子。

dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    dispatch_after(delayTime, dispatch_get_main_queue(), ^{
        //do your task
    });

这么大家就定义了一个延缓2秒后实施的职分。可是在此间有某些急需证实的是,无论你用的是- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay还是dispatch_after这么些法子。并不是说在您指定的延期后当即运行,那几个艺术都是根据单线程的,它只是将您延缓的操作出席到行列之中去。由于队列之中都是FIFO,所以必须在您这一个任务从前的操作完成后才会执行你的艺术。这几个延迟只是大概的推移。假若你在主线程里面调用那个法子,假如您主线程现在正在处理一个相当耗时的任务,那么您那几个延迟可能就会偏向很大。这么些时候你可以再开个线程,在其间实践你的延迟操作。

//放到全局默认的线程里面,这样就不必等待当前调用线程执行完后再执行你的方法
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC);
    dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //do your task
    });

概念

Grand Central Dispatch(GCD)是 Apple
开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支撑多核处理器以及其余对称多处理系统。它是一个在线程池情势的基本功上实施的出现职责。

dispatch_once

本条或许我们都万分的耳熟能详,那一个在单例开头化的时候是苹果官方推荐的艺术。这一个函数可以有限支撑在应用程序中只举办指定的任务一遍。即便在三十六线程的条件下举办,也能够有限支撑全部的安全。

    static id instance;
    static dispatch_once_t predicate;

    dispatch_once(&predicate, ^{
        //your init
    });

    return instance;
}

那里面的predicate总得是全局或者静态对象。在八线程下同时做客时,那些格局将被线程同步等待,直到指定的block执行到位。

优点

  • GCD 可用以多核的互动运算
  • GCD 会自动利用越多的 CPU 内核(比如双核、四核)
  • GCD 会自动管理线程的生命周期(创建线程、调度职责、销毁线程)
  • 程序员只需求报告 GCD 想要执行如何职责,不需求编制任何线程管理代码

dispatch_apply

本条格局是举办循环次数固定的迭代,倘诺在产出的queue里面可以进步质量。比如一个稳住次数的for循环

for (int i = 0; i < 1000; i ++) {
        NSLog(@"---%d---", i);
    }

借使只是在一个线程里面或者在一个串行的连串中是同等的,一个个举办。
近来大家用dispatch_apply来写那么些轮回:

dispatch_apply([array count], defaultQueue, ^(size_t i) {
        NSLog(@"----%@---", array[i]);
    });
    NSLog(@"end");

这么些主意执行后,它将像那些并发队列中不断的交由实施的block。这几个i是从0开头的,最后一个是[array count] - 1

使用那个方法有多少个注意点:

  1. 以此点子调用的时候会堵塞当前的线程,也就是上面的循环全体履行达成后,才会输出end
  2. 在你使用那么些义务举行操作的时候,你应当有限帮助您要履行的顺序任务是独立的,而且实行种种也是可有可无的。
  3. 在你利用那个方式的时候,你要么要权衡下总体的属性的,若是您执行的职分时间比线程切换的日子还短。那就贪小失大了。

义务和队列

dispatch_group

在其实开发中,大家兴许必要在一组操作全体成就后,才做任何操作。比如上传一组图片,或者下载多少个文件。希望在方方面面形成时给用户一个升迁。如果那个操作在串行化的种类中推行的话,那么你可以很明确的了然,当最后一个职分执行到位后,就所有完了了。那样的操作也并木有发挥多线程的优势。大家可以在出现的队列中展开那么些操作,可是那一个时候大家就不理解哪个是最终一个到位的了。那个时候我们得以凭借dispatch_group:

    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, defaultQueue, ^{
        //task1
        NSLog(@"1");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task2
        NSLog(@"2");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task3
        NSLog(@"3");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task4
        NSLog(@"4");
    });
    dispatch_group_async(group, defaultQueue, ^{
        //task5
        NSLog(@"5");
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

咱俩先是创制一个group下一场往里面参加我们要推行的操作,在dispatch_group_notify其一函数里面添加任何达成的操作。上边代码执行的时候,输出的1,2,3,4,5的逐一是不肯定的,可是出口的finish一定是在1,2,3,4,5从此。
对于增加到group的操作还有其它一个办法:

    dispatch_group_enter(group);
    dispatch_group_enter(group);

    dispatch_async(defaultQueue, ^{
        NSLog(@"1");
        dispatch_group_leave(group);
    });

    dispatch_async(defaultQueue, ^{
        NSLog(@"2");
        dispatch_group_leave(group);
    });

    dispatch_group_notify(group, queue, ^{
        NSLog(@"finish");
    });

咱俩得以用dispatch_group_enter来表示添加任务,dispatch_group_leave来表示有个职分现已已毕了。用那些方式自然要留意必须成双成对。

任务

任务就是实践操作的意思,换句话说就是线程内举办的代码,在 GCD 中位居
block 里。
实施职务的方法有两种:一齐施行(sync)和异步执行(async),两者的根本分化是:是还是不是等待队列里的天职达成以及是还是不是有所开启新线程的力量

  • 联机施行(sync)
    一道添加职责到指定的行列中,在增进的职分执行完结在此以前,会一贯等候,直到队列之中的任务到位之后再继续执行。只好在眼前线程中施行任务,不享有开启新线程的力量。
  • 异步执行(async)
    异步添加职分到指定的种类中,它不会做其余等待,能够继续执行职责。可以在新的线程中实践义务,具备开启新线程的能力。

注意:异步执行(async)就算持有开启新线程的力量,但是并不一定开启新线程。那跟义务所指定的行列类型有关(上边会讲)。

线程同步

在三多线程中一个比较主要的事物就是线程同步的标题。如若七个线程只是对某个资源只是读的历程,那么就不存在那个题材了。如若某个线程对这些资源须求展开写的操作,那那个时候就见面世数量不均等的标题了。

队列

此地的行列指执行职务的等候队列,即用来存放职务的体系。队列是一种特有的线性表,选用FIFO(先进先出)的基准,即新职务连续被插入到行列的末梢,而读取义务的时候总是从队列的头顶开始读取。每读取一个任务,则从队列中自由一个职务。
GCD
中的任务有两种:串行队列并发队列,两者都契合先入先出规则,不相同在于:执行种种分裂和开启线程数不相同。

  • 串行队列(Serial Dispatch Queue)
    只开启一个线程,每一回只进行一个任务,执行完这一个义务一连下一个义务。
  • 并发队列(Concurrent Dispatch Queue)
    可以在多个线程中,让多少个职分并发(同时)执行。注意:并发队列的面世功能唯有在异步(dispatch_async)函数下才使得

使用dispatch_barrier_async

    __block NSString *strTest = @"test";

    dispatch_async(defaultQueue, ^{
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
    });
    dispatch_async(defaultQueue, ^{
        NSLog(@"--%@--3-", strTest);
    });
    dispatch_async(defaultQueue, ^{
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
    });

看望这一个宪章的情景,我们让种种线程去拜谒那几个变量,其中有个操作是要修改那一个变量。我们把首个操作先判断有木有改动,然后故意推迟一下,那一个时候大家看下输出结果:

2015-01-03 15:42:21.351 测试[1652:60015] --test--3-
2015-01-03 15:42:21.351 测试[1652:60013] --modify--4-
2015-01-03 15:42:21.351 测试[1652:60014] --test--1-
2015-01-03 15:42:22.355 测试[1652:60014] ====changed===

俺们得以见到,再一次判断的时候,已经被涂改了,要是大家在实际上的事务中如此去看清某些重点的变量,可能就会产出严重的标题。下边看看我们什么样运用dispatch_barrier_async来举行联合:

 //并发队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);

    __block NSString *strTest = @"test";

    dispatch_async(concurrentQueue, ^{
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"--%@--3-", strTest);
    });
    dispatch_barrier_async(concurrentQueue, ^{
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"--%@--5-", strTest);
    });

现今看下输出结果:

2015-01-03 16:00:27.552 测试[1786:65947] --test--1-
2015-01-03 16:00:27.552 测试[1786:65965] --test--3-
2015-01-03 16:00:29.553 测试[1786:65947] --test--2-
2015-01-03 16:00:29.553 测试[1786:65947] --modify--4-
2015-01-03 16:00:29.553 测试[1786:65947] --modify--5-

后日大家得以窥见操作4用dispatch_barrier_async参与操作后,前面的操作3此前都操作完结以前那些strTest都并未变。而前边的操作都是改变后的值。那样我们的多寡争辩的标题就一举成功了。
目前验证下这一个函数干的事体,当这一个函数插足到行列后,里面block并不是当下施行的,它会先等待以前正在实施的block全体成就后,才实施,并且在它之后参预到行列中的block也在它操作停止后才能上升此前的产出执行。我们可以把那个函数了然为一条分割线,此前的操作,之后加盟的操作。还有一个点要评释的是其一queue不可能不是用dispatch_queue_create创办出来的才行。

四个第一

使用Dispatch Semaphore

dispatch_semaphore_t 类似信号量,可以用来支配访问某一资源访问数量。
动用进程:

  1. 先创设一个Dispatch Semaphore对象,用整数值表示资源的可用数量
  2. 在每个义务中,调用dispatch_semaphore_wait来等待
  3. 赢得资源就足以展开操作
  4. 操作完后调用dispatch_semaphore_signal来刑满释放资源

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
    __block NSString *strTest = @"test";

    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        if ([strTest isEqualToString:@"test"]) {
            NSLog(@"--%@--1-", strTest);
            [NSThread sleepForTimeInterval:1];
            if ([strTest isEqualToString:@"test"]) {
                [NSThread sleepForTimeInterval:1];
                NSLog(@"--%@--2-", strTest);
            } else {
                NSLog(@"====changed===");
            }
        }
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"--%@--3-", strTest);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        strTest = @"modify";
        NSLog(@"--%@--4-", strTest);
        dispatch_semaphore_signal(semaphore);
    });
    dispatch_async(concurrentQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"--%@--5-", strTest);
        dispatch_semaphore_signal(semaphore);
    });

如此大家同样可以确保,线程的数目安全。

队列与线程的关联:

主队列(main_queue)就是在主线程中的默许队列。并发队列(global_queue)就是新开辟线程中的队列。
一个线程中可以有多少个串行的系列,但是力不从心兼而有之四个冒出的队列,三个冒出的队列会放在多少个线程中。例如可以自定义队列加到主线程中一头执行,中断主队列职分执行完自定义队列中职分后接二连三执行主队列义务。

异步与产出的分别:

异步,是指执行顺序上,差异于同台的贯通,异步会延后进行异步内的职分。而出现是指时间上的,差距于串行的守候,并发会同时实施并发队列中的任务。

GCD的使用手续

GCD的利用有两步,第一步成立一个种类(串行队列或者并行队列),第二步将义务参预到等候队列中。

开创队列

普通选拔dispatch_queue_create(<#const char * _Nullable label#>, <#dispatch_queue_attr_t _Nullable attr#>)来创制队列。第一个参数表示队列的绝无仅有标识符,平时用逆序全程域名;第三个参数用来辨别是串行队列依然出现队列:DISPATCH_QUEUE_SERIAL
表示串行队列,DISPATCH_QUEUE_CONCURRENT 表示并发队列。

//创建串行队列
dispatch_queue_t serialQueue = dispatch_queue_create("com.dispatch.cp", DISPATCH_QUEUE_SERIAL);
//创建并发队列
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.dispatch.cp", DISPATCH_QUEUE_CONCURRENT);
得到队列

对于串行队列,GCD 提供了一种默许的特种的串行队列:主队列(Main
Dispatch Queue)

有着放在主队列中的义务,都会放到主线程中实施。可利用
dispatch_get_main_queue() 得到主队列。

对于出现队列,GCD 供了一种默认的异样的产出队列:大局并发队列(Global
Dispatch Queue)

可以应用 dispatch_get_global_queue
来获取。必要传入多个参数。第四个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。首个参数暂时没用,用0即可。

//获取默认串行队列(主队列)
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//获取默认并发队列 (全局并发队列)
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
始建职务

GCD 提供了协同施行职责的创造方法 dispatch_sync 和异步执行任务创建方法
dispatch_async

//创建同步任务
dispatch_sync(<#dispatch_queue_t  _Nonnull queue#>, ^{
    //这里放任务的执行方法
})

//创建异步任务
dispatch_async(<#dispatch_queue_t  _Nonnull queue#>, ^{
    //这里放任务的执行方法
})

即使采取 GCD
只需两步,然而既然大家有二种队列(串行队列/并发队列),二种职务执行措施(同步施行/异步执行),那么我们就有了八种分歧的结合方式。实际上,刚才还说了两种很是队列:全局并发队列、主队列。全局并发队列可以作为普通并发队列来使用。不过主队列因为有点越发,所以大家就又多了二种组成措施。那样就有多种差异的整合格局了。

  • 同台执行 + 并发队列
    不行。同步不能打开新线程,不可以促成产出。
  • 异步执行 + 并发队列
    二十四线程的常用方式,异步并发,多线程同时进行多个职责。
  • 联合施行 + 串行队列
    一般性串行,没有用到二十多线程。
  • 异步执行 + 串行队列
    四线程的常用情势,异步串行,新建一个线程串行执行八个义务。
  • 共同执行 + 主队列
    失效。新建任务需等候主队列义务落成,主队列后续义务需拭目以待新建职责,相互等待,代码卡死崩溃。
  • 异步执行 + 主队列
    好像异步串行,只是没有新开辟线程,在主线程落成异步。

GCD多种组成下的景观

1.联袂实施 + 并发队列

代码:

- (void)syncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]); //打印当前线程
    NSLog(@"syncConcurrent---begin");
    //创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    //同步任务 + 并发队列
    dispatch_sync(queue, ^{
        // 自定义任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"任务1---%@",[NSThread currentThread]); //打印当前线程

        }

    });
    dispatch_sync(queue, ^{
        // 自定义任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"任务2---%@",[NSThread currentThread]);// 打印当前线程

        }

    });
    dispatch_sync(queue, ^
    {
        // 自定义任务3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"任务3---%@",[NSThread currentThread]);// 打印当前线程

        }

    });
    NSLog(@"syncConcurrent---end");

}

log:

2018-04-08 15:07:13.433670+0800 应用加载时间的优化[5701:894068] currentThread---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:13.434007+0800 应用加载时间的优化[5701:894068] syncConcurrent---begin
2018-04-08 15:07:15.436162+0800 应用加载时间的优化[5701:894068] 任务1---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:17.437191+0800 应用加载时间的优化[5701:894068] 任务1---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:19.438426+0800 应用加载时间的优化[5701:894068] 任务2---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:21.439689+0800 应用加载时间的优化[5701:894068] 任务2---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:23.441261+0800 应用加载时间的优化[5701:894068] 任务3---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:25.442858+0800 应用加载时间的优化[5701:894068] 任务3---<NSThread: 0x600000076180>{number = 1, name = main}
2018-04-08 15:07:25.443222+0800 应用加载时间的优化[5701:894068] syncConcurrent---end

可以看出线程数一向为1,当前线程一向为主线程
小结:并发队列本身不可以创建线程,而一起职务同样没有创立线程。由此一向为主线程同步效果,没有兑现产出。

2.异步执行 + 并发队列

代码:

- (void)asyncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"asyncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 自定义任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 自定义任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程

        }

    });
    dispatch_async(queue, ^{
        // 自定义任务3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2]; //模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
        }

    });
    NSLog(@"asyncConcurrent---end");
}

log:

2018-04-08 22:01:41.497508+0800 应用加载时间的优化[6217:973574] currentThread---<NSThread: 0x604000078780>{number = 1, name = main}
2018-04-08 22:01:41.497657+0800 应用加载时间的优化[6217:973574] asyncConcurrent---begin
2018-04-08 22:01:41.498239+0800 应用加载时间的优化[6217:973574] asyncConcurrent---end
2018-04-08 22:01:44.622924+0800 应用加载时间的优化[6217:973669] 3---<NSThread: 0x600000277200>{number = 4, name = (null)}
2018-04-08 22:01:44.622929+0800 应用加载时间的优化[6217:973666] 2---<NSThread: 0x604000463440>{number = 3, name = (null)}
2018-04-08 22:01:46.624147+0800 应用加载时间的优化[6217:973667] 1---<NSThread: 0x604000461f40>{number = 5, name = (null)}
2018-04-08 22:01:46.624234+0800 应用加载时间的优化[6217:973666] 2---<NSThread: 0x604000463440>{number = 3, name = (null)}
2018-04-08 22:01:46.624266+0800 应用加载时间的优化[6217:973669] 3---<NSThread: 0x600000277200>{number = 4, name = (null)}
2018-04-08 22:01:48.626508+0800 应用加载时间的优化[6217:973667] 1---<NSThread: 0x604000461f40>{number = 5, name = (null)}

计算:可以观察开辟了八个线程(不清楚怎么不是多个),义务交替/同时举行。异步拥有开启新线程的力量,并发执行义务。第一时间在主线程中施行了syncConcurrent---begin
syncConcurrent---end
,然后在开启的多个线程中而且举行自定义的打印职务,然后在延迟三分钟后再同时打印第二次。

3.共同施行 + 串行队列

代码:

- (void)syncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"syncSerial---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        // 追加任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程

        }
    });
    dispatch_sync(queue, ^{
        // 追加任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        }

    });
    dispatch_sync(queue, ^{
        // 追加任务3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    NSLog(@"syncSerial---end");
}

log:

2018-04-08 22:27:00.564340+0800 应用加载时间的优化[6278:994824] currentThread---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:00.564452+0800 应用加载时间的优化[6278:994824] syncSerial---begin
2018-04-08 22:27:02.565885+0800 应用加载时间的优化[6278:994824] 1---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:04.566469+0800 应用加载时间的优化[6278:994824] 1---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:06.568109+0800 应用加载时间的优化[6278:994824] 2---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:08.569092+0800 应用加载时间的优化[6278:994824] 2---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:10.569759+0800 应用加载时间的优化[6278:994824] 3---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:12.571093+0800 应用加载时间的优化[6278:994824] 3---<NSThread: 0x604000078d40>{number = 1, name = main}
2018-04-08 22:27:12.571398+0800 应用加载时间的优化[6278:994824] syncSerial---end

总计:所有任务都在主线程中,没有开发新的线程。职务由上至下,间隔两秒,依次执行。

4.异步实施 + 串行队列

代码:

- (void)asyncSerial {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"asyncSerial---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        // 追加任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    NSLog(@"asyncSerial---end");
}

log:

2018-04-08 22:33:07.135609+0800 应用加载时间的优化[6314:1001768] currentThread---<NSThread: 0x604000073b80>{number = 1, name = main}
2018-04-08 22:33:07.136200+0800 应用加载时间的优化[6314:1001768] asyncSerial---begin
2018-04-08 22:33:07.136992+0800 应用加载时间的优化[6314:1001768] asyncSerial---end
2018-04-08 22:33:09.137247+0800 应用加载时间的优化[6314:1001860] 1---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:11.142105+0800 应用加载时间的优化[6314:1001860] 1---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:13.143027+0800 应用加载时间的优化[6314:1001860] 2---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:15.145256+0800 应用加载时间的优化[6314:1001860] 2---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:17.147141+0800 应用加载时间的优化[6314:1001860] 3---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}
2018-04-08 22:33:19.151007+0800 应用加载时间的优化[6314:1001860] 3---<NSThread: 0x60400027b7c0>{number = 3, name = (null)}

总括:在主线程中第一时间syncConcurrent---begin
syncConcurrent---end,然后在开启的新线程中串行执行自定义职责,依次间隔两秒。

5.联手实施 + 主队列

代码:

- (void)syncMain
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"syncMain---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(dispatch_get_main_queue(), ^{
        // 追加任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_sync(queue, ^{
        // 追加任务3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    NSLog(@"syncMain---end");
}

运转时会直接崩溃。
崩溃原因:那是因为我们在主线程中履行 syncMain 方法,相当于把 syncMain
义务放到了主线程的主队列中。而一同执行会等待眼前队列中的义务履行落成,才会随着执行。那么当我们把职责1日增到主队列中,职务1就在守候主线程处理完
syncMain 职务。而 syncMain 职责急需拭目以待职责1履行已毕,才能跟着执行。
那就是说,现在的情事就是syncMain职分和任务1都在等对方执行已毕。那样我们相互等待,所以就卡住了,所以大家的天职履行不断。
而此前的一路串行和一道并发都可以实施不会崩溃的来头在于,他们即便都在主线程中,但是并不在主队列(main_queue)中,而是在自定义的队列中。

6.异步举办 + 主队列

代码:

- (void)asyncMain {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"asyncMain---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        // 追加任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    NSLog(@"asyncMain---end");
}

log:

2018-04-08 23:14:51.902008+0800 应用加载时间的优化[6448:1043174] currentThread---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:14:51.902183+0800 应用加载时间的优化[6448:1043174] asyncMain---begin
2018-04-08 23:14:51.902511+0800 应用加载时间的优化[6448:1043174] asyncMain---end
2018-04-08 23:14:53.906967+0800 应用加载时间的优化[6448:1043174] 1---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:14:55.907579+0800 应用加载时间的优化[6448:1043174] 1---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:14:57.909015+0800 应用加载时间的优化[6448:1043174] 2---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:14:59.909606+0800 应用加载时间的优化[6448:1043174] 2---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:15:01.910556+0800 应用加载时间的优化[6448:1043174] 3---<NSThread: 0x600000076a00>{number = 1, name = main}
2018-04-08 23:15:03.912148+0800 应用加载时间的优化[6448:1043174] 3---<NSThread: 0x600000076a00>{number = 1, name = main}

总计:类似于异步执行串行队列,都是在第一时间执行完syncConcurrent---begin

syncConcurrent---end后按顺序间隔两秒执行职务。分化之处在于尚未开发新的线程,而是在主线程中异步执行。
因而参照上边所说的异步与产出的涉嫌,实际上异步使自定义的职分延后执行,整个线程仍是在主队列中串行执行自定义职责。而其效果,与异步串行队列效果等同,都是异步串行,根本不同在于前者将职分放在主线程的主队列中,而后者将职责在子线程的自定义队列中,也就是前者只有一个线程,后者多开发了一个线程。

线程间通讯

在iOS开发进程中,大家一般在主线程里边举办UI刷新,例如:点击、滚动、拖拽等事件。我们一般把有些耗时的操作放在其余线程,比如说图片下载、文件上传等耗时操作。而当大家偶尔在任何线程已毕了耗时操作时,需要再次回到主线程,那么就用到了线程之间的通信。
使用dispatch_async(dispatch_get_main_queue(), ^{ });回到主队列。
代码:

- (void)communication {
    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        // 异步追加任务
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        });
    });
}

log:

2018-04-08 23:37:32.260716+0800 应用加载时间的优化[6512:1064267] 1---<NSThread: 0x6040004613c0>{number = 3, name = (null)}
2018-04-08 23:37:34.263732+0800 应用加载时间的优化[6512:1064267] 1---<NSThread: 0x6040004613c0>{number = 3, name = (null)}
2018-04-08 23:37:36.265455+0800 应用加载时间的优化[6512:1064179] 2---<NSThread: 0x600000078780>{number = 1, name = main}

总计:可以看到在其他线程中先实施职务,执行完了后头重返主线程执行主线程的呼应操作。

GCD的其他常用方法

栅栏方法:dispatch_barrier_async

我们有时候须求异步执行两组操作,而且率先组操作实践完之后,才能初叶推行第二组操作。那样我们就须求一个一定于栅栏一样的一个方法将两组异步执行的操作组给分割起来,当然这里的操作组里可以涵盖一个或多少个任务。那就必要用到
dispatch_barrier_async 方法在四个操作组间形成栅栏。
代码:

- (void)barrier {
    NSLog(@"barrier---begin");
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 追加任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_barrier_async(queue, ^{
        // 追加任务 barrier
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务3
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_async(queue, ^{
        // 追加任务4
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"4---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    NSLog(@"barrier---end");
}

log:

2018-04-10 18:52:30.142820+0800 应用加载时间的优化[7378:1317650] barrier---begin
2018-04-10 18:52:30.143018+0800 应用加载时间的优化[7378:1317650] barrier---end
2018-04-10 18:52:32.147032+0800 应用加载时间的优化[7378:1317775] 1---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:32.147034+0800 应用加载时间的优化[7378:1317777] 2---<NSThread: 0x600000274500>{number = 3, name = (null)}
2018-04-10 18:52:34.148931+0800 应用加载时间的优化[7378:1317777] 2---<NSThread: 0x600000274500>{number = 3, name = (null)}
2018-04-10 18:52:34.148931+0800 应用加载时间的优化[7378:1317775] 1---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:36.149834+0800 应用加载时间的优化[7378:1317775] barrier---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:38.151610+0800 应用加载时间的优化[7378:1317775] barrier---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:40.153601+0800 应用加载时间的优化[7378:1317775] 3---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:40.153601+0800 应用加载时间的优化[7378:1317777] 4---<NSThread: 0x600000274500>{number = 3, name = (null)}
2018-04-10 18:52:42.155339+0800 应用加载时间的优化[7378:1317775] 3---<NSThread: 0x604000075880>{number = 4, name = (null)}
2018-04-10 18:52:42.155339+0800 应用加载时间的优化[7378:1317777] 4---<NSThread: 0x600000274500>{number = 3, name = (null)}

小结:由于是异步,所有任务都异步执行。出席了栅栏,所有异步任务先实施栅栏前,再实践栅栏,最终执行栅栏后。

延时艺术:dispatch_after

在指定时间(例如3秒)之后执行某个职务。可以用 GCD 的 dispatch_after
函数来完成。
急需小心的是:dispatch_after
函数并不是在指定时间将来才开始执行拍卖,而是在指定时间过后将职责增多到主队列中。严酷来说,那个日子并不是相对准确的,但想要大概延迟执行任务,dispatch_after
函数是很得力的。
代码:

- (void)after {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"after---begin");
    // 2.0秒后异步追加任务代码到主队列,并开始执行
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0*NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after---%@",[NSThread currentThread]);// 打印当前线程
    });
    NSLog(@"after---end");
}

log:

2018-04-10 19:08:03.125108+0800 应用加载时间的优化[7439:1332291] currentThread---<NSThread: 0x600000077e40>{number = 1, name = main}
2018-04-10 19:08:03.125318+0800 应用加载时间的优化[7439:1332291] after---begin
2018-04-10 19:08:03.125458+0800 应用加载时间的优化[7439:1332291] after---end
2018-04-10 19:08:05.125544+0800 应用加载时间的优化[7439:1332291] after---<NSThread: 0x600000077e40>{number = 1, name = main}

总计:可以观察是异步执行,没有卡主线程。延迟两秒后执行 block 内职分。

只举行四回代码:dispatch_once

我们在开创单例、或者有全方位程序运行进程中只举行一遍的代码时,我们就用到了
GCD 的 dispatch_once 函数。使用 dispatch_once
函数能保障某段代码在程序运行进程中只被实施1次,并且固然在多线程的条件下,dispatch_once
也得以确保线程安全。
代码:

+ (instancetype)instance
{

    static CPReachability *instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
      instance = [[self alloc] init];
      instance.netType = CPAPPNetTypeWIFI;
      instance.wifiName = [instance GetWifiName];
    });
    return instance;
}

小结:多用来单例。dispatch_once_t 其实是 long
类型,取其地方作为唯一标识符有限支撑 block 内部职务执行且仅被实践四遍。

敏捷迭代方法:dispatch_apply

平凡大家会用 for 循环遍历,不过 GCD 给大家提供了高效迭代的函数
dispatch_apply 。dispatch_apply
根据指定的次数将点名的任务增多到指定的队列中,并伺机全体队列执行完结。
大家可以运用异步队列同时遍历。比如说遍历 0~5 那6个数字,for
循环的做法是每回取出一个因素,逐个遍历。dispatch_apply
可以同时遍历八个数字。
代码:

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    NSLog(@"apply---begin");
    dispatch_apply(6, queue, ^(size_t index) {
        [NSThread sleepForTimeInterval:2];//模拟耗时操作
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

log:

2018-04-10 19:18:38.640868+0800 应用加载时间的优化[7506:1345125] apply---begin
2018-04-10 19:18:40.642368+0800 应用加载时间的优化[7506:1345125] 0---<NSThread: 0x6000000714c0>{number = 1, name = main}
2018-04-10 19:18:40.646225+0800 应用加载时间的优化[7506:1345197] 3---<NSThread: 0x600000265e80>{number = 5, name = (null)}
2018-04-10 19:18:40.646225+0800 应用加载时间的优化[7506:1345215] 1---<NSThread: 0x60400046c2c0>{number = 4, name = (null)}
2018-04-10 19:18:40.646272+0800 应用加载时间的优化[7506:1345198] 2---<NSThread: 0x600000265800>{number = 3, name = (null)}
2018-04-10 19:18:42.643982+0800 应用加载时间的优化[7506:1345125] 4---<NSThread: 0x6000000714c0>{number = 1, name = main}
2018-04-10 19:18:42.647137+0800 应用加载时间的优化[7506:1345197] 5---<NSThread: 0x600000265e80>{number = 5, name = (null)}
2018-04-10 19:18:42.647443+0800 应用加载时间的优化[7506:1345125] apply---end

计算:首先大家能见到 dispatch_apply 是出现执行的,因为在 queue
中,所以无法确保实施各样。但是结果是联名的,会等待所有职责完成后持续线程,最终执行
apply---end。优点在于大规模循环时比起 for
功效更高,比起手动开线程能防止线程数过多导致线程爆炸。
动用场景:
若是大家从服务器获取一个数组的数额,那么大家能够利用该方式从而急迅的批量字典转模型。

dispatch_group

dispatch_group 是 GCD
中的一组方法,他有一个组的定义,可以把相关的天职归并到一个组内来施行,通过监听组内所有职责的实施情状来做相应处理。

dispatch_group_create

用于创立职分组
dispatch_group_t dispatch_group_create(void);

dispatch_group_async

把异步职务交给到指定职务组和点名下拿骑行列执行

void dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);
  • group
    ——对应的职分组,之后方可因而dispatch_group_wait或者dispatch_group_notify监听职分组内职分的执行意况
  • queue ——block职责履行的线程队列,职分组内不同职务的队列可以差别
  • block —— 执行职责的block
dispatch_group_notify

待任务组执行达成时调用,不会阻塞当前线程
代码:

- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"group---begin");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        // 追加任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        // 追加任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
        }
        NSLog(@"group---end");
    });
}

log:

2018-04-10 19:31:53.446408+0800 应用加载时间的优化[7570:1360370] currentThread---<NSThread: 0x600000075ac0>{number = 1, name = main}
2018-04-10 19:31:53.446553+0800 应用加载时间的优化[7570:1360370] group---begin
2018-04-10 19:31:55.450356+0800 应用加载时间的优化[7570:1360470] 1---<NSThread: 0x600000272640>{number = 3, name = (null)}
2018-04-10 19:31:55.450352+0800 应用加载时间的优化[7570:1360463] 2---<NSThread: 0x600000273c00>{number = 4, name = (null)}
2018-04-10 19:31:57.450882+0800 应用加载时间的优化[7570:1360470] 1---<NSThread: 0x600000272640>{number = 3, name = (null)}
2018-04-10 19:31:57.450882+0800 应用加载时间的优化[7570:1360463] 2---<NSThread: 0x600000273c00>{number = 4, name = (null)}
2018-04-10 19:31:59.452501+0800 应用加载时间的优化[7570:1360370] 3---<NSThread: 0x600000075ac0>{number = 1, name = main}
2018-04-10 19:32:01.454278+0800 应用加载时间的优化[7570:1360370] 3---<NSThread: 0x600000075ac0>{number = 1, name = main}
2018-04-10 19:32:01.454655+0800 应用加载时间的优化[7570:1360370] group---end

小结:异步并发执行组一内职分和组二内职责,使用 dispatch_group_notify
监测以上两职务成功后回主线程进入 block
内执行任务三。全程异步,不卡线程。

dispatch_group_wait

停顿当前线程(阻塞当前线程),等待指定的 group
中的任务履行到位后,才会往下继续执行。
代码:

- (void)groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"group---begin");
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        // 追加任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0), ^{
        // 追加任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        }
    });
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"group---end");
}

log:

2018-04-10 19:49:33.996536+0800 应用加载时间的优化[7766:1383789] currentThread---<NSThread: 0x604000067f40>{number = 1, name = main}
2018-04-10 19:49:33.996684+0800 应用加载时间的优化[7766:1383789] group---begin
2018-04-10 19:49:36.000831+0800 应用加载时间的优化[7766:1383883] 2---<NSThread: 0x60400026ff00>{number = 3, name = (null)}
2018-04-10 19:49:36.000831+0800 应用加载时间的优化[7766:1383886] 1---<NSThread: 0x60000026ffc0>{number = 4, name = (null)}
2018-04-10 19:49:38.001697+0800 应用加载时间的优化[7766:1383883] 2---<NSThread: 0x60400026ff00>{number = 3, name = (null)}
2018-04-10 19:49:38.001987+0800 应用加载时间的优化[7766:1383886] 1---<NSThread: 0x60000026ffc0>{number = 4, name = (null)}
2018-04-10 19:49:38.003862+0800 应用加载时间的优化[7766:1383789] group---end

总结:使用
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);卡线程来等待 group
内容全方位履行完事后持续上面义务,达成同台。假使去掉
dispatch_group_wait则不卡线程,第一时间执行 group---end,然后实施
group 中情节。

dispatch_group_enter、dispatch_group_leave

void dispatch_group_enter(dispatch_group_t group);
用来添加对应义务组中的未执行达成的任务数,执行几回,未进行完成的天职数加1,当未履行已毕职责数为0的时候,才会使
dispatch_group_wait 解除阻塞和dispatch_group_notify 的 block执行。
void dispatch_group_leave(dispatch_group_t group);
用来缩短职务组中的未执行已毕的职务数,执行三回,未进行完成的天职数减1,dispatch_group_enter
和 dispatch_group_leave 要合作,不然系统会认为 group
任务没有实施已毕。
代码:

- (void)groupEnterAndLeave{
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"group---begin");
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务1
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        }
        dispatch_group_leave(group);
    });
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任务2
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);// 打印当前线程
        }
        dispatch_group_leave(group);
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的异步操作都执行完毕后,回到主线程.
        for(int i =0; i <2; ++i) {
            [NSThread sleepForTimeInterval:2];// 模拟耗时操作
            NSLog(@"3---%@",[NSThread currentThread]);// 打印当前线程
        }NSLog(@"group---end");
    });//
    // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)
    //    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);//
    NSLog(@"group---end"); 
}

log:

2018-04-10 20:05:44.440633+0800 应用加载时间的优化[7796:1394822] currentThread---<NSThread: 0x60000007f000>{number = 1, name = main}
2018-04-10 20:05:44.440802+0800 应用加载时间的优化[7796:1394822] group---begin
2018-04-10 20:05:44.441216+0800 应用加载时间的优化[7796:1394822] group---end
2018-04-10 20:05:46.443538+0800 应用加载时间的优化[7796:1394930] 2---<NSThread: 0x600000474440>{number = 3, name = (null)}
2018-04-10 20:05:46.443549+0800 应用加载时间的优化[7796:1394928] 1---<NSThread: 0x60400026a580>{number = 4, name = (null)}
2018-04-10 20:05:48.445104+0800 应用加载时间的优化[7796:1394930] 2---<NSThread: 0x600000474440>{number = 3, name = (null)}
2018-04-10 20:05:48.445101+0800 应用加载时间的优化[7796:1394928] 1---<NSThread: 0x60400026a580>{number = 4, name = (null)}
2018-04-10 20:05:50.445690+0800 应用加载时间的优化[7796:1394822] 3---<NSThread: 0x60000007f000>{number = 1, name = main}
2018-04-10 20:05:52.447481+0800 应用加载时间的优化[7796:1394822] 3---<NSThread: 0x60000007f000>{number = 1, name = main}
2018-04-10 20:05:52.447792+0800 应用加载时间的优化[7796:1394822] group---end

总结:等同于 dispatch_group_async

信号量:dispatch_semaphore

GCD 中的信号量是指 Dispatch
Semaphore,是负有计数的信号。类似于过高速路收费站的栏杆。可以因此时,打开栏杆,不得以通过时,关闭栏杆。在Dispatch
Semaphore中,使用计数来形成那么些意义,计数为0时守候,不可通过。计数为1或超出1时,计数减1且不等待,可通过。

Dispatch Semaphore提供了多个函数。

  • dispatch_semaphore_create:创设一个Semaphore并开端化信号的总量
  • dispatch_semaphore_signal:发送一个信号,让信号总量加1
  • dispatch_semaphore_wait:能够使总信号量减1,当信号总量为0时就会直接等候(阻塞所在线程),否则就足以健康执行。

只顾:信号量的选择前提是:想清楚你必要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后利用信号量。

Dispatch Semaphore 在事实上支付中重点用以:
1.维持线程同步,将异步执行职责转换为联合实施义务
2.担保线程安全,为线程加锁

Dispatch Semaphore 线程同步

俺们在付出中,会遇见这么的急需:异步执行耗时义务,并运用异步执行的结果开展局地外加的操作。换句话说,约等于,将将异步执行任务转换为联合实施义务。比如说:AFNetworking
中 AFURLSessionManager.m
里面的tasksForKeyPath:方法。通过引入信号量的法子,等待异步执行职分结果,获取到
tasks,然后再回来该 tasks。
代码:

- (void)semaphoreSync {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"semaphore---begin");
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任务1
        [NSThread sleepForTimeInterval:2];// 模拟耗时操作
        NSLog(@"1---%@",[NSThread currentThread]);// 打印当前线程
        number = 100;
        dispatch_semaphore_signal(semaphore);
    });
    NSLog(@"main task continue");
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}

log:

2018-04-10 21:58:57.974515+0800 应用加载时间的优化[8026:1443631] currentThread---<NSThread: 0x600000073900>{number = 1, name = main}
2018-04-10 21:58:57.974641+0800 应用加载时间的优化[8026:1443631] semaphore---begin
2018-04-10 21:58:57.974790+0800 应用加载时间的优化[8026:1443631] main task continue
2018-04-10 21:58:59.978823+0800 应用加载时间的优化[8026:1443709] 1---<NSThread: 0x604000269240>{number = 3, name = (null)}
2018-04-10 21:58:59.979016+0800 应用加载时间的优化[8026:1443631] semaphore---end,number = 100

小结:使用 semaphore 落成线程同步。可以在添加
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
此前已毕主线程异步并发操作,在随后进展线程同步,等待子线程职分已毕后实施后续操作。

线程安全和线程同步(为线程加锁)

线程安全:
即使您的代码所在的长河中有三个线程在同时运转,而那个线程可能会同时运行那段代码。如若老是运行结果和单线程运行的结果是相同的,而且其余的变量的值也和预期的是同一的,就是线程安全的。
若每个线程中对全局变量、静态变量唯有读操作,而无写操作,一般的话,这几个全局变量是线程安全的;若有几个线程同时执行写操作(更改变量),一般都急需考虑线程同步,否则的话就可能影响线程安全。

线程同步:
可清楚为线程 A 和 线程 B 一块合营,A 执行到自然水准时要信赖线程 B
的某部结果,于是停下来,示意 B 运行;B 依言执行,再将结果给 A;A
再持续操作。
举个大约例子就是:两人在联名聊天。多人无法而且说道,防止听不清(操作争执)。等一个人说完(一个线程甘休操作),另一个加以(另一个线程再起初操作)。

以领票为例:
非线程安全:(不拔取 semaphore):
代码:

- (void)initTicketStatusNotSafe {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"semaphore---begin");
    self.ticketSurplusCount = 50;
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    __weak typeof(self) weakSelf =self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketNotSafe];
    });
    dispatch_async(queue2, ^{
        [weakSelf saleTicketNotSafe];
    });
}

- (void)saleTicketNotSafe
{
    while(1) {
        if(self.ticketSurplusCount >0) {
            //如果还有票,继续售卖
            self.ticketSurplusCount--;
            NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@",self.ticketSurplusCount,[NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }else{
            //如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            break;
        }
    }
}

Log:

2018-04-10 22:25:01.047999+0800 应用加载时间的优化[8218:1472786] currentThread---<NSThread: 0x604000064880>{number = 1, name = main}
2018-04-10 22:25:01.048150+0800 应用加载时间的优化[8218:1472786] semaphore---begin
2018-04-10 22:25:01.048378+0800 应用加载时间的优化[8218:1472878] 剩余票数:48 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:01.048387+0800 应用加载时间的优化[8218:1472892] 剩余票数:49 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:01.249347+0800 应用加载时间的优化[8218:1472878] 剩余票数:47 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:01.249354+0800 应用加载时间的优化[8218:1472892] 剩余票数:46 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:01.450904+0800 应用加载时间的优化[8218:1472878] 剩余票数:45 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:01.450904+0800 应用加载时间的优化[8218:1472892] 剩余票数:45 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:01.651358+0800 应用加载时间的优化[8218:1472892] 剩余票数:44 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:01.651358+0800 应用加载时间的优化[8218:1472878] 剩余票数:44 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
.
.
.
2018-04-10 22:25:07.753135+0800 应用加载时间的优化
[8218:1472878] 剩余票数:2 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:07.753135+0800 应用加载时间的优化[8218:1472892] 剩余票数:2 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}
2018-04-10 22:25:07.958573+0800 应用加载时间的优化[8218:1472878] 剩余票数:1 窗口:<NSThread: 0x6000002651c0>{number = 4, name = (null)}
2018-04-10 22:25:07.958574+0800 应用加载时间的优化[8218:1472892] 剩余票数:0 窗口:<NSThread: 0x604000272800>{number = 3, name = (null)}

小结:剩余票数错乱,且重新,存在三个线程同时做客的情况,也就说存在一样张票卖四次的情景。

线程安全:(使用 semaphore 加锁):
使用 semaphoreLock = dispatch_semaphore_create(1); 创建锁
使用 dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
加锁
使用 dispatch_semaphore_signal(semaphoreLock); 解锁
用加锁和平解决锁包括住可能会被八线程同时执行修改的代码。
代码:

static dispatch_semaphore_t semaphoreLock;

- (void)initTicketStatusSafe {
    NSLog(@"currentThread---%@",[NSThread currentThread]);// 打印当前线程
    NSLog(@"semaphore---begin");
    //value表示最大线程同时访问数,此处为1,更改会出问题
    semaphoreLock = dispatch_semaphore_create(1);
    self.ticketSurplusCount =50;
    // queue1 代表北京火车票售卖窗口
    dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
    // queue2 代表上海火车票售卖窗口
    dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
    __weak typeof(self) weakSelf = self;
    dispatch_async(queue1, ^{
        [weakSelf saleTicketSafe];
    });dispatch_async(queue2, ^{
        [weakSelf saleTicketSafe];
    });
}

- (void)saleTicketSafe {
    while(1) {
        // 相当于加锁
        dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
        if(self.ticketSurplusCount >0) {
            //如果还有票,继续售卖
            self.ticketSurplusCount--;NSLog(@"%@", [NSString stringWithFormat:@"剩余票数:%d 窗口:%@",self.ticketSurplusCount, [NSThread currentThread]]);
            [NSThread sleepForTimeInterval:0.2];
        }else{
            //如果已卖完,关闭售票窗口
            NSLog(@"所有火车票均已售完");
            // 相当于解锁
            dispatch_semaphore_signal(semaphoreLock);
            break;
        }// 相当于解锁
        dispatch_semaphore_signal(semaphoreLock);
    }
}

log:

2018-04-10 22:41:43.466847+0800 应用加载时间的优化[8424:1495856] currentThread---<NSThread: 0x60000007b700>{number = 1, name = main}
2018-04-10 22:41:43.466991+0800 应用加载时间的优化[8424:1495856] semaphore---begin
2018-04-10 22:41:43.467215+0800 应用加载时间的优化[8424:1495959] 剩余票数:49 窗口:<NSThread: 0x60400046e840>{number = 3, name = (null)}
2018-04-10 22:41:43.671865+0800 应用加载时间的优化[8424:1495961] 剩余票数:48 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
2018-04-10 22:41:43.874748+0800 应用加载时间的优化[8424:1495959] 剩余票数:47 窗口:<NSThread: 0x60400046e840>{number = 3, name = (null)}
2018-04-10 22:41:44.078812+0800 应用加载时间的优化[8424:1495961] 剩余票数:46 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
2018-04-10 22:41:44.280894+0800 应用加载时间的优化[8424:1495959] 剩余票数:45 窗口:<NSThread: 0x60400046e840>{number = 3, name = (null)}
2018-04-10 22:41:44.483298+0800 应用加载时间的优化[8424:1495961] 剩余票数:44 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
2018-04-10 22:41:44.686228+0800 应用加载时间的优化[8424:1495959] 剩余票数:43 窗口:<NSThread: 0x60400046e840>{number = 3, name = (null)}
2018-04-10 22:41:44.890615+0800 应用加载时间的优化[8424:1495961] 剩余票数:42 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
.
.
.
2018-04-10 22:41:53.450842+0800 应用加载时间的优化[8424:1495961] 剩余票数:0 窗口:<NSThread: 0x600000270840>{number = 4, name = (null)}
2018-04-10 22:41:53.656188+0800 应用加载时间的优化[8424:1495959] 所有火车票均已售完
2018-04-10 22:41:53.656569+0800 应用加载时间的优化[8424:1495961] 所有火车票均已售完

总括:可以看出,在设想了线程安全的情事下,使用 dispatch_semaphore
机制之后,获得的票数是不易的,没有出现紊乱的意况。大家也就化解了五个线程同步的题材。那也就是线程锁的紧要