GCD概要及实现原理 - shujucn/ioscoding GitHub Wiki
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代码
});
});
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 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)带来的好处。
参考文档
- 《Objective-C 高级编程iOS 与OS X多线程和内存管理》
- 《Effective Objective-C 2.0》
- 《ConcurrencyProgrammingGuide》