浅谈GCD的当中贯彻

3、用于管理线程的C语言实现的一些容器。

iOS – GCD

GCD

Grand Central
Dispatch(GCD)是Apple开发的一个多核编程的解决方法。该方法在MacOSX10.6雪豹中首次推出,并随后被引入到了iOS4.0中。GCD是一个替代诸如NSThread,
NSOperationQueue等技术的很高效和强大的技术。GCD能够帮助我们使用非常简洁高效的方法实现复杂繁琐的多线程编程。

下面的例子列举了简单的异步线程处理,在后台处理完耗时的程序之后,在主线程更新UI。

 dispatch_async(queue, ^{
       /*
        * ...
        * 长时间处理的Code
        */

        //处理结束,主线程更新UI
        // 使用dispatch_get_main_queue() 获得主线程
        dispatch_async(dispatch_get_main_queue(), ^{
            // ... code
            // 执行需要在主线程执行的更新UI代码
        });
    });

工作队列,是一个用于创建内核线程的接口,通过它创建的内核线程来执行内核其他模块排列到队列里的工作。不同优先级的dispatch
queue对应着对应优先级的workqueue。GCD初始化的时候,使用pthread_workqueue_create_np创建pthread_workqueue

说明:

本文参考

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

Dispatch Source

GCD中除了主要的Dispatch Queue外,还有不太引人注目的Dispatch
Source。它是BSD系内核惯有功能kqueue的包装。

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

Dispatch Source可处理以下事件:

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

事件发生时,在指定的Dispatch Queue中可执行事件的处理。
下面我们使用DISPATCH_SOURCE_TYPE_READ,异步读取文件映像。

  *
    __block size_t total = 0;
    size_t size = 1024 * 10;//要读取的字节数
    char * buff = (char *)malloc(size);

    /*
     *  设定为异步映像
     */
    fcntl(sockfd, F_SETFL, O_NONBLOCK);

    /*
     *  获取用于追加事件处理的Global Dispatch Queue
     */    
    dispatch_queue_t queue = dispatc_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    /*
     *  基于READ事件作成Dispatch Source
     */  
    dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, sockfd, 0 , queuq);

    /*
     *  指定发生READ事件时执行的处理
     */    
    dispatch_source_set_event_handler(source, ^{
          /*
           *  获取可读取的字节数
           */
          size_t available = dispatch_source_get_data(source);
          /*
           *  从映像中读取
           */
          int length = read(sockfd, buff, available);
          /*
           *  发生错误时取消Dispatch Source
           */
          if (length < 0)
          {
                // 错误处理
                dispatch_source_cancel(source);
          }

          total += length;

          if (total == size)
          {
                // buff 的处理

                // 处理结束,取消Dispatch Source
                dispatch_source_cancel(source);
          }
    });

    /*
     *  指定取消Dispatch Source时的处理
     */
    dispatch_source_set_cancel_handler(source, ^{
        free(buff);
        close(sockfd);

        /*
         *  释放Dispatch Source(自身)
         */
        dispatch_release(source);
    });

    /*
     *  启动Dispatch Source
     */

    dispatch_resume(source);

与上面代码非常相似的代码,使用在了Core Foundation框架的用于异步网络的API
CFSocket中。因为Foundation框架的异步网络API是通过CFSocket实现的,所以可享受到仅使用Foundation框架的Dispatch
Source(即GCD)带来的好处。

调用该函数后,会通知对应的workqueue增加执行项目,XNU内核生成线程,线程执行pthread_workqueue函数执行block

small

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

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

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

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

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

参考文档

  • 《Objective-C 高级编程iOS 与OS X多线程和内存管理》
  • 《Effective Objective-C 2.0》
  • 《ConcurrencyProgrammingGuide》

执行过程

概念

GCD的实现原理

简单来说,GCD的实现需要使用这些工具:

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

通常,应用程序中编写的线程管理应用的代码要在系统iOS和OS
X的核心XNU内核级上实现。因此,无论编程人员如何努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。

使用GCD要比使用pthreads和NSThread这些一般的多线程编程API更好。并且,如果使用GCD就不必编写为操作线程反复出现的类似的代码(这被称为固定源代码片段),而可以在线程中集中实现处理内容。我们尽量多使用GCD或者使用了Cocoa框架GCD的NSOperationQueue类等API。

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

组件名称 提供技术
libdispatch Dispatch 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。

Main Dispatch Queue在RunLoop 中执行Block。

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中。如Overcommit
这个名称所示,不管系统状态如何,都会强制生成线程的Dispatch Queue。
这8种Global Dispatch
Queue各使用1个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_workqueue。

pthread_workqueue包含在Libc提供的pthreads
API中。其使用bsdthread_register和workq_open系统调用,在初始化XNU内核的workqueue之后获取workqueue信息。

XNU内核持有4中workqueue:

WORKQUEUE_HIGH_PRIORITY
WORKQUEUE_DEFAULT_PRIORITY
WORKQUEUE_LOW_PRIORITY
WORKQUEUE_BG_PRIORITY

以上为4中执行优先级的workqueue。该执行优先级与Global Dispatch
Queue的4种执行优先级相同。

下面看一下Dispatch Queue中执行Block的过程。当在Global Dispatch
Queue中执行Block时,libdispatch从Global Dispatch
Queue自身的FIFO队列中提出Dispatch
Continuation,调用pthread_workqueue_additem_np函数。将该Global
Dispatch Queue自身、符合其优先级的workqueue信息以及为执行Dispatch
Continuation的回调函数等传递给参数。

该线程虽然与iOS和OS X中通常使用的线程大致相同,但是有一部分pthread
API不能使用。详细信息科参考苹果的官方文档《并列编程指南》的“与POSIX线程的互换性”一节。

另外,因为workqueue生成的线程在实现用于workqueue的线程计划表中运行,所以与一般线程的上下文切换不同。这里也隐藏着使用GCD的原因。

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


Dispatch
Queue是GCD中很重要的一部分,它是负责执行处理的队列,它的内部主要由三部分构成:

GCD Code

2、处理信号量的原子操作;

big

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

dispatch queue执行block时,先从dispatch
queue自身的FIFO队列中取出dispatch
continuation,接着调用pthread_workqueue_additem_np函数,传入这些参数:dispatch
queue自身、一个符合其优先级workqueue,dispatch continuation。

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 中的代码。从而达到程序流畅的目的。

CGD

多线程编程

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

澳门网上正规赌场网址 1

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");

workqueue

GCD 底层实现。

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

澳门网上正规赌场网址 2

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。

Dispatch Queue

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是苹果开发的多线程编程的解决方案,通过简单的API就可以实现创建新线程去执行我们需要执行的任务,不需要我们手动地创建和管理线程。它的API包含在libdispatch库中。

Dispatch Queue执行过程是怎么的呢?

1、一个管理追加block的C语言实现的FIFO的队列;

dispatch queue 通过
dispatch_async等函数将block追加到队列上,block不是直接追加到队列,而是先构成一个dispatch
continuation构造体,构造体包含了这个block还有一些上下文信息,例如block所属的dispatch
group等等。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
0), ^{

NSLog;

});

Post Author: admin

发表评论

电子邮件地址不会被公开。 必填项已用*标注