iOS 并行编程:GCD Dispatch Sources

1 简介

dispatch source是一种用于处理事件的数据类型,这些被处理的事件为操作系统中的底层级别。Grand Central Dispatch(GCD)支持如下的dispatch sources类型:

  1. Timer dispatch sources:定时器类型,能够产生周期性的通知事件;
  2. Signal dispatch sources:信号类型,当UNIX信号到底时,能够通知应用程序;
  3. Descriptor sources:文件描述符类型,处理UNIX的文件或socket描述符,如:
    • 数据可读
    • 数据可写
    • 文件被删除、修改或移动
    • 文件的元信息被修改
  4. Process dispatch sources:进程类型,能够通知一些与进程相关的事件类型,如:
    • 当进程退出
    • 当进程调用了fork或exec
    • 当一个信号传递给了进程
  5. Mach port dispatch sources:端口匹配类型,能够通知一些端口事件的类型;
  6. Custom dispatch sources:自定义类型,可以自定义一些事件类型。

Dispatch sources能够替换一些异步的回调函数,特别是用于处理一些与系统相关的事件。当进行dispatch source配置时,可以指定希望监控的事件类型,且可以指定dispatch queue和代码来处理上述的事件,代码的形式有block对象或函数。当一个感兴趣的事件到达时,那么所指定的block或函数将会被调用核执行。

与将任务提交到GCD dispatch queue不同,dispatch sources将会持续对所提交的事件进行监控,除非精确取消所感兴趣的事件。

为了防止事件被积压在dispatch queue中,dispatch sources实现了一种事件合并机制。如果在上一个事件被放进队列和被执行之前,又来了一个新事件,则dispatch source将合并老事件和新事件。合并可能会替换或更新事件的信息,这完全依赖事件的类型。这种机制与UNIX系统信号的不排队机制是一样的。

2 创建Dispatch Sources

创建一个dispatch Sources将涉及两方面的创建过程:创建源事件和dispatch Sources对象。在创建了源事件之后,则可以按如下的步骤创建dispatch Sources对象:

  1. 使用dispatch_source_create函数来创建dispatch Sources对象;
  2. 配置dispatch Sources对象:
  • 为dispatch Sources对象指定一个事件处理句柄;
  • 若是timer sources类型的事件,则可以调用dispatch_source_set_timer函数来设置timer信息。
  1. 配置dispatch source对象的取消句柄,这为可选操作;
  2. 调用dispatch_resume函数开始进行事件的处理。

在一个dispatch sources对象被使用之前,需要对其进行一个附加的配置操作,因为当调用dispatch_source_create函数来创建一个dispatch sources对象后,该对象仍处于suspended(挂起)状态。处于挂起状态的dispatch sources对象是可以接收事件的,但不能这些处理事件。这种机制给了用户时间来配置事件的处理句柄和执行一些附件的配置操作。

2.1 配置Event Handler

为了处理dispatch sources对象所产生的事件,用户必须定义一个event handler(事件处理句柄)来执行这些事件。一个事件处理句柄可以是一个block对象或是一个函数,可以使用dispatch_source_set_event_handler 和 dispatch_source_set_event_handler_f函数来配置事件处理句柄。从而当一个事件到底时,dispatch source对象会将事件处理句柄投放到dispatch queue中进行执行。

事件处理句柄体的内容负责处理任何到底的事件。如果当一个新事件到达时,而前一个事件处理句柄虽被放入队列,但还未被执行,那么dispatch source将合并两个事件;如果当一个或多个事件到达时,前一个事件的处理句柄已经开始执行,则dispatch source将保存这些事件,直到当前的处理句柄执行后,dispatch source再将事件处理句柄投入队列中。

如下所示是block和函数的声明,函数有个参数,可以通过该参数获取一些上下文信息;而block没有任何参数,只能通过block之外的对象获取相关的信息。

1 // Block-based event handler
2 void (^dispatch_block_t)(void)
3 // Function-based event handler
4 void (*dispatch_function_t)(void *)


Function


Description


dispatch_source_get_handle


这个函数返回一个dispatch source监控的数据结构,根据不同的dispatch source类型,则返回的不同语义:

若是描述符类型,则返回一个int类型的文件描述符。

若是信号类型,则返回一个int类型的信号数字。

若是进程类型,则返回一个pid_t类型的数据结构。

若是端口类型,则返回一个端口号。

若是其它类型,则返回的值是不确定的。


dispatch_source_get_data

 

dispatch_source_get_mask

 

比如如下:

1 dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ,
2 myDescriptor, 0, myQueue);
3 dispatch_source_set_event_handler(source, ^{
4 // Get some data from the source variable, which is captured
5 // from the parent context.
6 size_t estimated = dispatch_source_get_data(source);
7 // Continue reading the descriptor...
8 });
9 dispatch_resume(source);

2.2 配置Cancellation Handler

Cancellation handlers(取消处理句柄)用于dispatch source对象释放之前对其内部资源进行清理操作,对于大多数dispatch source对象都不需要配置Cancellation handlers,仅仅当进行了一些自定义的行为时,才需要。但如果用dispatch source对象来处理descriptor 和 Mach port时,则必须配置Cancellation handlers来关闭文件描述符和端口号。

可以在任何时候配置Cancellation handlers,但一般情况是在创建了dispatch source对象之后进行配置。根据block和函数的不同,可以使用dispatch_source_set_cancel_handler 或dispatch_source_set_cancel_handler_f函数进行配置。

如下的例子是进行文件描述符关闭的Cancellation handlers配置操作。

1 dispatch_source_set_cancel_handler(mySource, ^{
2 close(fd); // Close a file descriptor opened earlier.
3 });

2.3 修改目标queue

在创建了dispatch source对象是会 指定event 和 cancellation handlers运行的queue,之后也可以通过dispatch_set_target_queue函数修改运行的queue。但这种改变最好尽快修改,如果一个event handler已经进行排队和等待运行,则该event handler将仍在前一个queue中执行。然而在修改queue之后到达的事件将在新配置的queue中执行。

2.4 内存管理

类似其它的dispatch对象,dispatch source对象也拥有引用计数,其在创建时将其引用计数值初始化为1,其后可以通过dispatch_retain 和 dispatch_release来改变引用计数值。

3 Dispatch Source例子

3.1 Timer

timer为一种定时器事件类型,它能周期性产生事件。但当计算机进入sleep状态时,将暂停所有的timer dispatch source对象,直到计算机恢复后才能恢复dispatch source对象。当使用dispatch_time函数DISPATCH_TIME_NOW常量来设置dispatch source对象时,则timer dispatch source将采用系统默认的时钟周期来触发事件;如果采用dispatch_walltime函数来设置dispatch source对象,则timer dispatch source能够跟踪触发的时间。

如下的例子是每隔30s触发timer dispatch source对象,其触发偏差为1s,并且在启动dispatch source后立即触发timer:

1 dispatch_source_t CreateDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue,dispatch_block_t block)
 2 {
 3     dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER,0, 0, queue);
 4     if (timer)
 5     {
 6         dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval,leeway);
 7         dispatch_source_set_event_handler(timer, block);
 8         dispatch_resume(timer);
 9     }
10     return timer;
11 }
12 void MyCreateTimer()
13 {
14     dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC,1ull * NSEC_PER_SEC, dispatch_get_main_queue(),
15                                                    ^{ MyPeriodicTask(); });
16     // Store it somewhere for later use.
17     if (aTimer)
18     {
19         MyStoreTimer(aTimer);
20     }
21 }

可以使用dispatch_after 或 dispatch_after_f函数来等待一段时间到达后执行一个block或函数,这个时间值可以是相对的或是绝对的,可以根据自己的需要设置。

3.2 Reading Descriptor

为了从文件或网络中读取数据,必须打开一个file或socket,并创建一个DISPATCH_SOURCE_TYPE_READ类型的dispatch source对象。不管什么时候,都不应该把文件描述符配置为阻塞类型的操作。如下是配置一个dispatch source对象来处理读文件事件:

1 dispatch_source_t ProcessContentsOfFile(const char* filename)
 2 {
 3     // Prepare the file for reading.
 4     int fd = open(filename, O_RDONLY);
 5     if (fd == -1)
 6         return NULL;
 7     fcntl(fd, F_SETFL, O_NONBLOCK); // Avoid blocking the read operation
 8     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 9     dispatch_source_t readSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue);
10     if (!readSource)
11     {
12         close(fd);
13         return NULL;
14     }
15     // Install the event handler
16     dispatch_source_set_event_handler(readSource, ^{
17         size_t estimated = dispatch_source_get_data(readSource) + 1;
18         // Read the data into a text buffer.
19         char* buffer = (char*)malloc(estimated);
20         if (buffer)
21         {
22             ssize_t actual = read(fd, buffer, (estimated));
23             Boolean done = MyProcessFileData(buffer, actual); // Process the data.
24             free(buffer); // Release the buffer when done.
25             if (done) // If there is no more data, cancel the source.
26                 dispatch_source_cancel(readSource);
27         }
28     });
29     dispatch_source_set_cancel_handler(readSource, ^{close(fd);}); // Install the cancellation handler
30     dispatch_resume(readSource); // Start reading the file.
31     return readSource;
32 }

3.3 Writing Descriptor

写文件描述符与读文件描述符类似,在配置了写文件描述符后,可创建DISPATCH_SOURCE_TYPE_WRITE类似的dispatch source对象。一旦创建了dispatch source对象之后,系统将立即调用event handler来写入数据到file或socket。当完成了写数据,则可以调用dispatch_source_cancel函数来取消dispatch source对象。同样不应该将文件描述符配置为阻塞类型的操作。如下是配置一个dispatch source对象来处理写文件事件:

1 dispatch_source_t WriteDataToFile(const char* filename)
 2 {
 3     int fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, (S_IRUSR | S_IWUSR | S_ISUID | S_ISGID));
 4     if (fd == -1)
 5         return NULL;
 6     fcntl(fd, F_SETFL); // Block during the write.
 7     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 8     dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, fd, 0, queue);
 9     if (!writeSource)
10     {
11         close(fd);
12         return NULL;
13     }
14     dispatch_source_set_event_handler(writeSource, ^{
15         size_t bufferSize = MyGetDataSize();
16         void* buffer = malloc(bufferSize);
17         size_t actual = MyGetData(buffer, bufferSize);
18         write(fd, buffer, actual);
19         free(buffer);
20         dispatch_source_cancel(writeSource); // Cancel and release the dispatch source when done.
21     });
22     dispatch_source_set_cancel_handler(writeSource, ^{close(fd);});
23     dispatch_resume(writeSource);
24     return (writeSource);
25 }

3.4 File-System Object

如果希望监控文件系统中对象的变化,可以创建DISPATCH_SOURCE_TYPE_VNODE类型的dispatch source对象,从而当一个文件被删除、写入或重命名等操作时,能够得到通知。如下例子为监控文件名字的变化:

1 dispatch_source_t MonitorNameChangesToFile(const char* filename)
 2 {
 3     int fd = open(filename, O_EVTONLY);
 4     if (fd == -1)
 5         return NULL;
 6     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 7     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE,
 8                                                       fd, DISPATCH_VNODE_RENAME, queue);
 9     if (source)
10     {
11         // Copy the filename for later use.
12         int length = strlen(filename);
13         char* newString = (char*)malloc(length + 1);
14         newString = strcpy(newString, filename);
15         dispatch_set_context(source, newString);
16         // Install the event handler to process the name change
17         dispatch_source_set_event_handler(source, ^{
18             const char* oldFilename = (char*)dispatch_get_context(source);
19             MyUpdateFileName(oldFilename, fd);
20         });
21         // Install a cancellation handler to free the descriptor
22         // and the stored string.
23         dispatch_source_set_cancel_handler(source, ^{
24             char* fileStr = (char*)dispatch_get_context(source);
25             free(fileStr);
26             close(fd);
27         });
28         // Start processing events.
29         dispatch_resume(source);
30     }
31     else
32         close(fd);
33     return source;
34 }

3.5 Signals

可以使用UNIX系统的sigaction函数来配置信号处理句柄,只要信号一到达就能立即进行处理。如果仅仅只是希望通知信号的到达,而不是真正想处理信号,则可以使用dispatch source来异步处理信号。

signal dispatch source不可以替代sigaction函数来配置信号处理句柄,sigaction配置的处理句柄能够接收到信号并防止应用程序被终止,signal dispatch source对象仅允许监控信号的到达,它不能用于查询所有的signal类型,特别是不能监控SIGILL、SIGBUS和SIGSEGV信号。如下例子配置dispatch source对象来监听SIGHUP信号:

1 void InstallSignalHandler()
 2 {
 3     // Make sure the signal does not terminate the application.
 4     signal(SIGHUP, SIG_IGN);
 5     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 6     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue);
 7     if (source)
 8     {
 9         dispatch_source_set_event_handler(source, ^{
10             MyProcessSIGHUP();
11         });
12         // Start processing signals
13         dispatch_resume(source);
14     }
15 }

3.6 Process

Process dispatch source对象可以监控子进程的行为,并进行合适的响应。如一个parent进程可以监控其子进程的行为。如下例子为子进程监控父进程的退出状态:

1 void MonitorParentProcess()
 2 {
 3     pid_t parentPID = getppid();
 4     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
 5     dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC,parentPID,  DISPATCH_PROC_EXIT, queue);
 6     if (source)
 7     {
 8         dispatch_source_set_event_handler(source, ^{
 9             MySetAppExitFlag();
10             dispatch_source_cancel(source);
11             dispatch_release(source);
12         });
13         dispatch_resume(source);
14     }
15 }

4 取消dispatch source

Dispatch source对象将一直保持有效状态,除非手动调用dispatch_source_cancel函数来取消它。但取消了dispatch source对象后,将不能再接收到新的事件。一般情况下是取消了dispatch source后,立即释放掉该对象,如:

1 void RemoveDispatchSource(dispatch_source_t mySource)
2 {
3     dispatch_source_cancel(mySource);
4     dispatch_release(mySource);
5 }

取消dispatch source是一个异步操作,即虽然在调用了dispatch_source_cancel函数之后,dispatch source不能再接收到任何事件,但它还可以继续处理在队列中的事件,直到在队列中的最后一个事件被执行完成后,dispatch source才会执行cancellation handler句柄。

5 暂停与恢复dispatch source

可以通过使用dispatch_suspend和 dispatch_resume函数来暂停和恢复事件传递给dispatch source对象。其中要平衡这两个函数的调用。当暂停了一个dispatch source对象之后,所有在这期间传递给dispatch source对象的事件都会被保存,但当有多个同样事件时,在dispatch source对象恢复之后,会将这些事件合并为一个再发送给dispatch source对象,这与UNIX的信号不排队机制是一样的。

时间: 2024-11-08 21:37:28

iOS 并行编程:GCD Dispatch Sources的相关文章

iOS 并行编程:GCD Dispatch Queues

1 简介 1.1 功能       Grand Central Dispatch(GCD)技术让任务并行排队执行,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务.任务可以是一个函数(function)或者是一个block. GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节. GCD中的队列称为dispatch queue,它可以保证先进来的任务先得到执行通过它能够大大简化多线程编程.工程师只要将要执行的任务(执行代码块)放入队列中,GCD将会为需要执行的任务创建

iOS 并行编程:Operation Queues

1 简介 1.1 功能        Operation Queue也是IOS的一种并行编程技术,类似Dispatch Queue可以帮助用户管理多线程.但是Operation Queue将任务封装在NSOperation对象中,从而可以更好的控制任务的执行.并且Dispatch Queue的先入先出的执行方式不同,Operation Queue任务的执行顺序可以控制.其中IOS是将任务交给NSOperation对象进行管理,其中NSOperation是个抽象类,必须被继承,目前系统预定义了两个

iOS 多线程编程gcd全面系统认识

这两天在看<OC高级编程-多线程编程和内存管理>日本人写的那本,该书对arc,block和gcd有了更深层次的解读,非常不错.现在总结一下gcd相关的知识.有关arc和block的参考arc   参考block 网上很多博客都对gcd有过讲解,很多是对gcd的全局队列,主线程队列,创建队列等等,做了单方面的描述,不是很全面系统.下面我们将学习一下系统得gcd.本文主要分为下面几个要点,前几个好点比较好理解,最后可能理解起来有些费劲! ● 什么是gcd,iOS为什么要用多线程 ● 创建线程,序列

iOS 并行编程:Thread

1 创建线程 1.1 NSThread       使用 NSThread 来创建线程有两个可以使用的方法: 1) 使用 detachNewThreadSelector:toTarget:withObject:类方法来生成一个新的线程. 2) 创建一个新的 NSThread 对象,并调用它的 start 方法. 这两种创建线程的技术都在你的应用程序里面新建了一个脱离的线程. 一个脱离的线程意味着当线程退出的时候线程的资源由系统自动回收. 1 -(void) myThreadMainMethod 

iOS多线程编程(四)------ GCD(Grand Central Dispatch)

一.简介 是基于C语言开发的一套多线程开发机制,也是目前苹果官方推荐的多线程开发方法,用起来也最简单,只是它基于C语言开发,并不像NSOperation是面向对象的开发,而是完全面向过程的.如果使用GCD,完全由系统管理线程,我们不需要编写线程代码.只需定义想要执行的任务,然后添加到适当的调度队列(dispatch_queue).GCD会负责创建线程和调度你的任务,系统会直接提供线程管理. 二.任务和队列 GCD中有两个核心概念 (1)任务:执行什么操作 (2)队列:用来存放任务 GCD的使用就

iOS/MacOS多线程编程GCD

GCD和Block一起,使得iOS多线程编程变得简单优雅许多.如此优雅简单的多线程API真希望C和C++标准中也会有 One of the technologies for starting tasks asynchronously is Grand Central Dispatch (GCD). This technology takes the thread management code you would normally write in your own applications a

iOS并发编程对比总结,NSThread,NSOperation,GCD - iOS

1. 多线程概念 进程 正在进行中的程序被称为进程,负责程序运行的内存分配 每一个进程都有自己独立的虚拟内存空间 线程 线程是进程中一个独立的执行路径(控制单元) 一个进程中至少包含一条线程,即主线程 可以将耗时的执行路径(如:网络请求)放在其他线程中执行 创建线程的目的就是为了开启一条新的执行路径,运行指定的代码,与主线程中的代码实现同时运行 1.1 多任务系统调度示意图 说明:每个应用程序由操作系统分配的短暂的时间片(Timeslice)轮流使用CPU,由于CPU对每个时间片的处理速度非常快

iOS并发编程笔记,包含GCD,Operation Queues,Run Loops,如何在后台绘制UI,后台I/O处理,最佳安全实践避免互斥锁死锁优先级反转等,以及如何使用GCD监视进程文件文件夹,并发测试的方案等

iOS并发编程笔记,包含GCD,Operation Queues,Run Loops,如何在后台绘制UI,后台I/O处理,最佳安全实践避免互斥锁死锁优先级反转等,以及如何使用GCD监视进程文件文件夹,并发测试的方案等 线程 使用Instruments的CPU strategy view查看代码如何在多核CPU中执行.创建线程可以使用POSIX 线程API,或者NSThread(封装POSIX 线程API).下面是并发4个线程在一百万个数字中找最小值和最大值的pthread例子: #import

GCD介绍(三): Dispatch Sources

何为Dispatch Sources 简单来说,dispatch source是一个监视某些类型事件的对象.当这些事件发生时,它自动将一个block放入一个dispatch queue的执行例程中. 说的貌似有点不清不楚.我们到底讨论哪些事件类型? 下面是GCD 10.6.0版本支持的事件: Mach port send right state changes. Mach port receive right state changes. External process state chang