GCD学习

Objective-C高级编程-GCD部分

Grand Central Dispatch(GCD)是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。

GCD的简单使用

dispatch_async(queue, ^{
    
    /*
     * 想在后台处理的事务,如数据库访问,大量的计算等
     */
     
     dispatch_async(dispatch_get_main_queue(), ^{
         
         /*
          * 在主线程执行的事务,如界面更新
          */
     });
});

这里dispatch_async的作用就是讲开发者定义的想执行的任务追加到适当的Dispatch Queue中。

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

其中需要传入两个参数,第一个是Dispatch Queue,第二个是Block变量。

这里dispatch_async将任务异步的追加到Dispatch Queue中,Dispatch Queue会按照FIFO的顺序执行处理。

这里Dispatch Queue有两种

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

简单来讲如果使用Serial Dispatch Queue来处理事务,事务的处理顺序会按照加入的顺序执行。

如果使用Concurrent Dispatch Queue来处理事务,则后面添加的事务不会等待前面的事务执行完成后才执行,也就是不保证事务的处理顺序是加入的顺序。加入的事务是并行处理,但并行处理的处理数量取决于当前系统的状态,即iOS和OS X基于Dispatch Queue中的处理数、CPU核数以及CPU负荷等当前系统的状态来决定Concurrent Dispatch Queue中并行执行的处理数。

获取Dispatch_Queue

1. dispatch_queue_create函数

dispatch_queue_t queue = dispatch_queue_create("MySerialDispatchQueue", NULL);

第一个参数是名称,可以方便调试。

第二个参数可以指定获取的是Serial Dispatch Queue还是Concurrent Dispatch Queue,参数为DISPATCH_QUEUE_CONCURRENT或者DISPATCH_QUEUE_SERIAL

通过dispatch_queue_create创建的Dispatch Queue在使用结束后必须通过dispatch_release函数释放。这里对应的还有dispatch_retain函数,即Dispatch Queue也像Objective-C的引用计数式内存管理一样,需要通过dispatch_retain函数和dispatch_release函数的引用计数来管理内存。

使用时可以在使用完dispatch_async函数之后立即使用dispatch_release函数进行释放,因为实际上传入dispatch_async函数的Block语法强持有了queue对象,所以不用担心在调用了dispatch_release函数后导致queue被废弃。

2. Main Dispatch Queue/Global Dispatch Queue

Main Dispatch Queue获取主线程执行的Dispatch Queue。

Global Dispatch Queue获取所有应用程序都能够使用的Concurrent Dispatch Queue,它有4个优先级,分别是高、默认、低和后台,执行优先级只是大致的判断,并不能保证实时性。

名称 Dispatch Queue的种类 说明
Main Dispatch Queue Serial 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 执行优先级:后台

各种Dispatch Queue获取方法如下:

//Main Dispatch Queue
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();

//Global Dispatch Queue High Priority
dispatch_queue_t globalDispatchQueueHight = 
    dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

这里其他几个优先级对应的参数为DISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND

关于GCD中注意的问题

数据竞争

如多个线程要修改同一个变量或者文件,最好将这些操作放在同一个Serial Dispatch Queue来执行,但比如读取操作就可以并行执行。

可能会用到的API
1. Serial Dispatch Queue

使用该queue可以有效避免数据竞争问题。

2. Dispatch Group

该API会将追加到Dispatch Group中的多个处理全部执行完成后执行结束处理。

3. dispatch_barrier_async函数

在访问数据库或文件时,想在多个读取操作后,进行一次写入操作,然后再执行多个读取操作,可以使用该函数,简化代码的逻辑。

//获取一个Concurrent Dispatch Queue
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//并行执行多个读操作
dispatch_async(queue, blk0_for_reading);
dispatch_async(queue, blk1_for_reading);
dispatch_async(queue, blk2_for_reading);
dispatch_async(queue, blk3_for_reading);
//这一步会等待上面的所有并行任务执行完毕才会执行
dispatch_barrier_async(queue, blk_for_writing);
//写操作执行完毕后,继续并行执行剩下的读操作
dispatch_async(queue, blk4_for_reading);
dispatch_async(queue, blk5_for_reading);
dispatch_async(queue, blk6_for_reading);
dispatch_async(queue, blk7_for_reading);

死锁

使用dispatch_sync可能会导致死锁,如下代码是在主线程中运行的话就会导致死锁:

//在主线程中获取主线程的Dispatch Queue
dispatch_queue_t queue = disaptch_get_main_queue();
//向main dispatch queue中同步添加一个Block
dispatch_sync(queue, ^{
    NSLog(@"Heollo?");
});

这段代码导致死锁的原因是,dispatch_sync是同步追加的函数,意味着进入该函数后,需要将Block代码添加到Queue中并且执行完成后才会返回,而该函数没有返回之前,主线程就会等待在这一步,并不会执行加入到Main Dispatch Queue中的Block语句,最终导致死锁的产生。

不要使用太多线程

太多的线程会导致消耗大量的内存,在使用过程中,需要结合使用环境,当前需要处理的逻辑等来决定多线程的使用,不要滥用。

GCD的实现

这部分书中着墨不多,主要包括两个部分,一个是常用的Dispatch Queue,另一个是不太引人瞩目的Dispatch Source。

Dispatch Queue

我们可以想象,GCD的实现最基本的需要以下这些工具

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

而苹果的官方说明中提到

通常,应用程序中编写的线程管理用的代码要在系统级实现。

实质上,GCD的实现是在iOS和OS X的核心XNU内核级上实现的。编程人员无论如何努力编写管理线程的代码,在性能方面也不可能胜过XNU内核级所实现的GCD。

以下软件组件用于实现Dispatch Queue

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

其中,我们使用GCD时使用到的API全部为包含在libdispatch库中的C语言函数。

libdispatch部分

Dispatch Queue通过结构体和链表,被实现为FIFO队列,这个队列管理的是使用dispatch_async等函数时传入的Block。该Block首先被包装成disptch_continuation_t类型的结构体中,该结构体存储Block以及Block所属的Dispatch Group和一些其他的信息(通常说的执行上下文)。

Dispatch Queue可通过dispatch_set_target_queue函数来设定,可以将多个Dispatch Queue连接起来,让本来并行执行的任务变为串行执行。但连接串的最后必须设定为Main Dispatch Queue,或各种优先级的Global Dispatch Queue,或是准备用于Seiral Dispatch Queue的各种优先级的Global Dispatch Queue。

其中有8种Global Dispatch Queue:

  • 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,表示不管系统状态如何,都会强制生成线程的Dispatch Queue。

pthreads部分

上面8中Global Dispatch Queue各使用1个pthread_workqueue。GCD初始化时,使用pthread_workqueue_create_np函数生成pthread_workqueue

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

XNU部分

XNU内核持有4中workqueue。

  • WORKQUEUE_HIGH_PRIOQUEUE
  • WORKQUEUE_DEFAULT_PRIOQUEUE
  • WORKQUEUE_LOW_PRIOQUEUE
  • WORKQUEUE_BG_PRIOQUEUE

这4个优先级与Global Dispatch Queue的4中执行优先级相同。

Dispatch Queue执行Bloack的过程
  1. libdispatch从Global Dispatch Queue自身的FIFO队列中取出Dispatch Continuation,调用pthread_workqueue_additem_np函数。将该Global Dispatch Queue自身、符合其优先级的workqueue信息以及为执行Dispatch Continuation的回调函数等传递给参数。
GCD学习_第1张图片
GCD_DispatchQueue执行Block过程.png

XNU内核基于系统状态判断是否要生成线程。如果是Overcommit优先级的则始终生成线程。

workqueue生成的线程在实现用于workqueue的线程计划表中运行,所以与一般线程的上下文切换不同。这里也隐藏着使用GCD的原因。该线程虽然与iOS和OS X中通常使用的线程大致相同,但是有有一部分pthread API不能使用。可参考官方文档《并列编程指南》的”与POSIX线程的互换性“一节。

(翻译真的很烂,猜测是这样的线程生成效率高)

回调

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

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

小结

XNU内核系统级的实现是GCD高性能的最大的原因,由编程人员管理的线程中,想发挥出匹敌GCD的性能是不可能的。

总结

多线程编程是一种易发生各种问题的编程技术,虽然回避这些问题有许多方法,且程序都偏于复杂,但使用多线程编程的好处显而易见,保证应用程序的响应性能,而GCD大大简化了偏于复杂的多线程编程的源代码。

参考

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

并列编程指南

你可能感兴趣的:(GCD学习)