Grand Central Dispatch(GCD)

Posted xiaoxiaobukuang

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Grand Central Dispatch(GCD)相关的知识,希望对你有一定的参考价值。

一、Grand Central Dispatch(GCD)概要

1、什么是GCD

Grand Central Dispatch(GCD)是异步执行任务的技术之一。
GCD用我们难以置信的非常简洁的记述方法,实现了极为复杂繁琐的多线程编程。
例如:

dispatch_async(queue, ^{
    //长时间处理
    //例如AR用动画识别
    //例如数据库访问
    //长时间处理结束,主线程使用该处理结果
    dispatch_async( dispatch_get_main_queue(), ^{
    //只在主线程可以执行的处理
        //例如用户界面更新
    });
});

在NSObject中提供了两个实例方法来实现简单的多线程技术:

  • ①、- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);
  • ②、- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
//NSObject  performSelectorInBackground:withObject:方法中执行后台线程
- (void)launchThreadByNSObject_performSelectorInBackground_withObject {
     [self performSelectorInBackground:@selector(doWork) withObject:nil];
}
//后台线程处理方法
- (void)doWork {
     @autoreleasepool{
          //长时间处理,   例如AR用画像识别    例如数据库访问
          //长时间处理结束,主线程使用其处理结果
          [self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
     }
}
//主线程处理方法
- (void)doneWork {
     //只在主线程可以执行的处理
     //例如用户界面更新
}

2、多线程编程

上下文切换:CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原cpu寄存器等信息,继续执行切换路径的CPU命令列;

问题:使用多线程的程序可以在某个线程和其他线程之间反复多次进行上下文切换,但是多线程编程实际上是一种易发生各种问题的编程技术。比如多个线程更新相同的资源会导致数据的不一致(数据竞争)、停止等待事件的线程会导致多个线程相互持续等待(死锁)、使用太多线程会消耗大量内存等。
这里写图片描述

应用程序在启动时,通过最先执行的线程,即『主线程』来描绘用户界面、处理触摸屏幕的事件等。如果在该主线程中进行长时间的处理,如AR用画像的识别或数据库访问,就会妨碍主线程的执行(阻塞)。在OS X和ios的应用程序中,会妨碍主线程中被称为RunLoop的主循环的执行,从而导致不能更新用户页面、应用程序的画面长时间停滞等问题;
这里写图片描述

二、GCD的API

1、Dispatch Queue

苹果官方堆GCD的说明
开发者要做的只是定义想要执行的任务并追加到适当的Dispatch Queue中。

dispatch_async(queue, ^{
    //想执行的任务
});

Dispatch Queue:是执行处理的等待队列。应用程序编程人员通过dispatch_async函数等API,在Block语法中记述想要执行的处理并将其追加到Dispatch Queue中。Dispatch Queue按照追加的顺序(先进先出FIFO,First-In-First-Out)执行处理。

Dispatch Queue执行处理时存在两种队列:

  • (1)、等待现在执行中处理的Serial Dispatch Queue;
  • (2)、不等待现在执行中处理的Concurrent Dispatch Queue;

Dispatch Queue的种类

Dispatch Queue的种类说明
Serial Dispatch Queue等待现在执行中处理的结果
Concurrent Dispatch Queue不等待现在执行中处理的结果

这里写图片描述
注意:不等待处理结束,可以并行执行多个处理,但并行执行的处理数量取决于当前系统的状态。

2、dispatch_queue_create

通过此函数,可生成Dispatch Queue 如:

dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);
dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);

如前所述,Concurrent Dispatch Queue并行执行多个追加处理,而Serial Dispatch Queue同时只能执行一个追加处理。虽然Serial Dispatch Queue和Concurrent Dispatch Queue受到系统资源的限制,但用dispatch_queue_creat函数可生成多个Dispatch Queue。当生成过个Serial Dispatch Queue时,各个Serial Dispatch Queue将并行执行。虽然在1个Serial Dispatch Queue中同时只能执行一个追加处理,但如果将处理分别追加到4个Serial Dispatch Queue中,各个Serial Dispatch Queue执行1个,即为同时执行4个处理。
如图:
这里写图片描述

但是,如果使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能。
这里写图片描述
要注意的是不能多个线程同时更新相同的数据,多线程更新相同资源换导致数据竞争使用Serial Dispatch Queue。当想并行执行不发生数据竞争等问题的处理时,使用Concurrent Dispatch Queue。而且对于Concurrent Dispatch Queue来说,不管生成多少,由于XNU内核只使用有效管理的线程,因此不会发生Serial Dispatch Queue的那些问题。

dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

(1)、第一个参数:const char *label;
创建Serial Dispatch Queue:

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);

指定Dispatch Queue的名称,像此源代码这样,Dispatch Queue的名称推荐使用应用程序ID这样逆序全程域名(FQDN,fully qualified domain name)。该名称在Xcode和Instruments的调试器中作为Dispatch Queue名称表示。另外,该名称也出现在应用程序崩溃时所生成的CrashLog中。我们命名原则:对于我们编程人员来说简单易懂,对于用户来说也要易懂。如果嫌命名麻烦设为NULL也可,但在调试中没有Dispatch Queue署名。
(2)、第二个参数:dispatch_queue_attr_t attr
指定生成的Dispatch Queue种类;

  • ①、创建Serial Dispatch Queue:

将该参数指定为NULL或者DISPATCH_QUEUE_SERIAL;

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
  • ②、创建Concurrent Dispatch Queue:
    将该参数指定为DISPATCH_QUEUE_CONCURRENT;
dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

注意:该函数生成的Dispatch Queue必须由程序员负责释放。这是因为Dispatch Queue并没有像Block那样具有作为Objective-C对象来处理的技术;

dispatch_release函数来释放。

dispatch_release(mySerialDispatchQueue);

dispatch_retain函数来增加计数器

dispatch_retain(mySerialDispatchQueue);

3、Main Dispatch Queue/Global Dispatch Queue

获取Dispatch Queue

(1)、Main Dispatch Queue

Main Dispatch Queue正如其名称中含有『Main』一样,是在主线程中执行的Dispatch Queue。因为主线程只有一个,所以Main Dispatch Queue自然就是 Serial Dispatch Queue。

追加到Main Dispatch Queue的处理在主线程中RunLoop中执行。由于在主线程中执行,因此要将用户界面更新等一些必须在主线程中执行的处理追加到Main Dispatch Queue使用。这与NSObject类的performSelectorOnMainThread实例方法这一执行方法相同。

这里写图片描述

(2)、Global Dispatch Queue

Global Dispatch Queue是所有应用程序都能够使用的Concurrent Dispatch Queue。没有必要通过dispatch_queue_creat函数逐个生成Concurrent Dispatch Queue。只要获取Global Dispatch Queue使用即可。

Dispatch Queue有四个优先级:

  • ①、高优先级(High Priority);
  • ②、默认优先级(Default Priority);
  • ③、低优先级(Low Priority);
  • ④、后台优先级(Background Priority);

通过XNU内核管理的用于Global Dispatch Queue线程, 将各自使用的Global Dispatch Queue的执行优先级作为线程的执行优先级使用。向Global Dispatch Queue追加处理时,应选择与处理内容对应的执行优先级的Global Dispatch Queue。

Dispatch Queue的种类:

名称Dispatch Queue的种类说明
Main Dispatch QueueSerial Dispatch Queue主线程执行
Global Dispatch Queue(High Priority)Concurrent Dispatch Queue执行优先级:高(最高优先)
Global Dispatch Queue(Default Priority)Concurrent Dispatch Queue执行优先级:默认
Global Dispatch Queue(Low Priority)Concurrent Dispatch Queue执行优先级:低
Global Dispatch Queue(Background Priority)Concurrent Dispatch Queue执行优先级:后台

(3)、例子

/**
 * Main Dispatch Queue的获取方法
 */
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
/**
 * Global Dispatch Queue(高优先级)的获取方法
 */
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/**
 * Global Dispatch Queue(默认优先级)的获取方法
 */
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**
 * Global Dispatch Queue(低优先级)的获取方法
 */
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/**
 * Global Dispatch Queue(后台优先级)的获取方法
 */
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);

使用Main Dispatch Queue和Global Dispatch Queue源代码:

/**
 * 在默认优先级的Global Dispatch Queue中执行Block
 */
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    /*
     * 可并行执行的处理
     */
    /**
     * 在Main Dispatch Queue中执行Block
     */
    dispatch_async(dispatch_get_main_queue(), ^{
        /**
         * 只能在主线程中执行的处理
         */
    });
});

4、dispatch_set_target_queue

dispatch_queue_create函数生成的Dispatch Queue不管是Serial Dispatch Queue还是Concurrent Dispatch Queue,都使用与默认优先级Global Dispatch Queue相同的执行优先级的线程。而变更生成的Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数。在后台执行动作处理的Serial Dispatch Queue的生成方法如下:

dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.example.gcd.serialDispatchQueue", NULL);
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(serialDispatchQueue, globalDispatchQueue);
dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
  • (1)、参数一:dispatch_object_t object
    指定变更执行优先级的Dispatch Queue;
  • (2)、参数二:dispatch_queue_t queue
    指定与要使用的执行优先级相同优先级的Global Dispatch Queue;

将Dispatch Queue指定为dispatch_set_target_queue函数的参数,不仅可以变更Dispatch Queue的执行优先级,还可以作为Dispatch Queue的执行阶层。如果在多个Serial Dispatch Queue中用dispatch_set_target_queue函数指定目标为某一个Serial Dispatch Queue,那么原来本应该并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能同时执行一个处理。
这里写图片描述

在必须不可并行执行的处理追加到多个Serial Dispatch Queue中时,如果使用dispatch_set_target_queue函数将目标指定为某一个Serial Dispatch Queue,即可防止处理并行执行。

5、dispatch_after/dispatch_time_t

延迟处理。 3秒如下:

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"waited at least three seconds.");
});
dispatch_after(dispatch_time_t when,
    dispatch_queue_t queue,
    dispatch_block_t block);
  • (1)、第一个参数:dispatch_time_t when

指定时间用的dispatch_time_t类型的值。该值使用dispatch_time函数或者dispatch_walltime函数作成;

dispatch_time能够获取从第一个参数dispatch_time_t类型值中指定的时间开始,到第二个参数只ID那个的毫微秒单位时间后的时间。

dispatch_time函数常用于计算相对时间。

dispatch_time_t
dispatch_time(dispatch_time_t when, int64_t delta);

①、第一个参数:dispatch_time_t when
经常使用DISPATCH_TIME_NOW,这表示现在的时间;
②、第二个参数:int64_t delta
数值和NSEC_PER_SEC的乘积得到单位为毫微秒的数值,『ull』是C语言中的数值字面量,是显示表示类型时使用的字符串(表示unsigned long long)。如果使用NSEC_PER_MSEC则表示可以以毫秒为单位计算。

/**
 * 从现在开始3秒后计算
 */
dispatch_time_t time_01 = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
/**
 * 从现在开始150毫秒后计算
 */
dispatch_time_t time_02 = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);

dispatch_walltime函数由POSIX中使用struct timespec类型的时间得到dispatch_time_t类型的值。

dispatch_walltime用于计算绝对时间。

dispatch_time_t getDispatchTimeByDate(NSDate *date){
    NSTimeInterval interval;
    double second,subsecond;
    struct timespec time;

    interval = [date timeIntervalSince1970];
    /**
     * 分解x,以得到x的整数和小数部分 second整数部分,subsecond小数部分
     */
    subsecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = subsecond*NSEC_PER_SEC;
    dispatch_time_t milestone = dispatch_walltime(&time, 0);
    return milestone;
}
  • (2)、第二个参数:dispatch_queue_t queue
    指定要追加处理的Dispatch Queue;
  • (3)、第三个参数:dispatch_block_t block
    指定记述要执行处理的Block;

6、Dispatch Groud

dispatch_group_create();
dispatch_group_async();
dispatch_group_notify();
dispatch_group_wait();

dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后才通知界面说完成。该组中是并发执行的。例如:

- (void)gcdGroup{
    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, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group1");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"updateUi");
    });
    dispatch_release(group);
}
打印顺序:
2015-03-16 23:33:09.593 GCDDemo[959:49260] group1
2015-03-16 23:33:10.593 GCDDemo[959:49262] group2
2015-03-16 23:33:11.594 GCDDemo[959:49261] group3
2015-03-16 23:33:11.595 GCDDemo[959:49222] updateUi

在Dispatch Group中也可以使用dispatch_group_wait函数仅等待全部处理执行结束;

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, ^{
    [NSThread sleepForTimeInterval:1];
    NSLog(@"group1");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"group2");
});
dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"group3");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"updateUi");
});
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
dispatch_release(group);
long
dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);

第二个参数指定为等待的时间(超时)。它属于dispatch_time_t类型的值。该源代码使用DISPATCH_TIME_FOREVER,意味着永久等待。只要属于Dispatch Group的处理尚未执行结束,就会一直等待,中途不能取消。

dispatch_time_t time_01 = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_group_wait(group, time_01);
if (result == 0) {
    /**
     * 属于Dispatch Group的全部处理执行结束
     */
}else{
    /**
     * 属于Dispatch Group的某一处理还在执行
     */
}

如果dispatch_group_wait函数返回值不为0,就意味着虽然经过了指定的时间,但属于Dispatch Group的某一个处理还在执行中。如果返回值为0,那么全部处理执行结束。当等待时间为DISPATCH_TIME_FOREVER、由dispatch_group_wait函数返回时,由于属于Dispatch Group的处理必定全部执行结束,因此返回值恒为0。

7、dispatch_barrier_async

访问数据库或者文件的时候,使用Serial Dispatch Queue可以避免数据竞争的问题。

为了效率,读取处理可以追加到Concurrent Dispatch Queue中,写入处理在任一个读取处理没有执行的状态下,追加到Serial Dispatch Queue中。也就是说,在写入处理结束之前,读取处理不可执行。

dispatch_barrier_async会等待追加到Concurrent DIspatch Queue上的并行执行的处理全部结束之后,再将指定的处理追加到该Queue中。然后再由dispatch_barrier_async函数追加的处理执行完毕之后,Concurrent Dispatch Queue才恢复为一般的动作,追加到该Queue的处理又开始并行执行。
如图:
这里写图片描述

8、dispatch_sync

(1)、async:非同步,将指定的Block 非同步地追加到指定的Dispatch Queue中, dispatch_async函数不做任何等待,如图:
这里写图片描述
(2)、sync:同步,将指定的Block 同步 追加到指定的Dispatch Queue中, 在追加Block结束之前,dispatch_sync会一直等待,如图:
这里写图片描述

注意死锁:

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
    NSLog(@"HELLO!!!");
});

该源代码在Main Dispatch Queue即主线程中执行指定的Block,并等待其执行结束。而其实在主线程中正在执行这些源代码,所以无法执行追加到Main Dispatch Queue的Block中。

9、dispatch_apply

dispatch_apple函数是dispatch_sync函数和Dispatch Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,并等待全部处理指定结束。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu",index);
});
NSLog(@"done");

打印:

2016-05-26 14:27:38.197 class_01[54636:2641799] 0
2016-05-26 14:27:38.197 class_01[54636:2641879] 1
2016-05-26 14:27:38.197 class_01[54636:2641880] 2
2016-05-26 14:27:38.197 class_01[54636:2641881] 3
2016-05-26 14:27:38.197 class_01[54636:2641799] 4
2016-05-26 14:27:38.197 class_01[54636:2641799] 8
2016-05-26 14:27:38.197 class_01[54636:2641880] 6
2016-05-26 14:27:38.197 class_01[54636:2641881] 7
2016-05-26 14:27:38.197 class_01[54636:2641799] 9
2016-05-26 14:27:38.197 class_01[54636:2641879] 5
2016-05-26 14:27:38.198 class_01[54636:2641799] done

第一个参数为重复次数,第二个参数为追加对象的Dispatch Queue,第三个参数为追加的处理。

由于dispatch_apply函数与dispatch_sync函数相同,会等待处理执行结束,因此推荐在dispatch_async函数中非同步地执行dispatch_apply函数。

NSArray* arr = @[@"0",@"1",@"2",@"3"];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**
 * 在Global Dispatch Queue中非同步执行
 */
dispatch_async(queue, ^{

    /**
     * Global Dispatch queue
     * 等待dispatch_apply函数中全部处理执行结束
     */
    dispatch_apply([arr count], queue, ^(size_t index) {
        /**
         * 并列处理包含在NSArray对象的全部对象
         */
        NSLog(@"%zu:%@",index,arr[index]);
    });
    /**
     * dispatch_apply函数中的处理全部执行结束
     */
    /**
     * 在Main Dispatch Queue中非同步执行
     */
    dispatch_async(dispatch_get_main_queue(), ^{
        /**
         * 在Main Dispatch Queue中执行处理
         */
        NSLog(@"done");
    });

});

打印:

2016-05-26 14:37:21.236 class_01[54939:2650072] 3:3
2016-05-26 14:37:21.236 class_01[54939:2650070] 1:1
2016-05-26 14:37:21.236 class_01[54939:2650071] 2:2
2016-05-26 14:37:21.236 class_01[54939:2650069] 0:0
2016-05-26 14:37:21.240 class_01[54939:2650047] done

10、dispatch_suspend/dispatch_resume

当追加大量处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。例如演算结果被Block截获时,一些处理会对这个演算结果造成影响;
在这种情况下,只要挂起Dispatch Queue即可。当可以执行时再回复。

//dispatch_suspend函数挂起指定的Dispatch Queue
dispatch_suspend(queue);
//dispatch_resume函数恢复指定的Dispatch Queue
dispatch_resume(queue);

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

11、Dispatch Semaphore

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

Dispatch Semaphore本来使用计数的信号,该计数是多线程编程中的计数类型信号。所谓信号,类似于过马路时常用的手旗。可以通过时举起手旗,不可通过时放下手旗。而在Dispatch Semaphore中,使用计数来实现该功能。计数为0时等待,计数为1或者大于1时,减去1而不等待。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);  

参数表示计数的初始值。本例将计数值初始化为『1』。

dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);

dispatch_semaphore_wait函数等待Dispathore Semaphore的计数值达到大于或等于1。当计数值大于等于1,或者在待机中计数值大于等于1时,对该计数进行减法并从dispatch_semaphore_wait函数返回。第二个参数与dispatch_group_wait函数相同,由dispatch_time_t类型值指定等待时间。

dispatch_semaphore_t semapthore = dispatch_semaphore_create(1);
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
long result = dispatch_semaphore_wait(semapthore, time);
if (result) {
    /**
     * 由于Dispatch Semaphore 的计数值达到大于等于1
     * 或者在待机中的指定时间内
     * Dispatch Semaphore 的计数值达到大于等于1
     * 可执行需要进行排他控制的处理
     */
    NSLog(@"111");
}else{
    /**
     * 由于Dispatch Semaphore 的计数值为0
     * 因此在达到指定时间为止待机
     */
    NSLog(@"222");
}

dispatch_semaphore_wait函数返回0时,可安全的执行需要进行排他控制的处理。该处理结束时通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1。

dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
/**
 * 生成Dispatch Semaphore
 * Dispatch Semaphore 的技术初始值设定为『1』
 * 保证可访问NSMutableArray类对象的线程
 * 同时只能访问一个
 */
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

NSMutableArray *array = [NSMutableArray array];

for (int index = 0; index < 100000; index++) {

    dispatch_async(queue, ^(){
        /**
         * 等待Dispatch Semaphore
         * 一直等待,知道Dispatch Semaphore的计数值达到大于等于1
         */
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//

        NSLog(@"addd :%d", index);
        /**
         * 由于Dispatch Semaphore的计数值达到大于等于1
         * 所以将Dispatch Semaphore的计数值减去1
         * dispatch_semaphore_wait函数执行返回
         * 
         * 即执行到此时的Dispatch Semaphore的计数值恒为『0』
         * 
         * 由于访问的NSMutableArray类对象的线程只有1个
         * 因此可安全地进行更新
         */
        [array addObject:[NSNumber numberWithInt:index]];
        /**
         * 排他控制处理结束
         * 所以通过dispatch_semaphore_signal函数将Dispatch Semaphore的计数值加1.
         * 如果有通过dispatch_semaphore_wait函数
         * 等待Dispatch Semaphore的计数值增加的线程
         * 就由最先等待的线程执行
         */
        dispatch_semaphore_signal(semaphore);

    });

}

12、dispatch_once

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

- (void)gcdGroup{
    // 一次性执行:
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // code to be executed once
    });
}

13、Dispatch I/O

在读取较大文件时,如果将文件分成合适的大小并使用Global Dispatch Queue并列读取的话,应该会比一般的读取速度快不少。现今的输入/输出硬件已经可以做到一次使用多个线程更快的并列读取。能实现这一功能就是Dispatch I/O和Dispatch Data。

三、GCD实现

1、Dispatch Queue

GCD的Dispatch Queue非常方便,那么它究竟是如何实现的呢:

  • 用于管理追加的Block的C语言层实现的FIFO队列;
  • Atomic函数中实现的用于排他控制的轻量级信号;
  • 用于管理线程的C语言层实现的一些容器;

用于实现Dispatch Queue而使用的软件组件

组件名称提供技术
libdispatchDispatch Queue
Libc(pthreads)pthread_workqueue
XNU内核workqueue

编程人员所使用的GCD的API全部为包含在libdispatch库中的C语言函数。Dispatch Queue通过结构体和链表,被实现为FIFO队列。FIFO队列管理是通过dispatch_async等函数所追加的Block。

Block并不是直接加入FIFO队列,而是先加入Dispatch Continuation这一dispatch_continuation_t类型结构体中,然后加入FIFO队列。该Dispatch Continuation用于记忆Block所属的Dispatch Group和其他一些信息,相当于一般常说的执行上下文。

Dispatch Queue可通过dispatch_set_target_queue函数设定,可以设定执行该Dispatch Queue处理的Dispatch Queue为目标。该目标可像串珠子一样,设定多个连接在一起的Dispatch Queue。但是在连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Serial Dispatch Queue的各种优先级的Global Dispatch Queue。

Global Dispatch Queue有如下8种:

  • ①、Global Dispatch Queue(High Priority)
  • ②、Global Dispatch Queue(Default Priority)
  • ③、Global Dispatch Queue(Low Priority)
  • ④、Global Dispatch Queue(Background Priority)
  • ⑤、Global Dispatch Queue(High Overcommit Priority)
  • ⑥、Global Dispatch Queue(Default Overcommit Priority)
  • ⑦、Global Dispatch Queue(Low Overcommit Priority)
  • ⑧、Global Dispatch Queue(Background Overcommit Priority)

优先级中附有Overcommit的Global Dispatch Queue使用在Serial Dispatch Queue中。

2、Dispatch Source

Dispatch Source 是BSD系内核惯有功能kqueue的包装。
kqueue是在XNU内核中发生各种事件时,在应用程序编程方执行处理的技术。其CPU负荷非常小,尽量不占用资源。kqueue可以说是应用程序处理XUN内核中发生各种事件方法中最优秀的一种。

Dispatch Source可处理如下事件:

Dispatch Source的种类

名称内容
DISPATCH_SOURCE_TYPE_DATA_ADD变量增加
DISPATCH_SOURCE_TYPE_DATA_OR变量OR
DISPATCH_SOURCE_TYPE_MACH_SENDMACH端口发送
DISPATCH_SOURCE_TYPE_MACH_RECVMACH端口接收
DISPATCH_SOURCE_TYPE_PROC变检测到与进程相关的事件
DISPATCH_SOURCE_TYPE_READ可读取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL接收信号
DISPATCH_SOURCE_TYPE_TIMER定时器
DISPATCH_SOURCE_TYPE_VNODE文件系统有变更
DISPATCH_SOURCE_TYPE_WRITE可写入文件映像

(1)、DISPATCH_SOURCE_TYPE_TIMER,定时器

/**
 * 指定DISPATCH_SOURCE_TYPE_TIMER 作为Dispatch Source
 *
 * 在定时器经过指定时间时设定Main Dispatch Queue为追加处理的Dispatch Queue
 */
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
/**
 * 将定时器设定为15秒后
 * 不指定为重复
 * 允许延迟1秒
 */
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 15ull * NSEC_PER_SEC), DISPATCH_TIME_FOREVER, 1ull * NSEC_PER_SEC);
/**
 * 指定定时器指定时间内执行的处理
 */
dispatch_source_set_event_handler(timer, ^{
    NSLog(@"wakeup!");
    /**
     * 取消Dispatch Source
     */
    dispatch_source_cancel(timer);
});
/**
 * 指定取消Dispatch Source处理
 */
dispatch_source_set_cancel_handler(timer, ^{
    NSLog(@"canceled!");
    /**
     * 释放Dispatch Source (自身)
     */
    dispatch_release(timer);
});
/**
 *  启动Dispatch Source
 */
dispatch_resume(timer);
void
dispatch_source_set_timer(dispatch_source_t source,
    dispatch_time_t start,
    uint64_t interval,
    uint64_t leeway);
  • ①、第一个参数:dispatch_source_t
    dispatch_source_t类型,将要开启的定时器;
  • ②、第二个参数:dispatch_time_t
    dispatch_time_t类型,定时器开启时间;
    该参数设置为DISPATCH_TIME_NOW时,表示立即开启;
    该参数设置为dispatch_time(DISPATCH_TIME_NOW, 15ull * NSEC_PER_SEC),表示15秒后开启;
  • ③、第三个参数:uint64_t interval
    表示循环时间;
    该参数设置为1ull * NSEC_PER_SEC,表示1秒循环;
    该参数设置为DISPATCH_TIME_FOREVER,表示只循环一次;
  • ④、第四个参数:uint64_t leeway
    表示延迟时间;
    该参数设置为1ull * NSEC_PER_SEC,表示延迟1秒。

Dispatch Source和Dispatch Queue不同,是可以取消的,而且取消必须执行的处理可指定为回调用的Block形式。因此使用Dispatch Source实现XNU内核中发生的事件处理要比直接使用kqueue实现更为简单。

以上是关于Grand Central Dispatch(GCD)的主要内容,如果未能解决你的问题,请参考以下文章

暂停和恢复 Grand Central Dispatch 线程

swift Grand Central Dispatch(GCD)发送信号量示例

NSOperation 与 Grand Central Dispatch

使用 Grand Central Dispatch (GCD) 创建恰好 N 个线程

核心数据和线程/ Grand Central Dispatch

Grand Central Dispatch 用于复杂流程?