您的位置:首页 > 其它

GCD中的一些高级特性

2014-05-06 09:18 375 查看
在开发中执行比较耗时的程序的时候,我们通常都是运用多线程的方式来处理的,在iOS的开发中我们可以使用GCD来处理,一旦用到了多线程就不可避免的会出现线程之间的锁的问题(死锁,读写锁,互斥锁等等)。下面我们就来谈谈在GCD中如何避免多线程操作的读写锁以前其他的一些特性。


1.运用GCD避免读写锁导致线程安全问题

假设,我们有一个
DataManager
类,它用来管理整个程序中的数据,再操作的时候就会出现添加数据和读取数据的操作,如果在同一个时间点,两个线程同时要对数据进行添加操作的话,则就会发生操作错误的问题,为了解决这个我们运用到GCD中的barrier和sync操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

@interface DataManager ()

@property (strong, nonatomic) NSMutableArray *dataArray;
@property (strong, nonatomic) dispatch_queue_t concurrentDataQueue;

@end

@implementation DataManager

+ (instancetype)shareInstance {
static DataManager *dataManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
dataManager = [[DataManager alloc] init];
dataManager.concurrentDataQueue = dispatch_queue_create("com.kingiol.gcd.dataManagerQueue", DISPATCH_QUEUE_CONCURRENT);
});
return dataManager;
}

- (NSArray *)data {
__block NSArray *returnData = nil;
dispatch_sync(self.concurrentDataQueue, ^{
returnData = [_dataArray copy];
});
return returnData;
}

- (void)addData:(id)data {
if (data) {
dispatch_barrier_async(self.concurrentDataQueue, ^{
[_dataArray addObject:data];
});
}
}

@end

通过上述的代码就能够很好的处理多线程对dataArray数组的操作,并且不会出现读写锁的问题。


2.运用GCD的group来监听一组并发是否运行结束

如果当前有3张图片,我们需要从网络上面下载,然后全部下载完成之后,我们需要统一的提醒用户下载完成,这个时候因为每一个图片下载都是并发的,传统的方法是每一个线程各自都有一个bool变量,然后我们去魂环判断这么变量是不是都被置成已完成的状态。现在我们来运用GCD中的group特性来统一处理:

1
2
3
4
5
6
7
8
9
10
11
12
13

dispatch_group_t downloadGroup = dispatch_group_create();
for (NSInteger i = 0; i < 3; i++) {
dispatch_group_enter(downloadGroup);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//下载图片程序
//在图片下载完成的block中调用:
dispatch_group_leave(downloadGroup);
});
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:@"全部下载完成" delegate:nil cancelButtonTitle:@"cancel" otherButtonTitles:nil, nil];
[alertView show];
});
随着移动设备的更新换代,移动设备的性能也不断提高,现在流行的CPU已经进入双核、甚至四核时代。如何充分发挥这些CPU的性能,会变得越来越重要。在iOS中如果想要充分利用多核心CPU的优势“”阅读器并发编程之GCD在《并发编程之Operation Queue》中讲了Cocoa并发编程中的Operation Queue,了解了Operation Queue是一个面向对象的并发编程接口,它支持并发数,线程优先级,任务优先级,任务依赖关系等多种配置,可以方便满足各种复杂的多任务处理场景。本篇将接着讲另一种并发编程机制 – GCD(Grand Central Dispatch)。iOS4.0中首度引入GCD,GCD是管理任务执行的一项技术,它使得我们对多任务处理变得更加方便和有效。它支持同步或异步任务处理,串行或并行的处理队列(Dispath Queue),非系统调用的信号量机制,定时任务处理,进程、文件或网络的监听任务等。这个庞大的任务处理技术大大减少了线程的管理工作,使基于任务的开发变得更加高效。 Dispatch QueueDispatch Queue是一个任务执行队列,可以让你异步或同步地执行多个Block或函数。Dispatch Queue是FIFO的,即先入队的任务总会先执行。目前有三种类型的Dispath Queue:1.串行队列(Serial dispatch queue)2.并发队列(Concurrent dispatch queue)3.主队列(Main dispatch queue) 串行队列串行队列一次只能处理一个任务,可以由用户调用dispatch_queue_create创建:dispatch_queue_t queue; 
queue = dispatch_queue_create("com.example.MyQueue", NULL); 
dispatch_queue_create第一个参数是串行队列标识,一般用反转域名的格式表示以防冲突;第二个参数是queue的类型,设为NULL时默认是DISPATCH_QUEUE_SERIAL,将创建串行队列,在必要情况下,你可以将其设置为DISPATCH_QUEUE_CONCURRENT来创建自定义并行队列。 并行队列并行队列可以同时处理多个任务,在不得以的情况下可以用dispatch_queue_create创建,但一般我们都要用系统预定义的并行队列,即全局队列(Global Concurrent Dispatch Queues)。目前系统预定义了四个不同运行优先级的全局队列,我们可以通过dispatch_get_global_queue来获取它们。dispatch_queue_t aQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
 dispatch_get_global_queue第一个参数是队列的优先级,分别对应四个全局队列:DISPATCH_QUEUE_PRIORITY_HIGHDISPATCH_QUEUE_PRIORITY_DEFAULTDISPATCH_QUEUE_PRIORITY_LOWDISPATCH_QUEUE_PRIORITY_BACKGROUND dispatch_get_global_queue中第二个参数目前系统保留,请设置为0即可。 主队列主队列是一个特殊的队列,它是系统预定义的运行在主线程的一个Dispatch Queue。可以通过dispatch_get_main_queue来获取唯一的主队列。主队列一般运行一些需要与主线程同步的一些短时任务。dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
 获取当前队列你可以通过dispatch_get_current_queue获取运行时的队列:dispatch_queue_t currentQueue = dispatch_get_current_queue(); 
如果在队列执行任务中调用,返回执行此任务的队列;如果在主线程中调用,将返回主队列;如果在一般线程(非主线程线程非队列执行任务)中调用,返回DISPATCH_QUEUE_PRIORITY_DEFAULT全局队列。 在队列中运行任务你可以随时向一个队列中添加一个新任务,只需要调用一下dispatch_async即可:dispatch_async(aQueue, ^{ 
    //Do some work; 
}); 
 dispatch_async中的任务是异步执行的,就是说dispatch_async添加任务到执行队列后会立刻返回,而不会等待任务执行完成。然而,必要的话,你也可以调用dispatch_sync来同步的执行一个任务:dispatch_sync(aQueue, ^{ 
    //Do some work; 
}); 
dispatch_sync会阻塞当前线程直到提交的任务完全执行完毕。 Dispatch Queue的内存管理除了系统预定义的Dispatch Queue,我们自定义的Dispatch Queue需要手动的管理它的内存。dispatch_retain和dispatch_release这两个函数可以控制Dispatch Queue的引用计数(同时可以控制后面会讲到的Dispatch Group和Dispatch Source的引用计数)。当Dispatch Queue引用计数变为0后,就会调用finalizer,finalizer是Dispatch Queue销毁前调用的函数,用来清理Dispatch Queue的相关资源。可以用dispatch_set_finalizer_f函数来设置Dispatch Queue的finalizer,这个函数同时可以设置Dispatch Group和Dispatch Source的销毁函数(后面会讲到)。void dispatch_set_finalizer_f(dispatch_object_t object, dispatch_function_t finalizer); 
 Dispatch Queue的上下文环境数据我们可以为每个Dispatch Queue设置一个自定义的上下文环境数据,调用dispatch_set_context来实现。同时我们也可以用dispatch_get_context获取这个上下文环境数据,这个函数同时可以设置Dispatch Group和Dispatch Source的上下文环境数据(后面会讲到)。void dispatch_set_context(dispatch_object_t object,void *context); 
void * dispatch_get_context(dispatch_object_t object); 
注意Dispatch Queue并不保证这个context不会释放,不会对它进行内存管理控制。我们需要自行管理context的内存分配和释放。一般我们非配内存设置context后,可以在finalizer里释放context占有的内存。 并行执行循环在编程过程中,我们经常会用到for循环,而且for循环要做很多相关的任务。比如:for (i = 0; i < count; i++) { 
   //do a lot of work here. 
   doSomething(i); 

 如果for循环中处理的任务是可并发的,显然放到一个线程中处理是很慢的,GCD提供两个函数dispatch_apply和dispatch_apply_f,dispatch_apply是用于Block的,而dispatch_apply_f可以用于c函数,它们可以替代可并发的for循环,来并行的运行而提高执行效率。dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
dispatch_apply(count, queue, ^(size_t i) { 
   //do a lot of work here. 
   doSomething(i); 
}); 
 Dispatch Group有时候我们进行下一步操作,而这个操作需要等待几个任务处理完毕后才能继续,这时我们就需要用的Dispatch Group(类似thread join)。我们可以把若干个任务放到一个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, ^{ 
   // Some asynchronous work 
}); 
dispatch_group_async跟dispatch_async一样,会把任务放到queue中执行,不过它比dispatch_async多做了一步操作就是把这个任务和group相关联。 把一些任务放到Dispatch Group后,我们就可以调用dispatch_group_wait来等待这些任务完成。若任务已经全部完成或为空,则直接返回,否则等待所有任务完成后返回。注意:返回后group会清空。dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 
// Do some work after. 
dispatch_release(group); 
 Dispatch信号量很多程序设计都设计到信号量,生产者-消费者模型在多线程编程中会频繁的使用。GCD提供了自己的一套信号量机制。dispatch_semaphore_t sema = dispatch_semaphore_create(RESOURCE_SIZE); 
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 
//do some work here. 
dispatch_semaphore_signal(sema); 
dispatch_semaphore_wait用来获取信号量,若信号量为0,则等待直到信号量大于0。在处理任务结束后,应释放相关资源并调用dispatch_semaphore_signal使信号量增加1个。 Dispatch SourceDispatch Source是GCD中监听一些系统事件的有个Dispatch对象,它包括定时器、文件监听、进程监听、Mach port监听等类型。 可以通过dispatch_source_create创建一个Dispatch Source:dispatch_source_t dispatch_source_create( 
   dispatch_source_type_t type, 
   uintptr_t handle, 
   unsigned long mask, 
   dispatch_queue_t queue); 
这里可以指定Dispatch Source的类型,type可以为文件读或写、进程监听等。handle为监听对象的句柄,如果是文件就是文件描述符,如果是进程就是进程ID。mask用来指定一些想要监听的事件,它的意义取决于type。queue指定事件处理的任务队列。 创建好Dispatch Source后,我们要为Dispatch Source设置一个事件处理模块。可以用dispatch_source_set_event_handler或dispatch_source_set_event_handler_f来设置:void dispatch_source_set_event_handler( 
   dispatch_source_t source, 
   dispatch_block_t handler); 
 设置好Dispatch Source后就可以调用dispatch_resume来启动监听。如果相应的事件发生就会触发事件处理模块。 同时我们也可以设置一个取消处理模块:dispatch_source_set_cancel_handler(mySource, ^{ 
   close(fd); // Close a file descriptor opened earlier. 
}); 
取消处理模块会在Dispatch Source取消时调用。 下面介绍一下主要的Dispatch Source类型和示例代码。 定时器定时器Dispatch Source可以每隔一个固定的时间处理一下任务。dispatch_source_t CreateDispatchTimer(uint64_t interval, 
              uint64_t leeway, 
              dispatch_queue_t queue, 
              dispatch_block_t block) 

   dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 
                                                     0, 0, queue); 
   if (timer) 
   { 
      dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway); 
      dispatch_source_set_event_handler(timer, block); 
      dispatch_resume(timer); 
   } 
   return timer; 

 
void MyCreateTimer() 

   dispatch_source_t aTimer = CreateDispatchTimer(30ull * NSEC_PER_SEC, 
                               1ull * NSEC_PER_SEC, 
                               dispatch_get_main_queue(), 
                               ^{ MyPeriodicTask(); }); 
 
   // Store it somewhere for later use. 
    if (aTimer) 
    { 
        MyStoreTimer(aTimer); 
    } 

 dispatch_after和dispatch_after_f有时候我们只想处理一次延迟任务,可以用dispatch_after和dispatch_after_fvoid dispatch_after( 
   dispatch_time_t when, 
   dispatch_queue_t queue, 
   dispatch_block_t block); 
 监听文件事件监听文件事件分好几个类型,有读、写、属性的监听。 读取文件dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, queue); 
dispatch_source_set_event_handler(source, ^{ 
   // Get some data from the source variable, which is captured 
   // from the parent context. 
   size_t estimated = dispatch_source_get_data(source); 
   // Continue reading the descriptor... 
}); 
dispatch_resume(source); 
 写文件dispatch_source_t writeSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_WRITE, 
                        fd, 0, queue); 
if (!writeSource) 

    close(fd); 
    return NULL; 

 
dispatch_source_set_event_handler(writeSource, ^{ 
    size_t bufferSize = MyGetDataSize(); 
    void* buffer = malloc(bufferSize); 
 
    size_t actual = MyGetData(buffer, bufferSize); 
    write(fd, buffer, actual); 
 
    free(buffer); 
 
    // Cancel and release the dispatch source when done. 
    dispatch_source_cancel(writeSource); 
}); 
 监听文件属性dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, 
            fd, DISPATCH_VNODE_RENAME, queue); 
if (source) 

  // Copy the filename for later use. 
  int length = strlen(filename); 
  char* newString = (char*)malloc(length + 1); 
  newString = strcpy(newString, filename); 
  dispatch_set_context(source, newString); 
 
  // Install the event handler to process the name change 
  dispatch_source_set_event_handler(source, ^{ 
        const char*  oldFilename = (char*)dispatch_get_context(source); 
        MyUpdateFileName(oldFilename, fd); 
  }); 
 
  // Install a cancellation handler to free the descriptor 
  // and the stored string. 
  dispatch_source_set_cancel_handler(source, ^{ 
      char* fileStr = (char*)dispatch_get_context(source); 
      free(fileStr); 
      close(fd); 
  }); 
 
  // Start processing events. 
  dispatch_resume(source); 

else 
  close(fd); 
 监听进程事件dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_PROC, 
                                                  parentPID, DISPATCH_PROC_EXIT, queue); 
if (source) 

   dispatch_source_set_event_handler(source, ^{ 
     MySetAppExitFlag(); 
     dispatch_source_cancel(source); 
     dispatch_release(source); 
   }); 
   dispatch_resume(source); 

 监听中断信号dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGHUP, 0, queue); 
if (source) 

  dispatch_source_set_event_handler(source, ^{ 
     MyProcessSIGHUP(); 
  }); 
 
  // Start processing signals 
  dispatch_resume(source); 

 参考文献Dispatch Queues:https://developer.apple.com/library/mac/documentation/general/conceptual/concurrencyprogrammingguide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW1Dispatch Sources:https://developer.apple.com/library/mac/documentation/general/conceptual/concurrencyprogrammingguide/GCDWorkQueues/GCDWorkQueues.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: