并发编程之Operation Queue和GCD

来源:互联网 发布:基金止盈软件 编辑:程序博客网 时间:2024/05/17 04:11

并发编程之GCD

在《并发编程之Operation Queue》中讲了Cocoa并发编程中的OperationQueue,了解了OperationQueue是一个面向对象的并发编程接口,它支持并发数,线程优先级,任务优先级,任务依赖关系等多种配置,可以方便满足各种复杂的多任务处理场景。本篇将接着讲另一种并发编程机制 – GCD(Grand CentralDispatch)。iOS4.0中首度引入GCD,GCD是管理任务执行的一项技术,它使得我们对多任务处理变得更加方便和有效。它支持同步或异步任务处理,串行或并行的处理队列(DispathQueue),非系统调用的信号量机制,定时任务处理,进程、文件或网络的监听任务等。这个庞大的任务处理技术大大减少了线程的管理工作,使基于任务的开发变得更加高效。

 

DispatchQueue

Dispatch Queue是一个任务执行队列,可以让你异步或同步地执行多个Block或函数。DispatchQueue是FIFO的,即先入队的任务总会先执行。目前有三种类型的Dispath Queue:

1.串行队列(Serial dispatch queue)

2.并发队列(Concurrent dispatch queue)

3.主队列(Main dispatch queue)

 

串行队列

串行队列一次只能处理一个任务,可以由用户调用dispatch_queue_create创建:

  1. dispatch_queue_t queue; 
  2. queue dispatch_queue_create("com.example.MyQueue"NULL); 

dispatch_queue_create第一个参数是串行队列标识,一般用反转域名的格式表示以防冲突;第二个参数是queue的类型,设为NULL时默认是DISPATCH_QUEUE_SERIAL,将创建串行队列,在必要情况下,你可以将其设置为DISPATCH_QUEUE_CONCURRENT来创建自定义并行队列。

 

并行队列

并行队列可以同时处理多个任务,在不得以的情况下可以用dispatch_queue_create创建,但一般我们都要用系统预定义的并行队列,即全局队列(Global Concurrent DispatchQueues)。目前系统预定义了四个不同运行优先级的全局队列,我们可以通过dispatch_get_global_queue来获取它们。

  1. dispatch_queue_t aQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 

 

dispatch_get_global_queue第一个参数是队列的优先级,分别对应四个全局队列:

DISPATCH_QUEUE_PRIORITY_HIGH

DISPATCH_QUEUE_PRIORITY_DEFAULT

DISPATCH_QUEUE_PRIORITY_LOW

DISPATCH_QUEUE_PRIORITY_BACKGROUND

 

dispatch_get_global_queue中第二个参数目前系统保留,请设置为0即可。

 

主队列

主队列是一个特殊的队列,它是系统预定义的运行在主线程的一个DispatchQueue。可以通过dispatch_get_main_queue来获取唯一的主队列。主队列一般运行一些需要与主线程同步的一些短时任务。

  1. dispatch_queue_t mainQueue dispatch_get_main_queue(); 

 

获取当前队列

你可以通过dispatch_get_current_queue获取运行时的队列:

  1. dispatch_queue_t currentQueue dispatch_get_current_queue(); 

如果在队列执行任务中调用,返回执行此任务的队列;如果在主线程中调用,将返回主队列;如果在一般线程(非主线程线程非队列执行任务)中调用,返回DISPATCH_QUEUE_PRIORITY_DEFAULT全局队列。

 

在队列中运行任务

你可以随时向一个队列中添加一个新任务,只需要调用一下dispatch_async即可:

  1. dispatch_async(aQueue, ^{ 
  2.     //Do some work; 
  3. }); 

 

dispatch_async中的任务是异步执行的,就是说dispatch_async添加任务到执行队列后会立刻返回,而不会等待任务执行完成。然而,必要的话,你也可以调用dispatch_sync来同步的执行一个任务:

  1. dispatch_sync(aQueue, ^{ 
  2.     //Do some work; 
  3. }); 

dispatch_sync会阻塞当前线程直到提交的任务完全执行完毕。

 

Dispatch Queue的内存管理

除了系统预定义的Dispatch Queue,我们自定义的DispatchQueue需要手动的管理它的内存。dispatch_retain和dispatch_release这两个函数可以控制DispatchQueue的引用计数(同时可以控制后面会讲到的Dispatch Group和DispatchSource的引用计数)。当DispatchQueue引用计数变为0后,就会调用finalizer,finalizer是DispatchQueue销毁前调用的函数,用来清理DispatchQueue的相关资源。可以用dispatch_set_finalizer_f函数来设置DispatchQueue的finalizer,这个函数同时可以设置Dispatch Group和DispatchSource的销毁函数(后面会讲到)。

  1. void dispatch_set_finalizer_f(dispatch_object_t object, dispatch_function_t finalizer); 

 

Dispatch Queue的上下文环境数据

我们可以为每个DispatchQueue设置一个自定义的上下文环境数据,调用dispatch_set_context来实现。同时我们也可以用dispatch_get_context获取这个上下文环境数据,这个函数同时可以设置Dispatch Group和DispatchSource的上下文环境数据(后面会讲到)。

  1. void dispatch_set_context(dispatch_object_t object,void *context); 
  2. void dispatch_get_context(dispatch_object_t object); 

注意DispatchQueue并不保证这个context不会释放,不会对它进行内存管理控制。我们需要自行管理context的内存分配和释放。一般我们非配内存设置context后,可以在finalizer里释放context占有的内存。

 

并行执行循环

在编程过程中,我们经常会用到for循环,而且for循环要做很多相关的任务。比如:

  1. for (i 0; count; i++) 
  2.    //do lot of work here. 
  3.    doSomething(i); 

 

如果for循环中处理的任务是可并发的,显然放到一个线程中处理是很慢的,GCD提供两个函数dispatch_apply和dispatch_apply_f,dispatch_apply是用于Block的,而dispatch_apply_f可以用于c函数,它们可以替代可并发的for循环,来并行的运行而提高执行效率。

  1. dispatch_queue_t queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  2. dispatch_apply(count, queue, ^(size_t i) 
  3.    //do lot of work here. 
  4.    doSomething(i); 
  5. }); 

 

DispatchGroup

有时候我们进行下一步操作,而这个操作需要等待几个任务处理完毕后才能继续,这时我们就需要用的DispatchGroup(类似thread join)。我们可以把若干个任务放到一个Dispatch Group中:

  1. dispatch_queue_t queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
  2. dispatch_group_t group dispatch_group_create(); 
  3. dispatch_group_async(group, queue, ^{ 
  4.    // Some asynchronous work 
  5. }); 

dispatch_group_async跟dispatch_async一样,会把任务放到queue中执行,不过它比dispatch_async多做了一步操作就是把这个任务和group相关联。

 

把一些任务放到DispatchGroup后,我们就可以调用dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。注意:返回后group会清空。

  1. dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 
  2. // Do some work after. 
  3. dispatch_release(group); 

 

Dispatch信号量

很多程序设计都设计到信号量,生产者-消费者模型在多线程编程中会频繁的使用。GCD提供了自己的一套信号量机制。

  1. dispatch_semaphore_t sema dispatch_semaphore_create(RESOURCE_SIZE); 
  2. dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 
  3. //do some work here. 
  4. dispatch_semaphore_signal(sema); 

dispatch_semaphore_wait用来获取信号量,若信号量为0,则等待直到信号量大于0。在处理任务结束后,应释放相关资源并调用dispatch_semaphore_signal使信号量增加1个。

 

DispatchSource

Dispatch Source是GCD中监听一些系统事件的有个Dispatch对象,它包括定时器、文件监听、进程监听、Machport监听等类型。

 

可以通过dispatch_source_create创建一个Dispatch Source:

  1. dispatch_source_t dispatch_source_create( 
  2.    dispatch_source_type_t type, 
  3.    uintptr_t handle, 
  4.    unsigned long mask, 
  5.    dispatch_queue_t queue); 

这里可以指定DispatchSource的类型,type可以为文件读或写、进程监听等。handle为监听对象的句柄,如果是文件就是文件描述符,如果是进程就是进程ID。mask用来指定一些想要监听的事件,它的意义取决于type。queue指定事件处理的任务队列。

 

创建好Dispatch Source后,我们要为DispatchSource设置一个事件处理模块。可以用dispatch_source_set_event_handler或dispatch_source_set_event_handler_f来设置:

  1. void dispatch_source_set_event_handler( 
  2.    dispatch_source_t source, 
  3.    dispatch_block_t handler); 

 

设置好DispatchSource后就可以调用dispatch_resume来启动监听。如果相应的事件发生就会触发事件处理模块。

 

同时我们也可以设置一个取消处理模块:

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

取消处理模块会在Dispatch Source取消时调用。

 

下面介绍一下主要的Dispatch Source类型和示例代码。

 

定时器

定时器Dispatch Source可以每隔一个固定的时间处理一下任务。

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

 

dispatch_after和dispatch_after_f

有时候我们只想处理一次延迟任务,可以用dispatch_after和dispatch_after_f

  1. void dispatch_after( 
  2.    dispatch_time_t when, 
  3.    dispatch_queue_t queue, 
  4.    dispatch_block_t block); 

 

监听文件事件

监听文件事件分好几个类型,有读、写、属性的监听。

 

读取文件

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

 

写文件

  1. dispatch_source_t writeSource dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, 
  2.                         fd, 0, queue); 
  3. if (!writeSource) 
  4.     close(fd); 
  5.     return NULL; 
  6.  
  7. dispatch_source_set_event_handler(writeSource, ^{ 
  8.     size_t bufferSize MyGetDataSize(); 
  9.     voidbuffer malloc(bufferSize); 
  10.  
  11.     size_t actual MyGetData(buffer, bufferSize); 
  12.     write(fd, buffer, actual); 
  13.  
  14.     free(buffer); 
  15.  
  16.     // Cancel and release the dispatch source when done. 
  17.     dispatch_source_cancel(writeSource); 
  18. }); 

 

监听文件属性

  1. dispatch_source_t source dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, 
  2.             fd, DISPATCH_VNODE_RENAME, queue); 
  3. if (source) 
  4.   // Copy the filename for later use. 
  5.   int length strlen(filename); 
  6.   charnewString (char*)malloc(length 1); 
  7.   newString strcpy(newString, filename); 
  8.   dispatch_set_context(source, newString); 
  9.  
  10.   // Install the event handler to process the name change 
  11.   dispatch_source_set_event_handler(source, ^{ 
  12.         const char oldFilename (char*)dispatch_get_context(source); 
  13.         MyUpdateFileName(oldFilename, fd); 
  14.   }); 
  15.  
  16.   // Install cancellation handler to free the descriptor 
  17.   // and the stored string. 
  18.   dispatch_source_set_cancel_handler(source, ^{ 
  19.       charfileStr (char*)dispatch_get_context(source); 
  20.       free(fileStr); 
  21.       close(fd); 
  22.   }); 
  23.  
  24.   // Start processing events. 
  25.   dispatch_resume(source); 
  26. else 
  27.   close(fd); 

 

监听进程事件

  1. dispatch_source_t source dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, 
  2.                                                   parentPID, DISPATCH_PROC_EXIT, queue); 
  3. if (source) 
  4.    dispatch_source_set_event_handler(source, ^{ 
  5.      MySetAppExitFlag(); 
  6.      dispatch_source_cancel(source); 
  7.      dispatch_release(source); 
  8.    }); 
  9.    dispatch_resume(source); 

 

监听中断信号

  1. dispatch_source_t source dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue); 
  2. if (source) 
  3.   dispatch_source_set_event_handler(source, ^{ 
  4.      MyProcessSIGHUP(); 
  5.   }); 
  6.  
  7.   // Start processing signals 
  8.   dispatch_resume(source); 

 

参考文献

Dispatch Queues:https://developer.apple.com/library/mac/documentation/general/conceptual/concurrencyprogrammingguide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1

Dispatch Sources:https://developer.apple.com/library/mac/documentation/general/conceptual/concurrencyprogrammingguide/GCDWorkQueues/GCDWorkQueues.html

0 0
原创粉丝点击