iOS-GCD

一、GCD的API

1. Dispatch queue

在执行处理时存在两种Dispatch queue:

  • 等待现在执行中任务结束的Serial Dispatch queue
  • 不等待现在执行中任务结束的Concurrent Dispatch queue
  1. Serial Dispatch queue:一个Serial Dispatch queue只生成并使用一个线程,顺序执行。
  2. Concurrent Dispatch queue:使用多个线程同时执行多个处理。XNU内核决定当前应当使用的线程数。

2. 如何生成Serial Dispatch queue和Concurrent Dispatch queue

1. dispatch_queue_create
  1. 第一种方式是通过dispatch_queue_create函数可生成Dispatch queue:
//Serial Dispatch queue
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.test.serialDispatchQueue", NULL);
    
//Concurrent Dispatch queue
dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("com.test.concurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
  • 第一个参数指定Dispatch queue的名称,推荐使用应用程序ID这种逆序全域名,该名称会在Xcode和Instruments的调试器中作为Dispatch queue名称表示,另外也会出现的CrashLog中。如果嫌麻烦可以设为NULL,但调试中你一定会后悔没有署名。
  • 第二个参数设置为NULL,生成Serial Dispatch queue;设置为DISPATCH_QUEUE_CONCURRENT,生成Concurrent Dispatch queue。
  1. 只在为了避免多个线程更新相同资源导致数据竞争时使用Serial Dispatch queue。当使用create方法生成Serial Dispatch queue时,每生成一个Serial Dispatch queue,就会生成一个线程。如果过多使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度的降低系统的响应性能。所以不能激动几下大量生成Serial Dispatch queue。
    当想并行执行并且不会发生数据竞争等问题时使用Concurrent Dispatch queue。对于Concurrent Dispatch queue来说,不管生成多少个,由于XNU内核只使用有效管理的线程,因此不会发生Serial Dispatch queue那些问题。
  2. 由于Dispatch queue并没有像Block那样具有作为oc对象来处理的技术,所以通过dispatch_queue_create函数生成的Dispatch queue在使用结束后,需要通过dispatch_release函数来释放。不过ARC下现在已经不用处理,从iOS几开始?待查明。
2. Main Dispatch Queue/Global Dispatch Queue

第二种方法是获取系统标准提供的Dispatch queue:Main Dispatch Queue和Global Dispatch Queue。

  • Main Dispatch Queue,是在主线程中执行的Dispatch Queue。因为主线程只有一个,所以它自然也就是Serial Dispatch queue。
    追加到Main Dispatch Queue的处理在主线程的RunLoop中执行。因此要将用户界面的界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue使用。
  • Global Dispatch Queue是整个应用程序都能使用的Concurrent Dispatch queue。Global Dispatch Queue有4个执行优先级,分别是高优先级、默认优先级、低优先级、后台优先级。

具体生成代码如下:

dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
    
    dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

3. dispatch_set_target_queue

每一个你创建的队列都必须有一个目标队列。默认情况下, 是优先级为DISPATCH_QUEUE_PRIORITY_DEFAULT的全局并发队列。
拥有一个目标队列对一个普通的队列来说有什么意义呢?答案可能有点令人意外:队列里每一个准备好要执行的block,将会被重新加入到这个队列的目标队列里去执行。
但是等一下,我们不是一直假设block就在其所在的队列里执行吗?难道这都是骗人的吗?
也不见得。因为所有新建的的队列会把默认优先级的全局并发队列当做其目标队列,所以不管哪一个队列上,任何一个准备好将要执行的block基本上都会立即执行。除非你改变队列的目标队列,否则这些block看起来就是在你的队列中执行的。

你的队列继承了其目标队列的优先级。将你的队列的目标队列改为更高或更低优先级的全局并发队列,能有效的改变你的队列的优先级。

只有全局并发队列和主队列才能执行block。其他所有的队列最终都必须设置其中一个为它的目标队列。

上面的引用出自:【译】GCD目标队列(GCD Target Queues)

为此做了下测试:

dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行队列
    dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL);//串行队列
    dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL);//串行队列
    
    dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);//并发队列
    dispatch_set_target_queue(queue1, globalQueueHigh);
    dispatch_set_target_queue(queue2, globalQueueHigh);
    dispatch_set_target_queue(queue3, globalQueueHigh);
    
    
    dispatch_async(queue1, ^{
        NSLog(@"queue1job1 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"queue1job1 out");
    });
    dispatch_async(queue1, ^{
        NSLog(@"queue1job2 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"queue1job2 out");
    });
    dispatch_async(queue1, ^{
        NSLog(@"queue1job3 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"queue1job3 out");
    });
    
    dispatch_async(queue2, ^{
        NSLog(@"queue2job1 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"queue2job1 out");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2job2 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"queue2job2 out");
    });
    dispatch_async(queue2, ^{
        NSLog(@"queue2job3 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"queue2job3 out");
    });
    
    dispatch_async(queue3, ^{
        NSLog(@"queue3job1 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"queue3job1 out");
    });
    dispatch_async(queue3, ^{
        NSLog(@"queue3job2 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"queue3job2 out");
    });
    dispatch_async(queue3, ^{
        NSLog(@"queue3job3 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"queue3job3 out");
    });

输出:

2017-09-07 10:55:16.208 GCDTest[43628:4071278] queue1job1 in
2017-09-07 10:55:16.208 GCDTest[43628:4071276] queue3job1 in
2017-09-07 10:55:16.208 GCDTest[43628:4071290] queue2job1 in
2017-09-07 10:55:18.211 GCDTest[43628:4071278] queue1job1 out
2017-09-07 10:55:18.211 GCDTest[43628:4071290] queue2job1 out
2017-09-07 10:55:18.211 GCDTest[43628:4071276] queue3job1 out
2017-09-07 10:55:18.211 GCDTest[43628:4071278] queue1job2 in
2017-09-07 10:55:18.211 GCDTest[43628:4071290] queue2job2 in
2017-09-07 10:55:18.211 GCDTest[43628:4071276] queue3job2 in
2017-09-07 10:55:19.212 GCDTest[43628:4071276] queue3job2 out
2017-09-07 10:55:19.212 GCDTest[43628:4071290] queue2job2 out
2017-09-07 10:55:19.212 GCDTest[43628:4071278] queue1job2 out
2017-09-07 10:55:19.213 GCDTest[43628:4071276] queue3job3 in
2017-09-07 10:55:19.213 GCDTest[43628:4071290] queue2job3 in
2017-09-07 10:55:19.213 GCDTest[43628:4071278] queue1job3 in
2017-09-07 10:55:22.216 GCDTest[43628:4071276] queue3job3 out
2017-09-07 10:55:22.216 GCDTest[43628:4071278] queue1job3 out
2017-09-07 10:55:22.216 GCDTest[43628:4071290] queue2job3 out

经过实验与思考之后,我觉得可以这样理解(如果不正确,请留言指正,多谢):

  • 如果这些队列的目标队列是并发队列,那么队列在注入到目标队列的时候,这些队列之间还是并发的,具体每个队列内部是并发还是顺序执行保持不变。可以看出,这些队列在重新指定目标队列为全局并发队列之后,队列之间的关系并没有改变,仍然是并发,因为默认的他们的目标队列就是Concurrent Dispatch Queue,只是可以改变优先级而已,个人觉得重新设置目标队列为全局并发队列的情况不多。
  • 如果这些队列的目标队列是Serial Dispatch Queue,那么这些队列在注入到目标队列的时候,所有队列中的任务都会按注入队列的先后顺序同步执行(即同时只能执行一个任务)。

理解了目标队列,我们看看怎么使用的:

  1. 通过dispatch_queue_create函数生成Dispatch Queue不管是Serial Dispatch Qeueu还是Concurrent Dispatch Queue,默认的优先级都是Global Dispatch Queue默认的优先级。如果想改变通过create生成Dispatch queue,通过dispatch_set_target_queue来指定与哪个Global Dispatch Queue的优先级一样:
    dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行队列
    dispatch_queue_t globalQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);//并发队列
    dispatch_set_target_queue(queue, globalQueueHigh);

代码中,指定了queue,与globalQueueHigh的优先级一样。

  1. dispatch_set_target_queue除了能用来设置队列的优先级之外,还能够创建队列的层次体系。当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的target指向新创建的队列,那么原本应该并行执行的多个队列在目标队列上开始同步执行,即同时只能执行一个任务。
dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目标队列
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行队列
    dispatch_queue_t queue2 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并发队列
    //设置参考
    dispatch_set_target_queue(queue1, targetQueue);
    dispatch_set_target_queue(queue2, targetQueue);

    dispatch_async(queue2, ^{
        NSLog(@"job3 in");
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"job3 out");
    });
    dispatch_async(queue2, ^{
        NSLog(@"job2 in");
        [NSThread sleepForTimeInterval:1.f];
        NSLog(@"job2 out");
    });
    dispatch_async(queue1, ^{
        NSLog(@"job1 in");
        [NSThread sleepForTimeInterval:3.f];
        NSLog(@"job1 out");
    });

输出结果:

2017-09-06 22:58:34.998 testMethodSwizzling[6748:2156619] job3 out
2017-09-06 22:58:34.998 testMethodSwizzling[6748:2156619] job2 in
2017-09-06 22:58:36.004 testMethodSwizzling[6748:2156619] job2 out
2017-09-06 22:58:36.005 testMethodSwizzling[6748:2156619] job1 in
2017-09-06 22:58:39.010 testMethodSwizzling[6748:2156619] job1 out

注意:Main Dispatch Queue/Global Dispatch Queue均不可用该函数指定。

4. dispatch_after

该函数并不是在指定的时候后执行任务,而是在指定的时间后追加任务到队列中。

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"3秒时间到");
    });
  • 第一个参数为dispatch_time_t类型。我们可以使用dispatch_time和dispatch_walltime函数,前者是用于计算相对时间,如距现在3秒;后者用于计算绝对时间,如2020年1月1日0时0分0秒。

5. Dispatch Group

在追加到Dispatch Queue中的多个处理结束后想执行结束处理,如果使用的是Serial Dispatch Queue,只需要在最后追加结束处理即可,但是如果使用的是Concurrent Dispatch Queue,就需要用到Dispatch Group
无论向什么Dispatch Queue中追加处理,使用Dispatch Group都可以监视这些执行的结束。

  1. dispatch_group_notify
    使用如下:
    dispatch_queue_t concurrentDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, concurrentDispatchQueue, ^{
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"1");
    });
    dispatch_group_async(group, concurrentDispatchQueue, ^{
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"2");
    });
    dispatch_group_async(group, concurrentDispatchQueue, ^{
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"3");
    });
    
    //追加结束处理
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"done");
    });
    
    dispatch_group_async(group, concurrentDispatchQueue, ^{
        [NSThread sleepForTimeInterval:2.f];
        NSLog(@"4");
    });
2017-09-07 15:05:28.812 GCDTest[44028:4144855] 3
2017-09-07 15:05:28.812 GCDTest[44028:4144874] 2
2017-09-07 15:05:28.812 GCDTest[44028:4144856] 4
2017-09-07 15:05:28.812 GCDTest[44028:4144858] 1
2017-09-07 15:05:28.813 GCDTest[44028:4144767] done

在追加到Dispatch Group中的处理全部执行结束时,该代码中使用的dispatch_group_notify函数会将执行的Block追加到指定的Dispatch Queue中。可以看出即使在notify之后追加的,也一样,这一点与barrier要区分开来。

  1. dispatch_group_wait
    在Dispatch Group中也可以使用dispatch_group_wait函数,它会一直等待,直到指定的时间结束,或Dispatch Group中的处理全部执行完毕。
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

上边代码中,会一直等待到Dispatch Group中的处理执行结束,因为时间设置的是永远。“等待”的意思是该函数一直处于调用状态不返回,当前线程停止。

该函数的返回值:

  • 返回值等于0,表示Dispatch Group处理全部执行结束
  • 返回值不等于0,表示是Dispatch Group没有执行结束,是由于时间到了导致的结束*。

我们可以使用dispatch_group_wait(group, DISPATCH_TIME_NOW);不用任何等待,即可判断Dispatch Group执行是否结束。

6. dispatch_barrier_async

场景:
在访问数据库或文件时,如前所述,使用Serial Dispatch Queue可避免数据竞争问题。
写入操作的确不可以与其他写入操作以及包含读取处理的其他处理并行执行,但是如果读取处理只是与读取处理并行执行,那么多个并行执行就不会发生问题。也就是说读取处理追加到Concurrent Dispatch Queue中,写入处理在任何一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中即可(在写入处理结束之前,读取处理不可执行)。

dispatch_barrier_async函数与Concurrent Dispatch Queue一起使用:dispatch_barrier_async会等待之前追加到Concurrent Dispatch Queue中的执行全部执行完毕,然后再追加自己的处理到Concurrent Dispatch Queue中,等待dispatch_barrier_async追加的处理执行完毕后,再恢复Concurrent Dispatch Queue中的执行,之后的处理又开始并行执行。

代码如下:

    dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"read1");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"read2");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"read3");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"read4");
    });
    dispatch_barrier_async(concurrentDispatchQueue, ^{
        NSLog(@"write-barrier");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"read5");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"read6");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"read7");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"read8");
    });

输出结果:

2017-09-07 14:53:02.920 GCDTest[43953:4135868] read1
2017-09-07 14:53:02.920 GCDTest[43953:4135849] read2
2017-09-07 14:53:02.920 GCDTest[43953:4135869] read4
2017-09-07 14:53:02.920 GCDTest[43953:4135851] read3
2017-09-07 14:53:02.921 GCDTest[43953:4135851] write-barrier
2017-09-07 14:53:02.922 GCDTest[43953:4135869] read5
2017-09-07 14:53:02.922 GCDTest[43953:4135868] read7
2017-09-07 14:53:02.922 GCDTest[43953:4135851] read8
2017-09-07 14:53:02.922 GCDTest[43953:4135849] read6

使用Concurrent Dispatch Queue和dispatch_barrier_async函数可实现高效率的数据库访问和文件访问。

7. dispatch_(a)sync

  1. dispatch_async函数的async意味着非同步(asynchronous),就是将制定的Block非同步的追加到Dispatch Queue中,dispatch_async不做任何等待。
  2. dispatch_sync函数,将指定的Block“同步”的追加到指定Dispatch Queue中,该Block处理执行结束之前,该函数不会返回,即阻塞该线程。也可以说是简易版的dispatch_group_wait函数。
    正是因为dispatch_sync简单,所以也容易引起问题,死锁。
dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"死锁了呀");
    });

dispatch_sync会阻塞当前线程,直到后边的Block任务执行结束,而Block任务恰恰是在主线程中执行的被阻塞了,永远不会执行结束。

所以在使用dispatch_sync时一定要深思熟虑,稍有不慎就会导致程序死锁。

  1. dispatch_barrier_async和dispatch_barrier_sync的区别,为了更直观,我们来看一段代码:
dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"1");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"2");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"3");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"4");
    });
    dispatch_barrier_async(concurrentDispatchQueue, ^{
        NSLog(@"barrier");
    });
    NSLog(@"aaaaa");
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"5");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"6");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"7");
    });
    dispatch_async(concurrentDispatchQueue, ^{
        NSLog(@"8");
    });

dispatch_barrier_async和dispatch_barrier_sync

  • 相同点:都是等待barrier之前的任务都执行完,再执行自己追加的任务,都是等待自己追加的任务结束之后,再执行barrier之后追加的任务。
  • 不同点:async在追加了自己的任务之后马上会执行之后的追加任务的代码,也就是代码中的“aaaa”会紧接着打印出来;
    而sync在追加了自己的任务之后,会等待在那里,直到自己追加的任务结束后再,执行后边的追加代码,所以“aaaa”一定会在“barrier”之后打印出来。我个人感觉它们在使用的时候,对于concurrentDispatchQueue内的处理没有影响,只会因为阻塞了当前线程而对队列外的其他处理造成影响。

8. dispatch_apply

  1. dispatch_apply函数是dispatch_sync和Dispatch Group的关联API。该函数按指定的次数,将指定Block追加到指定的Dispatch Queue中,并等待任务执行结束。
    dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"%zu",index);
    });
    NSLog(@"done");

输出:

2017-09-07 16:46:00.556 GCDTest[44379:4211879] 1
2017-09-07 16:46:00.556 GCDTest[44379:4211881] 2
2017-09-07 16:46:00.555 GCDTest[44379:4211742] 0
2017-09-07 16:46:00.556 GCDTest[44379:4211864] 3
2017-09-07 16:46:00.556 GCDTest[44379:4211879] 4
2017-09-07 16:46:00.556 GCDTest[44379:4211742] done

可以看出,dispatch_apply会阻塞直到所有任务都完成。

  1. 我们看到dispatch_apply和dispatch_sync一样都会等待处理执行结束,这样总是不好的,推荐在dispatch_async函数中非同步的执行dispatch_apply。
dispatch_queue_t queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        dispatch_apply(5, queue, ^(size_t index) {
            NSLog(@"%zu:do something",index);
        });
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"更新ui");
        });
    });

9. dispatch_suspend/dispatch_resume

  • 使用dispatch_suspend函数挂起指定的Dispatch Queue
  • 使用dispatch_resume函数恢复指定的Dispatch Queue

这两个函数对已经执行的处理没有影响。挂起后,Dispatch Queue中尚未执行的处理在此之后停止执行,而恢复则使这些处理能够继续执行。

10. Dispatch Semaphore

当并行执行的处理更新数据时,会产生数据不一致的情况,有时应用程序还会异常结束。虽然使用Serial Dispatch Queue和dispatch_barrier_async函数可以避免这类问题,但有必要进行更细粒度的排他控制。
先看一段代码:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSMutableArray *array = [[NSMutableArray alloc] init];
    
    for (int i=0; i<5000; i++) {
        dispatch_async(queue, ^{
            [array addObject:@(i)];
        });
    }

这一段代码,使用Global Dispatch Queue更新,由于内存错误很容易导致程序异常结束。此时应使用Dispatch Semaphore。
那么,什么是Dispatch Semaphore?
Dispatch Semaphore是持有计数的信号。整个思路是,计数为0时等待,计数为1或大于1时,减去1停止等待,开始执行处理,处理结束时将计数加1。

  1. dispatch_semaphore_create
    dispatch_semaphore_t semephore = dispatch_semaphore_create(1);
    通过该函数生成Dispatch Semaphore。
    参数标识计数的初始值。
  2. dispatch_semaphore_wait
    dispatch_semaphore_wait(semephore, DISPATCH_TIME_FOREVER);
    该函数会一直等待,直到Semaphore的计数值大于等于1,或到达设置的时间。如果semephore计数值大于等于1,对计数值减1,并且该函数返回。该函数的返回值与dispatch_group_wait类似,0表示由于计数大于等于1导致的返回,非0表示时间到了导致的返回。
  3. dispatch_semaphore_signal
    dispatch_semaphore_signal(semephore);
    通过该函数将semephore的计数值加1.
    完整的代码为:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSMutableArray *array = [[NSMutableArray alloc] init];
    dispatch_semaphore_t semephore = dispatch_semaphore_create(1);
    
    for (int i=0; i<5000; i++) {
        dispatch_async(queue, ^{
            dispatch_semaphore_wait(semephore, DISPATCH_TIME_FOREVER);
            [array addObject:@(i)];
            dispatch_semaphore_signal(semephore);
        });
    }

在没有Serial Dispatch Queue和dispatch_barrier_async函数那么大粒度且一部分处理需要进行排他控制的情况下,Dispatch Semaphore便可发挥威力。

11. dispatch_once

dispatch_once函数是保证在应用程序中只执行一次指定处理的API。

static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
    });

即使在多线程环境下,也可保证百分百安全。

12. Dispatch I/O

在读取较大文件时,将文件分成合适的大小并使用Global Dispatch Queue并列读取,会比一般的读取速度快不少。
能实现这一功能的就是Dispatch I/O和Dispatch Data了。
分割读取的数据通过使用Dispatch Data可更为简单的进行结合和分割。

13. 为什么废弃dispatch_get_current_queue

  1. 该方法用于获取当前队列,iOS6.0已废弃。为什么呢?
    我们在使用dispatch_sync时一不小心就会导致死锁:
dispatch_sync(syncQueue, ^{
        NSLog(@"若当前队列就是串行syncQueue,死锁了呀");
    });

如果当前队列就是串行syncQueue,就会导致死锁,所以我们经常会通过dispatch_get_current_queue来判断目标队列是不是当前队列。
这种做法可以处理简单的情况,但是仍有死锁的危险,比如,有两个串行队列:

dispatch_queue_t queueA = dispatch_queue_create("queueA", DISPATCH_QUEUE_SERIAL);//串行队列
    dispatch_queue_t queueB = dispatch_queue_create("queueB", DISPATCH_QUEUE_SERIAL);//串行队列
    dispatch_sync(queueA, ^{
        dispatch_sync(queueB, ^{
            if (dispatch_get_current_queue()!=queueA) {
                dispatch_sync(queueA, ^{
                    NSLog(@"dosomething");
                });
            }
            else
            {
                NSLog(@"dosomething");
            }
            
        });
    });

虽然判断了当前队列不是queueA,但是依然死锁了。
因为最外层的已经阻塞queueA,直到里边的内容执行完毕,而最里边的内容是在queueA上执行的,A已经被阻塞了,又不可能执行完毕,所以导致了死锁。
这么看来使用dispatch_get_current_queue,仍然会有导致死锁的危险。

  1. 那我们使用什么方式来解决这种情况呢?
    上边的例子显得有点做作,那么我们找个正常点的例子:两个串行队列A、B,其中A是B的目标队列,这样队列之间会形成层级体系,B中的任务稍候都会在A中一次执行,于是排在AB中的任务都是在A中串行执行,这时如果在队列B中的块中,判断当前队列不是A那么就认为可以在队列A上执行同步派发操作,实际上会导致死锁。
    解决方案:队列特有数据
    通过dispatch_queue_set_specific可以把任意数据以键值对的形式关联到队列指定队列里。在需要判断当前队列是不是目标队列的时候,然后通过dispatch_get_specific获取,如果能取到说明当前队列就是目标队列,如果没有就说明不是。
dispatch_queue_t queueA = dispatch_queue_create("com.xxx.xxx", NULL);
    static int kQueueSpecificKey = 111111;
    CFStringRef value = CFSTR("queueAValue");
    //给queueA设置队列特有数据
    dispatch_queue_set_specific(queueA, &kQueueSpecificKey, (void *)value, (dispatch_function_t)CFRelease);
    
    //需要判断queueA是不是当前队列的时候,只需要获取队列特有数据,看能否取到之前设置的值即可
    CFStringRef retrievedValue = dispatch_get_specific(&kQueueSpecificKey);
    if (retrievedValue) {
        //当前队列就是queueA,因为能取到队列特有数据
    }
    else
    {
        //当前队列不是queueA,因为没有取到队列特有数据
    }

二、典型例子

只要是调用dispatch_sync同步执行任务,不管是提交并发队列还是串行队列,都是在当前线程上执行。

1.
- (void)viewDidLoad {
    dispatch_sync(dispatch_get_main_queue(), ^{
        [self dosomething];
    });
}

这种情况会导致死锁,但是一定要知道,是由队列引起的循环等待,而不是由线程引起的

死锁原因
2.
- (void)viewDidLoad {
    dispatch_sync(serial_queue, ^{
        [self dosomething];
    });
}

这种情况不会引起死锁。


屏幕快照 2018-04-15 下午1.35.08.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容

  • 简介 GCD(Grand Central Dispatch)是在macOS10.6提出来的,后来在iOS4.0被引...
    sunmumu1222阅读 858评论 0 2
  • 一. 重点: 1.dispatch_queue_create(生成Dispatch Queue) 2.Main D...
    BestJoker阅读 1,582评论 2 2
  • GCD笔记 总结一下多线程部分,最强大的无疑是GCD,那么先从这一块部分讲起. Dispatch Queue的种类...
    jins_1990阅读 757评论 0 1
  • Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的...
    韩七夏阅读 287评论 0 0
  • 简介 GCD(Grand Central Dispatch)是在macOS10.6提出来的,后来在iOS4.0被引...
    JerryLMJ阅读 2,312评论 1 11