IOS并发编程GCD
iOS有三种多线程编程的技术
GCD
异步调用的实现中往往采用并发机制,然而并不是所有异步都是并发机制,也有可能是其他机制,比如一些依靠中断进行的操作。
1.GCD的一个重要概念是队列(dispatch queue),它的核心理念:将长期运行的任务拆分成多个工作单元,并将这些单元添加到dispath queue中,系统会为我们管理这些dispath queue,为我们在多个线程上执行工作单元,我们不需要直接启动和管理后台线程。
2.系统提供了许多预定义的dispath queue,包括可以保证始终在主线程上执行工作的dispath queue。也可以创建自己的dispath queue,而且可以创建任意多个。GCD的dispath queue严格遵循FIFO(先进先出)原则,添加到dispath queue的工作单元将始终按照加入dispath queue的顺序启动。
3.dispatch queue按先进先出的顺序,串行或并发地执行任务
1> serial dispatch queue一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务
2> concurrent dispatch queue则尽可能多地启动任务并发执行
三种队列及管理:
串行队 Serial
2> 你必须显式地创建和管理所有你使用的串行queue,应用可以创建任意数量的串行queue,但不要为了同时执行更多任务而创建更多的串行queue。如果你需要并发地执行大量任务,应该把任务提交到全局并发queue
运行时获得公共队 Concurrent
GCD提供了函数让应用访问几个公共dispatch queue:
1> 使用dispatch_get_current_queue函数作为调试用途,或者测试当前queue的标识。在block对象中调用这个函数会返回block提交到的queue(这个时候queue应该正在执行中)。在block对象之外调用这个函数会返回应用的默认并发queue。
2> 使用dispath_get_global_queue可以获取去得到concurrent dispatch queues队列,如下:
let globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数目前未使用到,默认0即可
此外还有DISPATCH_QUEUE_PRIORITY_BACKGROUND
Main dispatch queue
// 异步下载图片 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSURL *url = [NSURL URLWithString:@"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"]; UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]]; // 回到主线程显示图片 dispatch_async(dispatch_get_main_queue(), ^{ self.imageView.image = image; }); });
创建队列:
let queue = dispatch_queue_create("gcdtest.rongfzh.yc", nil) //串行 let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_SERIAL) //串行 let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT) //并行
添加任务入队
1.添加单个任务到queue
1> 异步添加任务
你可以异步或同步地添加一个任务到Queue,尽可能地使用dispatch_async或dispatch_async_f函数异步地调度任务。因为添加任务到Queue中时,无法确定这些代码什么时候能够执行(GCD会自动根据任务在多核处理器上分配资源,优化程序)。因此异步地添加block或函数,可以让你立即调度这些代码的执行,然后调用线程可以继续去做其它事情。特别是应用主线程一定要异步地 dispatch 任务,这样才能及时地响应用户事件
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { // 耗时的操作 dispatch_async(dispatch_get_main_queue(), { // 更新界面 }) })
2> 同步添加任务
少数时候你可能希望同步地调度任务,以避免竞争条件或其它同步错误。 使用dispatch_sync和dispatch_sync_f函数同步地添加任务到Queue,这两个函数会阻塞当前调用线程,直到相应任务完成执行。注意:绝对不要在任务中调用 dispatch_sync或dispatch_sync_f函数,并同步调度新任务到当前正在执行的 queue。对于串行queue这一点特别重要,因为这样做肯定会导致死锁;而并发queue也应该避免这样做。
// 调用前,查看下当前线程 NSLog("当前调用线程:%@", NSThread.currentThread()) // 创建一个串行queue let queue = dispatch_queue_create("cn.itcast.queue", nil) dispatch_async(queue, { NSLog("开启了一个异步任务,当前线程:%@", NSThread.currentThread()) } ) dispatch_sync(queue, { NSLog("开启了一个同步任务,当前线程:%@", NSThread.currentThread()) } )
打印结果:
2015-05-09 00:49:27.539 ImageLoaderExample[2122:81150] 当前调用线程:<NSThread: 0x7fcbdad27890>{number = 1, name = main}
2015-05-09 00:49:27.541 ImageLoaderExample[2122:81222] 开启了一个异步任务,当前线程:<NSThread: 0x7fcbdae66a30>{number = 2, name = (null)}
2015-05-09 00:49:27.541 ImageLoaderExample[2122:81150] 开启了一个同步任务,当前线程:<NSThread: 0x7fcbdad27890>{number = 1, name = main}
暂停和继续queue
我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。
dispatch_group_async的使用
dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。下面是一段例子代码:
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) let group = dispatch_group_create() dispatch_group_async(group, queue, { NSThread.sleepForTimeInterval(1) NSLog("group1"); }) dispatch_group_async(group, queue, { NSThread.sleepForTimeInterval(2) NSLog("group2"); }) dispatch_group_async(group, queue, { NSThread.sleepForTimeInterval(3) NSLog("group3"); }) dispatch_group_async(group, queue, { NSThread.sleepForTimeInterval(3) NSLog("group34"); }) dispatch_group_notify(group, dispatch_get_main_queue(), { NSLog("updateUi"); });
2015-05-08 23:23:40.344 ImageLoaderExample[1504:48633] group1
2015-05-08 23:23:41.340 ImageLoaderExample[1504:48631] group2
2015-05-08 23:23:42.340 ImageLoaderExample[1504:48638] group34
2015-05-08 23:23:42.340 ImageLoaderExample[1504:48634] group3
2015-05-08 23:23:42.341 ImageLoaderExample[1504:48547] updateUi
// 根据url获取UIImage - (UIImage *)imageWithURLString:(NSString *)urlString { NSURL *url = [NSURL URLWithString:urlString]; NSData *data = [NSData dataWithContentsOfURL:url]; // 这里并没有自动释放UIImage对象 return [[UIImage alloc] initWithData:data]; } - (void)downloadImages { dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 异步下载图片 dispatch_async(queue, ^{ // 创建一个组 dispatch_group_t group = dispatch_group_create(); __block UIImage *image1 = nil; __block UIImage *image2 = nil; // 关联一个任务到group dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 下载第一张图片 NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg"; image1 = [self imageWithURLString:url1]; }); // 关联一个任务到group dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 下载第二张图片 NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg"; image2 = [self imageWithURLString:url2]; }); // 等待组中的任务执行完毕,回到主线程执行block回调 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ self.imageView1.image = image1; self.imageView2.image = image2; // 在这里释放图片资源 [image1 release]; [image2 release]; }); // 释放group dispatch_release(group); }); }
dispatch_barrier_async的使用
在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行NSLog("begin"); let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT) dispatch_async(queue, { NSThread.sleepForTimeInterval(2) NSLog("dispatch_async1"); } ); dispatch_async(queue, { NSThread.sleepForTimeInterval(4) NSLog("dispatch_async2"); } ); dispatch_barrier_async(queue, { NSLog("dispatch_barrier_async"); NSThread.sleepForTimeInterval(4) } ); dispatch_async(queue, { NSThread.sleepForTimeInterval(1) NSLog("dispatch_async3"); } ); dispatch_async(queue, { NSThread.sleepForTimeInterval(1) NSLog("dispatch_async4"); } );
2015-05-08 23:39:11.729 ImageLoaderExample[1635:55195] begin
2015-05-08 23:39:13.731 ImageLoaderExample[1635:55268] dispatch_async1
2015-05-08 23:39:15.730 ImageLoaderExample[1635:55267] dispatch_async2
2015-05-08 23:39:15.731 ImageLoaderExample[1635:55267] dispatch_barrier_async
2015-05-08 23:39:20.742 ImageLoaderExample[1635:55268] dispatch_async4
2015-05-08 23:39:20.742 ImageLoaderExample[1635:55267] dispatch_async3
dispatch_after 用于延迟执行一些代码
,dispatch_block_t ,可用于取消dispatch_after 的延时任务
纳秒 NSEC_PER_SEC = 109秒
//let delayInSeconds:Int64 = 1000,000,000 * 2 // Int64(2 * NSEC_PER_SEC) let time = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * NSEC_PER_SEC)) dispatch_after(time, dispatch_get_main_queue()){ //... }
dispatch_once 只执行一次,用于一些单例。
+ (instancetype)sharedInstance { static id sharedInstance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [self new]; }); return sharedInstance; }
dispatch_apply 并发地执行循环迭代
如果你使用循环执行固定次数的迭代, 并发dispatch queue可能会提高性能。可以指定串行抑或并行
和普通for循环一样,dispatch_apply和dispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。
let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_CONCURRENT) dispatch_apply(30, queue, {print("|*\($0)*|")})
打印结果:
|*0*||*4*||*5*|||||****6312****||||*|7||***|8|9**|*1|*|01|*1|*|**1|||12**3*15*||*1|*16|4*|**|1|||**|711**98|2**|0||**||2|*|1*2**2223|*4*|*||||**|2|2*56*2**2|7|8*||**|29*|
let queue = dispatch_queue_create("gcdtest.rongfzh.yc", DISPATCH_QUEUE_SERIAL) dispatch_apply(30, queue, {print("|*\($0)*|")})
打印结果:
|*0*||*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*|
更细粒度的排他控制:dispatch_semaphore
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // Create dispatch_semaphore // semaphore value初始化为1 / 保证可访问NSMutableArray类对象的线程同时只有一个 dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); NSMutableArray *array = [[NSMutableArray alloc] init]; for (int i = 0; i < 100000; i++) { dispatch_async(queue, ^{ // Waiting for dispatch semaphore, 直到semaphore值达到大于等于1 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); // 由于Dispatch semaphore的计数值达到大于等于1 // 所以将Dispatch semaphore的计数值减1 // dispatch_semaphore_wait函数执行返回 // 即执行到此时的Dispatch semaphore计数值恒为0 // 由于可访问NSMutableArray类对象的线程只有一个 // 因此可安全进行更新 [array addObject:[NSNumber numberWithInt:i]]; // 排他控制处理结束 // 所以通过dispatch_semaphore_signal函数 // 将Dispatch semaphore的计数值加1 // 如果有通过dispatch_semaphore_wait函数等待Dispatch semaphore的 // 计数值增加的线程,由最先等待的线程执行 dispatch_semaphore_signal(semaphore); }); } 26 dispatch_release(semaphore);
Dispatch I/O:并发读取文件数据,高效率读取文件:
// 并发读取文件原理 dispatch_async(queue, ^{/* 读取0-8191字节*/}); dispatch_async(queue, ^{/* 读取8192-16383字节*/}); dispatch_async(queue, ^{/* 读取163784-24575字节*/}); dispatch_async(queue, ^{/* 读取24576-32767字节*/}); dispatch_async(queue, ^{/* 读取32768-40959字节*/}); dispatch_async(queue, ^{/* 读取40960-49151字节*/}); dispatch_async(queue, ^{/* 读取49152-57343字节*/}); dispatch_async(queue, ^{/* 读取57344-65535字节*/}); // 实例代码如下 dispatch_queue_t pipe_q = dispatch_queue_create("PipQ", NULL); dispatch_io_t pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pip_q, &(int err){ close(fd); }); *out_fd = dfpair[1]; // 设定函数一次读取的大小(分割大小) dispatch_io_set_low_water(pipe_channel, SIZE_MAX); dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^{ if (0 == err) { size_t len = dispatch_data_get_size(pipedata); if (len > 0) { const char *bytes = NULL; char *encoded; dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len); encoded = asl_core_encode_buffer(bytes, len); asl_set((aslmsg)merged_msg, ASL_KEY_AUX_DATA, encoded); free(encoded); _asl_send_message(NULL, merged_msg, -1, NULL); _asl_msg_release(merged_msg); dispatch_release(md); } } if (done) { dispatch_semaphore_signal(sem); dispatch_release(pipe_channel); dispatch_release(pipe_q); } });