Grand Central Dispatch(GCD) 是 Apple 开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并发任务。GCD 可用于多核的并行运算;GCD 会自动利用更多的 CPU 内核(比如双核、四核);GCD 会自动管理线程的生命周期(创建线程、调度任务、销毁线程);
同步&异步 同步和异步的概念主要区别:具不具备开启新的线程的能力,决定了任务在哪一个线程执行了;但更确切的说同步(sync)和 异步(async)的区别在于会不会阻塞当前线程,直到 Block 中的任务执行完毕;同步(sync) 操作,它会阻塞当前线程并等待 Block 中的任务执行完毕,然后当前线程才会继续往下运行。异步(async)操作,当前线程会直接往下执行,它不会阻塞当前线程;
任务&队列 任务:即具体要干的事情要执行的操作,一般是一段代码块,在 GCD 中是一个 就是一个 Block 块,这里需要特意留意下任务是 Block 块 ;任务存放于队列中,任务有两种执行方式:是串行还是并行;同步和异步决定任务在那条线程中执行;
队列:用于存放任务,GCD 中队列分为两大类 dispatch_queue_t
,串行队列 和 并行队列,队列的类型决定了任务的执行方式;
串行队列:放到串行队列的任务,任务会以 FIFO(先进先出)的方式地取出来,让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务),实际上串行队列中也只能开启一条新线程;其中特殊的是系统自身就已经创建了一个主队列,对应着主线程其也是串行队列,主线程的作用是处理UI事件(点击事件、滚动事件、拖拽事情),串行队列的创建及获取有如下方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 dispatch_queue_t queue = dispatch_get_main_queue();dispatch_queue_t queue = dispatch_queue_create("com.zerocc.test00Queue" , NULL );dispatch_queue_t queue = dispatch_queue_create("com.zerocc.test01Queue" , DISPATCH_QUEUE_SERIAL);
并发队列:放到并发队列的任务,任务也是以 FIFO 的方式取出来,但不同的是,它取出来一个就会放到一个线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起并发执行的;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 );dispatch_queue_t queue = dispatch_queue_create("com.zerocc.test02Queue" , DISPATCH_QUEUE_CONCURRENT);
总结:
dispatch_sync
同步执行不管任务是在串行队列还是在并行队列,任务都是在当前线程一个接一个执行(主队列特例除外例如子线程中调用 sync + 主队列那么任务会从子线程回到主线程执行) - apple documentation Discussion ;
dispatch_async
异步执行任务放在串行队列,任务在其它线程一个接一个执行;任务放在并行队列,任务在多个线程中并发执行;
GCD 常用方法 dispatch_once (GCD 一次性代码执行) dispatch_once
能保证 Block 中代码块在程序运行过程只执行一次,常用于单例的创建;并且在多线程的环境下,dispatch_once 可以保证线程安全;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 - (void )touchesBegan:(NSSet <UITouch *> *)touches withEvent:(UIEvent *)event { for (int i=0 ; i<3 ; i++) { [self disaptchOnce]; } } - (void )disaptchOnce { NSLog (@"%s" , __func__); static dispatch_once_t onceToken; dispatch_once (&onceToken, ^{ NSLog (@"dispatch_once - 执行任务" ); }); }
1 2 3 4 5 ... CCBlogCode[41182 :17902797 ] -[CCGCDVC disaptchOnce] ... CCBlogCode[41182 :17902797 ] dispatch_once - 执行任务 ... CCBlogCode[41182 :17902797 ] -[CCGCDVC disaptchOnce] ... CCBlogCode[41182 :17902797 ] -[CCGCDVC disaptchOnce]
dispatch_group(GCD 队列组) 队列组可以将多个队列添加到一个组里,从而管理监听所有任务都执行完成情况,当所有任务都执行完成有一个回调监听。这里的多个队列可以是不同的;运用场景可以是多个网络请求的控制等例如表单类型填写上报优先级问题;实现步骤如下:
创建队列组,通过调用 dispatch_group_create()
方法创建得到 dispatch_group_t
类型 group;
将队列中的任务添加到队列组 group 中, 通过 dispatch_group_async(队列组,队列,block)
方法;次步也可以使用 dispatch_group_enter
和 dispatch_group_leave
这两个组合替代 dispatch_group_async
, dispatch_group_enter
:表示将一个队列中任务放入 group 队列组中,此时 group 队列组任务数加1, dispatch_group_leave
:表示将一个队列中任务从 group 队列组撤出,此时 group 队列组任务数减1;一定是配合一对一使用否则 group 任务数不为0(也就是标志是否执行完),任务数不为0则不会走下一步的监听回调 Block;
监听所有队列中任务完成的回调,具体任务完成回调监听方法 dispatch_group_notify(队列组,队列,block)
;
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 - (void )dispatchGroup { dispatch_queue_t queue = dispatch_queue_create("com.zerocc.testQueue" , DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, queue, ^{ for (int i = 0 ; i < 3 ; i++) { [NSThread sleepForTimeInterval:5 ]; NSLog (@"任务1---%@" , [NSThread currentThread]); } }); dispatch_group_async(group, queue, ^{ for (int i = 0 ; i < 3 ; i++) { NSLog (@"任务2---%@" , [NSThread currentThread]); } }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ for (int i = 0 ; i < 3 ; i++) { [NSThread sleepForTimeInterval:1 ]; NSLog (@"任务3---%@" , [NSThread currentThread]); } }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog (@"任务123都执行完毕---%@" ,[NSThread currentThread]); }); }
1 2 3 4 5 6 7 8 9 10 11 ... CCBlogCode[1435 :621582 ] 任务2 ---<NSThread: 0x1cc07f3c0 >{number = 3 , name = (null )} ... CCBlogCode[1435 :621582 ] 任务2 ---<NSThread: 0x1cc07f3c0 >{number = 3 , name = (null )} ... CCBlogCode[1435 :621582 ] 任务2 ---<NSThread: 0x1cc07f3c0 >{number = 3 , name = (null )} ... CCBlogCode[1435 :621584 ] 任务3 ---<NSThread: 0x1c4263e80 >{number = 4 , name = (null )} ... CCBlogCode[1435 :621584 ] 任务3 ---<NSThread: 0x1c4263e80 >{number = 4 , name = (null )} ... CCBlogCode[1435 :621584 ] 任务3 ---<NSThread: 0x1c4263e80 >{number = 4 , name = (null )} ... CCBlogCode[1435 :621581 ] 任务1 ---<NSThread: 0x1c807db00 >{number = 5 , name = (null )} ... CCBlogCode[1435 :621581 ] 任务1 ---<NSThread: 0x1c807db00 >{number = 5 , name = (null )} ... CCBlogCode[1435 :621581 ] 任务1 ---<NSThread: 0x1c807db00 >{number = 5 , name = (null )} ... CCBlogCode[1435 :621451 ] 任务123 都执行完毕---<NSThread: 0x1d007c4c0 >{number = 1 , name = main}
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 - (void )dispatchGroupEnterAndleave { NSLog (@"currentThread---%@" ,[NSThread currentThread]); dispatch_queue_t queue = dispatch_queue_create("com.zerocc.testQueue" , DISPATCH_QUEUE_CONCURRENT); dispatch_group_t group = dispatch_group_create(); dispatch_group_enter(group); dispatch_async (queue, ^{ for (int i = 0 ; i < 2 ; ++i) { [NSThread sleepForTimeInterval:5 ]; NSLog (@"任务1---%@" ,[NSThread currentThread]); } dispatch_group_leave(group); }); dispatch_group_enter(group); dispatch_async (dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{ for (int i = 0 ; i < 2 ; ++i) { [NSThread sleepForTimeInterval:1 ]; NSLog (@"任务2---%@" ,[NSThread currentThread]); } dispatch_group_leave(group); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog (@"任务12都执行完毕---%@" ,[NSThread currentThread]); }); }
1 2 3 4 5 6 7 ... CCBlogCode[1467 :622770 ] currentThread---<NSThread: 0x1d406f940 >{number = 1 , name = main} ... CCBlogCode[1467 :622862 ] 任务2 ---<NSThread: 0x1c806c540 >{number = 3 , name = (null )} ... CCBlogCode[1467 :622862 ] 任务2 ---<NSThread: 0x1c806c540 >{number = 3 , name = (null )} ... CCBlogCode[1467 :622858 ] 任务1 ---<NSThread: 0x1c406cf40 >{number = 4 , name = (null )} ... CCBlogCode[1467 :622858 ] 任务1 ---<NSThread: 0x1c406cf40 >{number = 4 , name = (null )} ... CCBlogCode[1467 :622770 ] 任务12 都执行完毕---<NSThread: 0x1d406f940 >{number = 1 , name = main}
队列组中还有一个方法 dispatch_group_wait(队列组, DISPATCH_TIME_FOREVER)
,可以用来阻塞当前线程,这里指的当前线程为队列组 group 所在的线程。
dispatch_after(GCD 延时执行) GCD 延时执行就是针对某个队列中的任务 (block 块中)延迟执行,通过调用方法 dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block)
来指定某个任务在延迟特定的时间后再执行;
1 2 3 4 5 6 7 - (void )dispatchAfter { dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC )), dispatch_get_main_queue(), ^{ NSLog (@"延迟2秒执行---%@" ,[NSThread currentThread]); }); }
dispatch_apply(GCD 快速迭代) GCD 快速迭代类似于 for 和 while 循环遍历,但是不同的是可以将执行任务代码块添加到指定队列中进行;如果任务在串行队列中执行使用 dispatch_apply
效果等同于一般 for 循环,如果是并发队列进行异步执行的话,dispatch_apply
可以在多个线程同时异步遍历执行,会自动开启新线程执行任务,并且执行的顺序是不固定的。
1 2 3 4 5 6 7 8 9 10 11 12 13 - (void )dispatchApply { NSLog (@"迭代---begin" ); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ); dispatch_apply(10 , queue, ^(size_t index) { [NSThread sleepForTimeInterval:2 ]; NSLog (@"迭代%zd---%@" ,index, [NSThread currentThread]); }); NSLog (@"迭代---end" ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 ... CCBlogCode[2001 :814061 ] 迭代---begin ... CCBlogCode[2001 :814061 ] 迭代0 ---<NSThread: 0x1d4260780 >{number = 1 , name = main} ... CCBlogCode[2001 :814193 ] 迭代1 ---<NSThread: 0x1c807e000 >{number = 3 , name = (null )} ... CCBlogCode[2001 :814426 ] 迭代3 ---<NSThread: 0x1c426c280 >{number = 5 , name = (null )} ... CCBlogCode[2001 :814454 ] 迭代5 ---<NSThread: 0x1c0263fc0 >{number = 7 , name = (null )} ... CCBlogCode[2001 :814453 ] 迭代4 ---<NSThread: 0x1cc07e340 >{number = 6 , name = (null )} ... CCBlogCode[2001 :814425 ] 迭代2 ---<NSThread: 0x1c02642c0 >{number = 4 , name = (null )} ... CCBlogCode[2001 :814061 ] 迭代6 ---<NSThread: 0x1d4260780 >{number = 1 , name = main} ... CCBlogCode[2001 :814426 ] 迭代8 ---<NSThread: 0x1c426c280 >{number = 5 , name = (null )} ... CCBlogCode[2001 :814193 ] 迭代7 ---<NSThread: 0x1c807e000 >{number = 3 , name = (null )} ... CCBlogCode[2001 :814454 ] 迭代9 ---<NSThread: 0x1c0263fc0 >{number = 7 , name = (null )} ... CCBlogCode[2001 :814061 ] 迭代---end
dispatch_source
(GCD 实现定时器)dispatch_source… 的使用,其中通过调用方法dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)
创建事件,第一个参数为 dispatch_source_type_t
类型,其代表指定事件类型,可以自定义事件或者 Timer 事件还有 Mach 端口相关事件等等,可以 command 点进去看API了;此文只对定时器事件运用分析。
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 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 - (void )dispatchSource { __block int timeout = 15 ; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ); dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0 , 0 , queue); dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC , 0 * NSEC_PER_SEC ); __weak typeof (self )weakSelf = self ; weakSelf.timerBtn.userInteractionEnabled = false ; dispatch_source_set_event_handler(timer, ^{ timeout--; if (timeout > 0 ) { dispatch_async (dispatch_get_main_queue(), ^{ NSString * title = [NSString stringWithFormat:@"%d秒后重新" ,timeout]; [weakSelf.timerBtn setTitle:title forState:UIControlStateNormal ]; }); }else { dispatch_source_cancel(timer); } }); dispatch_source_set_cancel_handler(timer, ^{ dispatch_async (dispatch_get_main_queue(), ^{ weakSelf.timerBtn.userInteractionEnabled = YES ; [weakSelf.timerBtn setTitle:@"重新获取" forState:UIControlStateNormal ]; }); NSLog (@"定时取消" ); }); dispatch_resume(timer); }
dispatch_barrier_async
(GCD 栅栏方法)dispatch_barrier_async
作用就是分割其上下两部分任务执行过程,都是异步执行但是先执行其前面的任务先,再执行自身 Block 块中的任务,最后继续往下执行;表现形式就是分割作用。
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 - (void )dispatchBarrier { dispatch_queue_t queue = dispatch_queue_create("zerocc.com.testQueue" , DISPATCH_QUEUE_CONCURRENT); dispatch_async (queue, ^{ for (int i = 0 ; i < 2 ; i++) { [NSThread sleepForTimeInterval:3 ]; NSLog (@"任务1---%@" ,[NSThread currentThread]); } }); dispatch_async (queue, ^{ for (int i = 0 ; i < 2 ; i++) { [NSThread sleepForTimeInterval:2 ]; NSLog (@"任务2---%@" ,[NSThread currentThread]); } }); dispatch_barrier_async(queue, ^{ for (int i = 0 ; i < 2 ; ++i) { [NSThread sleepForTimeInterval:2 ]; NSLog (@"barrier---%@" ,[NSThread currentThread]); } }); dispatch_async (queue, ^{ for (int i = 0 ; i < 2 ; ++i) { NSLog (@"任务3---%@" ,[NSThread currentThread]); } }); }
1 2 3 4 5 6 7 8 9 ... CCBlogCode[30046 :4604551 ] 任务2 ---<NSThread: 0x600001bc0300 >{number = 3 , name = (null )} ... CCBlogCode[30046 :4604553 ] 任务1 ---<NSThread: 0x600001bc0900 >{number = 4 , name = (null )} ... CCBlogCode[30046 :4604551 ] 任务2 ---<NSThread: 0x600001bc0300 >{number = 3 , name = (null )} ... CCBlogCode[30046 :4604553 ] 任务1 ---<NSThread: 0x600001bc0900 >{number = 4 , name = (null )} ... CCBlogCode[30046 :4604553 ] barrier---<NSThread: 0x600001bc0900 >{number = 4 , name = (null )} ... CCBlogCode[30046 :4604553 ] barrier---<NSThread: 0x600001bc0900 >{number = 4 , name = (null )} ... CCBlogCode[30046 :4604553 ] 任务3 ---<NSThread: 0x600001bc0900 >{number = 4 , name = (null )} ... CCBlogCode[30046 :4604553 ] 任务3 ---<NSThread: 0x600001bc0900 >{number = 4 , name = (null )}
dispatch_semaphore(GCD 信号量) GCD 信号量可以理解为一个计数器,用来控制线程并发访问的最大数量问题,保证线程同步等。信号量初始值为1,代表只允许当前线程访问资源,当信号量值小与等于零时当前线程就进入休眠等待状态。
初始化信号量,调用 dispatch_semaphore_create(1)
方法初始化信号量值为1;
判断信号量值决定其是否能往下继续执行,通过调用方法 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
。如果信号量的值大于0,则该方法所处的线程就会继续执行,并且信号量值 -1;如果信号量值 <= 0, 当前线程进入休眠等待状态,也可以通过控制第二个参数使其不一直处于等待状态,例如:DISPATCH_TIME_NOW+3 表示3秒后继续向下执行;
一个任务执行完后记得使信号量的值加1,通过调用方法 dispatch_semaphore_signal
信号值 +1,从而后面任务可以继续执行;
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 37 38 - (void )dispatchSemaphore { dispatch_queue_t queue = dispatch_queue_create("com.zerocc.semaphore" , DISPATCH_QUEUE_CONCURRENT); dispatch_semaphore_t semaphore = dispatch_semaphore_create(1 ); dispatch_async (queue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); [NSThread sleepForTimeInterval:5 ]; NSLog (@"任务1---%@" ,[NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); dispatch_async (queue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); for (int i = 0 ; i < 3 ; ++i) { [NSThread sleepForTimeInterval:3 ]; NSLog (@"任务2---%@" ,[NSThread currentThread]); } dispatch_semaphore_signal(semaphore); }); dispatch_async (queue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); for (int i = 0 ; i < 3 ; ++i) { [NSThread sleepForTimeInterval:1 ]; NSLog (@"任务3---%@" ,[NSThread currentThread]); } dispatch_semaphore_signal(semaphore); }); }
1 2 3 4 5 6 7 8 ... CCBlogCode[1688 :347237 ] 任务1 ---<NSThread: 0x600003f3d9c0 >{number = 3 , name = (null )} ... CCBlogCode[1688 :347235 ] 任务2 ---<NSThread: 0x600003f3f1c0 >{number = 4 , name = (null )} ... CCBlogCode[1688 :347235 ] 任务2 ---<NSThread: 0x600003f3f1c0 >{number = 4 , name = (null )} ... CCBlogCode[1688 :347235 ] 任务2 ---<NSThread: 0x600003f3f1c0 >{number = 4 , name = (null )} ... CCBlogCode[1688 :347234 ] 任务3 ---<NSThread: 0x600003f3dc80 >{number = 5 , name = (null )} ... CCBlogCode[1688 :347234 ] 任务3 ---<NSThread: 0x600003f3dc80 >{number = 5 , name = (null )} ... CCBlogCode[1688 :347234 ] 任务3 ---<NSThread: 0x600003f3dc80 >{number = 5 , name = (null )}
本文标题: iOS 开发之多线程 GCD
文章作者: zerocc
发布时间: 2015年08月05日
原始链接: http://www.zerocc.com.cn/20150805.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!