GCD的使用和原理

在我们做iOS开发的过程中,经常会与多线程打交道,异步绘制,网络请求等,方式有NSThread,NSOperationQueue,GCD等,在这里GCD的地位举足轻重,那么今天写一篇关于GCD的文章。首先迎来第一个问题:

什么是GCD
全名叫 Grand Central Dispatch 是一种异步执行任务的技术,一套基于c语言实现的api,语法十分简洁,只需要简单定义任务或按需加入到队列中,就可以按照计划实现功能,下面一个简单的例子。

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //网络请求
        //异步绘制图像
        //数据库访问等
        dispatch_async(dispatch_get_main_queue(), ^{
            //刷新UI
        });
    });

在这里使用异步的方式定义了一个任务,并将其添加到一个全局队列中,这里面的任务内容可以进行一些耗时操作,由于是异步所以不影响主线程,当任务结束之后,同样使用异步的方式定义了一个任务,将其添加到了主队列中,这里执行的任务会在主线程完成。

同步和异步
前面内容提到了“异步”一词,那么这里聊一下什么是“异步”,相应的还有“同步”一词。
异步的作用是不需要CPU立刻响应,而是等待一个信号或回调来下一步操作,当前线程可以立刻执行后续内容,而同步为可以阻塞当前线程,当执行完一个任务之后才能执行下一个。
异步是目的,多线程是手段
当我们开启一个新的线程之后,可以将任务交由新的线程执行,实现异步效果,当需要的时候再返回之前的线程。

用图来做一个同步和异步的解释


异步和同步

这里thread1模拟主线程,当执行任务A的时候由于耗时比较短,所以可以在主线程上完成,当执行到任务B的时候,由于耗时长,所以开辟了一条新的线程,将任务交由子线程处理,同时继续完成任务C,当耗时操作完成之后,将结果返回给主线程进行操作,实现了异步功能。

使用多线程仍然要注意几个问题,1.数据竞争 2.死锁 3.线程开辟太多导致内存消耗过大,不过只要使用得当,多线程对我们开发的好处十分巨大。

串行队列和并发队列
首先解释一下“队列”,如其名字,它是执行处理的等待队列,这里的队列调度方式为先进先出模式(FIFO-First In First Out),也就是先添加到队列中的任务先执行,当然还有其他几种模式,不过这里暂时不考虑,有兴趣的请自行查资料

FIFO

队列的种类也是两种,Serial Dispatch Queue和 Concurrent Dispatch Queue,分别为串行队列和并发队列,串行队列中的任务必须按照顺序依次执行,并发队列可以多个线程以并发的形式执行。
串行队列和并发队列

下面用代码来看串行队列和并发队列的区别,首先使用串行队列的方式创建几个异步操作

dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        NSLog(@"%@---0", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"%@---1", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"%@---2", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"%@---3", [NSThread currentThread]);
    });

看看输出结果

<NSThread: 0x6000002777c0>{number = 3, name = (null)}---0
<NSThread: 0x6000002777c0>{number = 3, name = (null)}---1
<NSThread: 0x6000002777c0>{number = 3, name = (null)}---2
<NSThread: 0x6000002777c0>{number = 3, name = (null)}---3

很明显,这里是同一个线程,而且也确实是按照顺序执行的,那么接下来使用并发队列来操作。

dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        NSLog(@"%@---0", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"%@---1", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"%@---2", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"%@---3", [NSThread currentThread]);
    });

再看看输出结果

<NSThread: 0x60400046adc0>{number = 5, name = (null)}---2
<NSThread: 0x60400046b000>{number = 3, name = (null)}---0
<NSThread: 0x6000004655c0>{number = 4, name = (null)}---1
<NSThread: 0x6000004653c0>{number = 6, name = (null)}---3

很明显,并没有按照顺序执行,而且不是同一个线程,这就印证了上面所说的串行队列和并发队列的区别。
但是不代表创建了多少个并发任务就会开辟多少个线程,这个线程数量由XNU内核来决定,将上面的案例调整为8个任务,可以看到有些线程是重复的,表明了如果一开始就可以开辟8个线程,就不会出现这种情况,所以当一个任务结束之后,下一个任务可以放到完成的线程中,但是和串行队列还是有区别的


多个任务的并发队列

首先会开辟4条线程处理任务,将task0-task3异步形式放入线程中执行,假定task0首先完成,并发队列中取出task4放入线程3中执行,以此类推,所以也就造成了有些线程是重复的原因。

DISPATCH MAIN QUEUE/DISPATCH GLOBAL QUEUE
我们平时常用的大多是这两种,前者是在主线程执行的queue,由于主线程只有一个,自然是个串行队列,而global_queue是个并发队列,这个队列有4个优先级,分别是低优先级(DISPATCH_QUEUE_PRIORITY_LOW),默认优先级(DISPATCH_QUEUE_PRIORITY_DEFAULT),高优先级(DISPATCH_QUEUE_PRIORITY_HIGH),和后台优先级(
DISPATCH_QUEUE_PRIORITY_BACKGROUND),代码如下

dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);

GCD的api

前面已经提到一些关于GCD的api,下面会详细的探讨这些api的功能
dispatch_queue_create

dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.example.gcdDeo",NULL);

这里是创建一个queue的方法,返回类型为dispatch_queue_t,方法含有两个参数,一个是队列名,推荐使用应用程序ID逆序的域名方式,后面参数表示生成的队列类型,一种为DISPATCH_QUEUE_SERIAL为串行队列,这里也可以使用NULL,这个宏定义的值就是NULL,另一种是DISPATCH_QUEUE_CONCURRENT表示并发队列,这里虽然串行队列中的任务是按顺序执行的,但是如果创建多个串行队列,是会并行处理。


并行执行的多个Serial Dispatch Queue

由于一个队列只操作一个线程,所以在执行一些比较重要的操作时,尽量使用串行队列,避免造成数据冲突。

dispatch_(a)sync
最常用的就是这两个函数了dispatch_sync同步执行,dispatch_async异步执行,第一个参数为指定的队列名,block为执行的任务内容,但是使用gcd也要注意一些问题

    NSLog(@"1");
    
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    
    NSLog(@"3");
 1
//接下来报错
同步主队列的问题

看得出这个问题的原因,同步的方式在主线程执行一个任务,造成了线程阻塞。

dispatch_after
当我们需要在一段时间之后延迟执行某些任务的时候,可以使用dispatch_after这个函数,示例代码如下:

NSLog(@"延迟之前");
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
    NSLog(@"延迟3秒执行");
});

这里的返回结果为

2018-09-04 14:52:59.437518+0800 GCDDemo[75192:21501573] 延迟之前
2018-09-04 14:53:02.438042+0800 GCDDemo[75192:21501573] 延迟3秒执行

可以看得出的确是延迟了3秒执行的任务,不过这里的实际意义为在3秒后将任务添加到主队列中执行,由于Main Dispatch Queue在主线程的Runloop中,所以真实的时间为3秒加上最短立刻最长Runloop一次循环的时间,并且如果Runloop中堆积的任务较多,可能时间会更长,所以时间并不是完全精确,不过想要大致完成延迟功能,dispatch_after是完全没有问题的。
time为dispatch_time_t类型,从第一个参数时间开始,到第二个指定后的时间结束,这里NSEC_PER_SEC是时间类型,以秒为单位,NSEC_PER_MSEC是以毫秒做单位,其他类型单位这里不多解释了。

dispatch_group
项目中我们经常会遇到将多个异步任务的结果同时返回,如果只是同步的话,执行完最后一个任务就是结束,但是异步却不行,所以这里我们需要使用dispatch_group,代码如下

    NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_group_async(group, queue, ^{
        sleep(4);
        NSLog(@"子线程1-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_async(group, queue, ^{
        sleep(3);
        NSLog(@"子线程2-----%@", [NSThread currentThread]);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部结束-----%@", [NSThread currentThread]);
    });

这里使用sleep模拟了两个耗时操作,并且打印了几个位置所处的线程,首先使用dispatch_group_create创建了一个组,并且使用了global queue,接下来模拟异步延时操作,使用dispatch_group_async这个函数和dispatch_async的功能是一样的,只不过前者归属于第一个参数这个组,当函数全部结束之后会调用dispatch_group_notify这个方法,表示所有异步操作都已经结束,代码执行结果如下

2018-09-04 16:22:03.449143+0800 GCDDemo[76086:21787405] 全部开始-----<NSThread: 0x6040002601c0>{number = 1, name = main}
2018-09-04 16:22:06.451174+0800 GCDDemo[76086:21787469] 子线程2-----<NSThread: 0x60400027ed40>{number = 3, name = (null)}
2018-09-04 16:22:07.452564+0800 GCDDemo[76086:21787470] 子线程1-----<NSThread: 0x600000470100>{number = 4, name = (null)}
2018-09-04 16:22:07.452926+0800 GCDDemo[76086:21787405] 全部结束-----<NSThread: 0x6040002601c0>{number = 1, name = main}

这里可以看出的确是当两个子线程都完成之后,才回到主线程的回调中,另外一种方式通过dispatch_group_wait方式阻止后续操作,直到异步函数全部完成

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"全部结束-----%@", [NSThread currentThread]);

这里wait表示等待,第一个参数表示等待的对象,类型是dispatch_group_t,第二个参数表示等待时间,这里使用DISPATCH_TIME_FOREVER表示一直等待,所以这个函数变成了直到组中代码全部结束,等待才会停止,不过这里还是更加推荐使用dispatch_group_notify方式,因为dispatch_group_wait是同步的,所以不推荐在主线程中使用。

dispatch_group_enter/dispatch_group_leave
实际工作中,会出现让多个请求同时返回结果的案例,这时如果用上面的方法会造成一定问题,因为上面异步的任务只是一个NSLog,如果是一个延时请求呢,下面模拟一下多个请求同时发生的案例。

NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(4);
            NSLog(@"模拟请求1-----%@", [NSThread currentThread]);
        });
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"模拟请求2-----%@", [NSThread currentThread]);
        });
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部结束-----%@", [NSThread currentThread]);
    });

这里在dispatch_group_async这些个异步任务中,再次用异步的方式模拟了一个请求任务,下面看一下结果

2018-09-04 16:50:50.221045+0800 GCDDemo[76421:21817204] 全部开始-----<NSThread: 0x60000007ecc0>{number = 1, name = main}
2018-09-04 16:50:50.249321+0800 GCDDemo[76421:21817204] 全部结束-----<NSThread: 0x60000007ecc0>{number = 1, name = main}
2018-09-04 16:50:53.225810+0800 GCDDemo[76421:21817394] 模拟请求2-----<NSThread: 0x604000663000>{number = 5, name = (null)}
2018-09-04 16:50:54.225917+0800 GCDDemo[76421:21817393] 模拟请求1-----<NSThread: 0x6040004649c0>{number = 3, name = (null)}

造成这样的原因是发起请求的两个任务已经完成了,所以调用了notify方法,但是请求的结果还没有成功,所以这样的代码是有问题的,那么这里就使用dispatch_group_enter和dispatch_group_leave这对方法来解决。

NSLog(@"全部开始-----%@", [NSThread currentThread]);
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create("com.example.gcdDemo", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(4);
            NSLog(@"模拟请求1-----%@", [NSThread currentThread]);
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_group_enter(group);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"模拟请求2-----%@", [NSThread currentThread]);
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"全部结束-----%@", [NSThread currentThread]);
    });

这样的结果就没有问题了,这对方法可以理解为retain和release,当每次进入到一个新的异步任务中时,使用dispatch_group_enter告诉原本的异步任务这里还没有结束,当请求完成之后使用dispatch_group_leave表示已经结束了,可以退出这个异步操作了,这样的话,才能保证真正完成了一个延时函数,结果如下

2018-09-04 16:54:22.832901+0800 GCDDemo[76462:21821447] 全部开始-----<NSThread: 0x60000007e980>{number = 1, name = main}
2018-09-04 16:54:25.837875+0800 GCDDemo[76462:21821502] 模拟请求2-----<NSThread: 0x604000460840>{number = 3, name = (null)}
2018-09-04 16:54:26.833634+0800 GCDDemo[76462:21821503] 模拟请求1-----<NSThread: 0x6000004676c0>{number = 4, name = (null)}
2018-09-04 16:54:26.833992+0800 GCDDemo[76462:21821447] 全部结束-----<NSThread: 0x60000007e980>{number = 1, name = main}

当前案例还有其他的解决方案,我准备在下一篇文章中将案例的其他解决办法写出来,这里不加赘述了。

dispatch_barrier_(a)sync
再次通过一个案例来认识这个函数,假设有a,b,c,d四个异步任务,我新增加了一个任务new,我希望可以在a和b之后完成并且在c和d之前完成,这里可以通过同步等其他方式解决,不过有个更好的方式解决这个问题,dispatch_barrier_async,和它的名字一样,可以理解为一个栅栏,将前后分隔开,在下先上代码

dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        sleep(3);
        NSLog(@"task - A");
    });
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"task - B");
    });
    
    NSLog(@"before barrier");
    
    dispatch_barrier_sync(queue, ^{
        NSLog(@"task - new");
    });
    
    NSLog(@"after barrier");
    
    dispatch_async(queue, ^{
        sleep(2);
        NSLog(@"task - C");
    });
    
    dispatch_async(queue, ^{
        sleep(1);
        NSLog(@"task - D");
    });

这里模拟了四个异步操作任务,将中间位置插入了一个new任务,这里看看返回结果

2018-09-04 17:55:08.783989+0800 GCDDemo[76920:21886183] before barrier
2018-09-04 17:55:10.787647+0800 GCDDemo[76920:21886310] task - B
2018-09-04 17:55:11.785806+0800 GCDDemo[76920:21886309] task - A
2018-09-04 17:55:11.786003+0800 GCDDemo[76920:21886183] task - new
2018-09-04 17:55:11.786164+0800 GCDDemo[76920:21886183] after barrier
2018-09-04 17:55:12.790452+0800 GCDDemo[76920:21886312] task - D
2018-09-04 17:55:13.790432+0800 GCDDemo[76920:21886309] task - C

这里可以看出,由于有模拟延时操作,所以before首先输出,然后执行了A和B两个任务,而barrier中的任务并没有延时却在A和B之后,说明栅栏功能生效,有因为是sync同步的关系,所以后面的after紧接着输出,这里把sync换成async会有什么结果呢

2018-09-04 17:59:29.114406+0800 BlockDemo[76948:21890748] before barrier
2018-09-04 17:59:29.114600+0800 BlockDemo[76948:21890748] after barrier

before和after是紧挨着输出的,说明dispatch_barrier_sync同样有同步的作用,而dispatch_barrier_async也有着异步的效果。

dispatch_apply
如果有一个案例需要按指定次数执行gcd的任务,可以用这个函数,第一个参数为次数,第二个参数为指定的队列,第三个参数是带参数的block,参数为当前次数下标

NSArray * array = @[@"1",@"2",@"3",@"4",@"5",];
    dispatch_apply([array count], dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"元素:%@----第%ld次", array[index],index);
    });
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942336] 元素:4----第3次
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942205] 元素:1----第0次
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942337] 元素:3----第2次
2018-09-04 18:44:56.970194+0800 GCDDemo[77406:21942335] 元素:2----第1次
2018-09-04 18:44:56.970380+0800 GCDDemo[77406:21942205] 元素:5----第4次

dispatch_semaphore
前文已经有讲部分关于产生不一样数据的处理问题,不过有时需要更细致的排他控制,例如你到了一个屋子,里面有一个椅子,你可以坐下,如果再拿来一个椅子,依然可以坐下,但是如果把椅子拿走,那么只能站着等待了,semaphore使用信号量的方式实现了类似的情况,首先第一个函数:

dispatch_semaphore_create(0);

这里创建了一个semaphore,返回类型是dispatch_semaphore_t,有一个参数,表示初始信号量的值,这里给0,如果遇到wait则需要等待。

dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
dispatch_semaphore_signal(semaphore);

当第一个函数中的信号量0时,这个函数需要进行等待,如果大于0,则将信号量-1,第二个参数为等待的时间,这个可以根据需求使用,这里假定需要等待无限长的时间,知道信号量增加未知
第二个函数中会给信号量+1,可以看出这两个函数是要成对出现的,如果单独出现wait而且初始化为0的话,会造成后续任务全部卡住无法执行。下面给一个小的案例

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(3);
            NSLog(@"完成1");
            dispatch_semaphore_signal(semaphore);
        });
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            sleep(2);
            NSLog(@"完成2");
            dispatch_semaphore_signal(semaphore);
        });
    });
    
    
    dispatch_group_notify(group, queue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"全部完成");
    });

这里再次模拟了多个请求需要同时返回结果的问题,通过信号量的方式,使用信号量的方式同样可以完成这个需求,当notify中有两个等待信号的时候,只能通过请求成功的信号量增加的方法去抵消,当两个请求全部完成的时候,等待信号也全部结束,这时表示任务全部完成。
不过如果更换一下需求会怎么样呢,如果我们需要让多个请求同步执行要怎么做,首先我们需要开启异步去管理,同样请求也是异步方法,所以我们用这种方式不能让请求同步执行,这里需要使用线程依赖的方式操作,GCD线程依赖这里面也使用semaphore的方式去做,修改代码如下

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore0 = dispatch_semaphore_create(0);
    dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(0);
    
    dispatch_group_async(group, queue, ^{
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [NSThread sleepForTimeInterval:3];
            NSLog(@"完成1");
            dispatch_semaphore_signal(semaphore0);
        });
    });
    
    dispatch_group_async(group, queue, ^{
        dispatch_semaphore_wait(semaphore0,DISPATCH_TIME_FOREVER);
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"完成2");
            dispatch_semaphore_signal(semaphore1);
        });
    });
    
    
    dispatch_group_notify(group, queue, ^{
        dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
        NSLog(@"全部完成");
    });

这里创建了两个信号量,因为任务1模拟了时间更久,我们这里需要让任务1先完成,那么我们在任务2中把任务1的信号量等待,直到任务1完成并增加信号量,再执行任务2,结果如下

2018-09-05 10:27:02.725024+0800 GCDDemo[81615:22654482] 完成1
2018-09-05 10:27:04.727478+0800 GCDDemo[81615:22654482] 完成2
2018-09-05 10:27:04.727852+0800 GCDDemo[81615:22654485] 全部完成

可以看出我们的目的已经实现了,但是如果有多个请求任务呢,必然需要创建多个信号量,按照需要的顺序进行依赖,但是这种方法其实写起来容易乱,最好的方式是使用NSOperationQueue添加线程依赖,但是这里不多加赘述,同样后续文章中会详细分析一次此案例,并使用其他方式解决这个问题。

dispatch_once
这个函数应该也不会陌生,当我们希望只会执行一次的函数我们会使用dispatch_once,所有我们创建单例的时候也会使用到这个函数。

+ (instancetype)shareInstance {
    static Manager * manager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        manager = [Manager new];
    });
    return manager;
}

这种单例不需要担心线程问题,即使是多线程环境下也一定是安全的,onceToken会保证运行过程中这部分只会执行一次。

GCD的实现

GCD的使用是十分方便的,这里探讨一下它是如何实现的
C语言实现的用于管理追加Block的FIFO队列
用于排他控制的atomic类型的轻量级信号
C语言实现的管理线程的容器
首先确定一下用于实现dispatch queue的软件组件

组件和技术

我们所使用的全部api都处于libdispatch库中,Dispatch Queue通过结构体和链表实现FIFO队列。FIFO通过dispatch_async等函数管理添加的block。
但是block并不是直接加入到FIFO队列中,而是先加入Dispatch Continuation这个dispatch_continuation_t类型的结构体中,再加入到队列中,这个结构体包含了block所属的group等信息,也就是执行上下文。

XNU内核有4中workqueue,优先级和Global Dispatch Queue的优先级相同,当在Global Dispatch Queue中执行block时,libdispatch从FIFO队列中取出Dispatch Continuation,调用pthread_workqueue_additem_np函数,将自身信息,符合其优先级的workqueue信息以及执行回调函数等传递给参数。

pthread_work_queue_additem_np函数使用workq_kernreturn系统调用。通知workqueue应当执行的项目。根据通知,XNU内核基于系统判断是否生成线程,如果是overcommit属性则始终生成线程。

workqueue的线程执行pthread_workqueue函数,该函数调用libdispatch的回调函数,在回调函数中执行加入到Dispatch Continuation的Block。

Block执行结束后,进行通知Dispatch Group结束、释放Dispatch Continuation等处理。开始准备执行Global Dispatch Queue的下一个Block

以上就是Dispatch Queue的大致执行过程。

Dispatch Source
GCD中除了常用的Dispatch Queue外,还有Dispatch Source,它具有很多种类型处理能力,最常见的就是使用定时器。

dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
    //创建一个dispatch_source_t类型变量,类型指定为定时器
    dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,1.0*NSEC_PER_SEC, 0);
    //指定定时器执行时间为每秒执行一次
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"定时内容");
        //每秒执行的内容
    });
    dispatch_source_set_cancel_handler(timer, ^{
        NSLog(@"定时取消");
        //取消定时器的回调
    });
    dispatch_resume(timer);
    //启动定时器
    //dispatch_source_cancel(timer);
    //取消定时器

这里本身会存在一个问题,就是set_event_handle这个回调会不执行,原因是当执行过作用域之后,这个source可能会被释放掉,所以可以使用添加到属性的方式放大source的作用域,保证定时器可以始终执行

@property (nonatomic, strong) dispatch_source_t timer;

这里如果内存管理语义使用了assign创建定时器,则会报出会被释放的错误。

使用Dispatch Queue本身是不具备“取消”功能的,要么放弃取消,要么使用NSOperationQueue等方法,而Dispatch Source具备该功能,而且取消后执行的处理可以使用block形式,这里也能看出Dispatch Source的强大功能。

到此为止关于GCD的这篇文章就结束了,如果文章中出现问题欢迎指出,并且如果有更优秀的看法也欢迎提出或讨论。

本文部分内容参考自《Objective-C高级编程》一书,有兴趣的小伙伴可以翻看一下

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

推荐阅读更多精彩内容