OC -> GCD

96
KongGG
2016.08.10 16:10* 字数 2011

iOS - GCD

概念

big

GCD (Grand Central Dispatch): 伟大的的中枢调节器; GCD 是苹果公司开发的一个创建子线程的一个方法,主要为多核的并行开发运算提出解决方案,利用 GCD 可以是的 CPU 的内核运用的更加充分,同时 GCD 也会自动管理线程中的生命周期。

small

1.队列:队列是一种线性表,队列的上下都是开口的与栈相反,队列中遵循的原则是FIFO即为先进先出,队列是在表尾进行添加操作,表头进行删除操作即先进先出后进后出。

2.同步:同步是指的是在调用方法时,按照顺序的执行一些代码。在第一个方法没有执行完的时候第二个方法是不会进行的。

3.异步:异步和同步相反,调用方法时,当没有收到第一个方法调用的返回值时,第二个方法也可以执行。

4.串行:程序运行时,程序会按照顺序执行代码,只是存在一个运行上下文。

5.并发:程序运行时,程序存在多个运行上下文,可以通过这些上下文执行不同的代码。

多线程编程

我们研究多线程编程之前,需要知道当我们执行一段代码的时候 CPU是如何执行的。

Cup 执行过程



通过上图我们要了解一个概念叫做上下文切换,上下文切换是 OSX 和 iOS 的核心 XNU 内核在发生操作系统事件时会切换执行路径,例如 CPU的寄存器等信息保存到各自路径专用的内存块中,从切换目标路径专用的内存块中,复原 CPU 寄存器等信息,继续执行切换路径的 CPU 命令列。叫做上下文切换。

利用这种编程的方式叫做多线程编程,但是通过多线程编程同样的也会产生很多的问题具体问题如下

  1. 多线程更新相同的资源会导致数据的不一致。

    具体表现为两个线程同时对一个数据进行更新,而两个线程得到的数据不相同从而导致数据的不一致性。
  2. 死锁问题,多个线程之间相互持有,造成持续等待。

    如下代码就发生了线程的死锁,程序先执行 1,这时候同步线程会让程序进入等待等 2 执行完了之后再执行 3,但是 sync 有产生了队列,队列会将操作放到对尾 2 等到 3 执行完了在执行,而 3 又在等待 2 执行完了它在执行。所以造成了死锁现象。
  3. 过多的线程会消耗大量的内存。

    因为过多的线程会使得 CPU 大量的调用“上下文切换”从而使得 CPU 的消耗巨大,从而导致程序的卡顿。
    printf("1");
    dispatch_sync(dispatch_get_main_queue(),^{
        NSLog(@"线程发生了死锁");
        printf("2");
    })
    printf("3");

GCD Code

dispatch_queue_create,dispatch_sync 和 dispatch_async

通过 dispatch_queue_create 函数可以生成 Dispatch Queue 根据参数的不同可以生成 Serial 和 Concurrent 两种类型的 Dispatch Queue。

dispatch_queue_t queue = dispatch_queue_create("www.kong.com",NULL);

..create后面的两个参数:<br>第一个参数是队列的名称<br>第二个参数传NULL的时候是创建一个串行的队列。传DISPATCH_QUEUE_CONCURRENT`的时候是创建一个并发队列。

这样我们分四种情况对 GCD 进行讨论,同步串行,同步并发,异步串行,异步并发.

** 同步串行**


    dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", NULL);
    dispatch_sync(queue ,^{
        NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_sync(queue ,^{
        NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_sync(queue ,^{
        NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_release(queue);//ARC 条件下我们需要将其进行释放。

打印的结果为

a当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
b当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
c当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}

创建了串行队列,通过同步的方式添加到Dispatch Queue 等待队列中,没有开辟新的线程,所有的打印都是在主线程中进行,队列 queue 按照 abc 的顺序添加到等待队列中,也就是 FIFO 原则,出队列的时候也是满足该原则实现了先进先出,后进后出,打印的时候按顺序进行打印。

** 同步并发**


    dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue ,^{
        NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_sync(queue ,^{
        NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_sync(queue ,^{
        NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_release(queue);//ARC 条件下我们需要将其进行释放。

打印的结果为

a当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
b当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}
c当前的线程为 <NSThread: 0x12f505470>{number = 1, name = main}

创建了并发的队列添加到队列中的时候用 sync 同步的方式将其追加到Dispatch Queue 中 没有开辟新的线程,所有的数据处理仍然是在主线程中进行处理,Dispatch Queue 按照追加的顺序(队列 FIFO)的方式执行处理。

** 异步串行**


    dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", NULL);
    dispatch_async(queue ,^{
        NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_async(queue ,^{
        NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_async(queue ,^{
        NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_release(queue);//ARC 条件下我们需要将其进行释放。

打印的结果为

a当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)} (这里的 name = null,可以通过[NSThread currentThread]来设置)
b当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)}
c当前的线程为 <NSThread: 0x12f505470>{number = 2, name = (null)}

创建串行的Queue,然后通过异步的方式添加到队列中去,使得 Dispatch Queue开辟了一个新的线程,但是还是通过同步队列的方式来进行执行,都是在dispatch_async 创建出来的线程中按照顺序执行队列中的操作。

** 异步并发**


 dispatch_queue_t queue = dispatch_queue_create("wwww.kong.com", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue ,^{
        NSLog(@"a当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_async(queue ,^{
        NSLog(@"b当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_async(queue ,^{
        NSLog(@"c当前的线程为 %@",[NSThread currentThread]);
    });
    dispatch_release(queue);//ARC 条件下我们需要将其进行释放。

打印的结果为:

a当前的线程为 <NSThread: 0x147d41a20>{number = 2, name = (null)}
c当前的线程为 <NSThread: 0x147d41a20>{number = 2, name = (null)}
b当前的线程为 <NSThread: 0x147e51ec0>{number = 3, name = (null)}

创建的是并发队列,通过异步的方式将其追加到 Dispatch Queue 中,就会使得 CPU 开辟新的线程来执行代码块 Blcok 中的代码。从而达到程序流畅的目的。

GCD中其他的一些方法

** 利用 global 创建线程**


    //通过dispatch_get_global_queue 创建的是并发的队列
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        执行耗时操作       
     });

*对于 main dispatch_queue 和 global dispatch_queue 执行 dispatch_retain 函数和 dispatch_release 函数不会引起任何的变化,也不会有任何的问题。
当我们使用 dispatch_queue_create 的时候要考虑到在 ARC 中何时执行 dispatch_retain 和 dispatch_release 两个函数方法。

** dispatch_after**


    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0f * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"3秒后执行该方法");
    });

通过这种方法延迟3秒执行,要知道这种方法的实现是在3秒后将 block 中的代码追加到 Dispatch Queue 中,并不是在指定的时间后进行处理。不像 NSTimer 里面的设置在什么时间后进行处理。

** dispatch_group**


    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group =  dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行 block 1 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行 block 2 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行 block 3 %@",[NSThread currentThread]);
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"执行 block 4 %@",[NSThread currentThread]);
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"执行完 group 执行的操作");
    });

通过运用组的话,可以先执行组中的内容,然后当组中的内容执行完了之后用 group 的 notify 方法可以在执行 block 中的方法。这种方法用于顺序执行,例如下载图片的时候,下载多张图片,然后进行拼接到一起,时候可以用这种方法将两个图片都下载下来,然后在 group_notify中对图片进行拼接,然后得到结果。group_notify 也是起到一个追加的作用,将 block 中的内容追加到 Dispatch Queue 的后面。等到 group 中的队列执行完后在执行。

** dispatch_barrier_async**


    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        NSLog(@"向数据库中写入数据");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"从数据库中进行数据的读取"); 
    });
    dispatch_async(queue, ^{
        NSLog(@"从数据库中写入数据");
    });

通过 dispatch_barrier_async 方法可以使当前的线程只执行 barrier_async(block)中的操作在两次写入操作的时候如果不用 dispatch_barrier_async 方法的话就会导致读取数据的同时也在写入数据,就导致了获取的数据不准确,此时的 dispatch_barrier_async 就可以起到线程锁的作用,保障读写的安全。

** dispatch_apply**


  NSArray *applyArray = @[@1,@2,@3,@4,@5,@6];
    dispatch_apply([applyArray count], queue, ^(size_t index) {
        NSLog(@"%zu   %@",index,[applyArray objectAtIndex:index]);
    });

这段代码是把一项任务提交到队列中多次执行,队列的串行并行由创建的队列所决定。这里类似一个循环遍历的功能,这样将不想关的循环调到后台线程执行,会将执行效率大大提高。

GCD 底层实现。

GCD 是用于管理追加 Block 的 C 语言层实现 FIFO队列
里面主要运用了 libdispatch,Libc(pthreads),XNU内核。
GCD中的 API 全部为包含在 libdispatch 库中的 C 语言函数,Dispatch Queue通过结构体和链表,被实现为 FIFO队列 ,FIFO队列管理是通过 dispatch_async 等函所追加的 Block。

GCD 实现过程

Block 并不是直接加入 FIFO队列中,而是先加入 Dispatch Continuation 这一个 dispatch_continuation_t 类型的结构体中,然后在加入 FIFO队列,该 Dispatch Continuation 用于记忆 Block 所属的 DispatchGroup 和其他的一些信息。 当 Dispatch Queue 中执行 Block 的时候,libdispatch 从 Global Dispatch Queue 自身的 FIFO 队列中取出 Dispatch Continuation ,调用 pthread_workqueue_additem_up 函数。将该 Global Dispatch Queue 自身、符合其优先级的 workqueue 信息以及为执行 Dispatch Continuation 的回调函数等传递给参数。 workqueue 最终决定是否生成线程。
workqueue 的线程执行 pthread_workqueue 函数,该函数调用 libdispatch 的回调函数,在该回调函数中执行加入到 Dispatch Continuation 的 Block。
Block 执行结束后,进行通知 DispatchGroup 结束,释放 Dispatch Continuation 等处理,开始准备执行加入到 Global Dispatch Queue中的下一个 Block。

说明:

本文参考

图书《iOS与 OSX 多线程和内存管理》

Objective-C