GCD 使用说明
GCD提供的一些操作队列的方法
- dispatch_set_target_queue
系统的Global Queue是可以指定优先级的,那我们如何给自己创建的队列执行优先级呢?
这里我们就可以用到dispatch_set_target_queue这个方法:
dispatch_queue_t serialDiapatchQueue=dispatch_queue_create("com.test.queue", NULL); dispatch_queue_t dispatchgetglobalqueue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, ); //将serialDiapatchQueue放到全局队列中作为子队列,这样优先级就是使用默认 dispatch_set_target_queue(serialDiapatchQueue, dispatchgetglobalqueue);<br /> dispatch_async(serialDiapatchQueue, ^{ NSLog(@"我优先级低,先让让"); });<br /> dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ), ^{ NSLog(@"我优先级高,我先block"); });
我把自己创建的队列塞到了系统提供的global_queue队列中,我们可以理解为:我们自己创建的queue其实是位于global_queue中执行,
所以改变global_queue的优先级,也就改变了我们自己所创建的queue的优先级。所以我们常用这种方式来管理子队列。
(一),使用dispatch_set_target_queue更改Dispatch Queue的执行优先级
dispatch_queue_create函数生成的DisPatch Queue不管是Serial DisPatch Queue还是Concurrent Dispatch Queue,执行的优先级都与默认优先级的Global Dispatch queue相同,如果需要变更生成的Dispatch Queue的执行优先级则需要使用dispatch_set_target_queue函数
- (void)testTeagerQueue1 { dispatch_queue_t serialQueue = dispatch_queue_create("com.oukavip.www",NULL); dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,); // 第一个参数为要设置优先级的queue,第二个参数是参照物,既将第一个queue的优先级和第二个queue的优先级设置一样。 dispatch_set_target_queue(serialQueue, globalQueue); }
(二),使用dispatch_set_target_queue修改用户队列的目标队列,使多个serial queue在目标queue上一次只有一个执行
首先,我们需要阐述一下生成多个Serial DisPatch Queue时的注意事项
Serial DisPatch Queue是一个串行队列,只能同时执行1个追加处理(即任务),当用Dispatch_queue_create函数生成多个Serial DisPatch Queue时,每个Serial DisPatch Queue均获得一个线程,即多个Serial DisPatch Queue可并发执行,同时处理添加到各个Serial DisPatch Queue中的任务,但要注意如果过多地使用多线程,就会消耗大量内存,引起大量的上下文切换,大幅度降低系统的响应性能,所以我们只在为了避免多个线程更新相同资源导致数据竞争时,使用Serial DisPatch Queue
第一种情况:使用dispatch_set_target_queue(Dispatch Queue1, Dispatch Queue2)实现队列的动态调度管理
- (void)testTargetQueue2 { //创建一个串行队列queue1 dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL); //创建一个串行队列queue2 dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL); //使用dispatch_set_target_queue()实现队列的动态调度管理 dispatch_set_target_queue(queue1, queue2); /* <*>dispatch_set_target_queue(Dispatch Queue1, Dispatch Queue2); 那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行: <*>dispatch_suspend(dispatchA); 这时则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。 这里只简单举个例子,说明dispatch队列运行的灵活性,在实际应用中你会逐步发掘出它的潜力。 dispatch队列不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue,不得不说这是个小小的 */ dispatch_async(queue1, ^{ for (NSInteger i = 0; i < 10; i++) { NSLog(@"queue1:%@, %ld", [NSThread currentThread], i); [NSThread sleepForTimeInterval:0.5]; if (i == 5) { dispatch_suspend(queue2); } } }); dispatch_async(queue1, ^{ for (NSInteger i = 0; i < 100; i++) { NSLog(@"queue1:%@, %ld", [NSThread currentThread], i); } }); dispatch_async(queue2, ^{ for (NSInteger i = 0; i < 100; i++) { NSLog(@"queue2:%@, %ld", [NSThread currentThread], i); } }); }
第二种情况:使用dispatch_set_target_queue将多个串行的queue指定到了同一目标,那么着多个串行queue在目标queue上就是同步执行的,不再是并行执行。
- (void)testTargetQueue { //1.创建目标队列 dispatch_queue_t targetQueue = dispatch_queue_create("test.target.queue", DISPATCH_QUEUE_SERIAL); //2.创建3个串行队列 dispatch_queue_t queue1 = dispatch_queue_create("test.1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("test.2", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue3 = dispatch_queue_create("test.3", DISPATCH_QUEUE_SERIAL); //3.将3个串行队列分别添加到目标队列 dispatch_set_target_queue(queue1, targetQueue); dispatch_set_target_queue(queue2, targetQueue); dispatch_set_target_queue(queue3, targetQueue); dispatch_async(queue1, ^{ NSLog(@"1 in"); [NSThread sleepForTimeInterval:.f]; NSLog(@"1 out"); }); dispatch_async(queue2, ^{ NSLog(@"2 in"); [NSThread sleepForTimeInterval:.f]; NSLog(@"2 out"); }); dispatch_async(queue3, ^{ NSLog(@"3 in"); [NSThread sleepForTimeInterval:.f]; NSLog(@"3 out"); }); }
- dispatch_after
这个是最常用的,用来延迟执行的GCD方法,因为在主线程中我们不能用sleep来延迟方法的调用,所以用它是最合适的,我们做一个简单的例子:
NSLog(@"小破孩-波波1"); double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ NSLog(@"小破孩-波波2"); });
输出的结果:
2016-03-07 11:25:06.019 GCD[2443:95722] 小破孩-波波1 2016-03-07 11:25:08.019 GCD[2443:95722] 小破孩-波波2
我们看到他就是在主线程,就是刚好延迟了2秒,当然,我说这个2秒并不是绝对的,为什么这么说?还记得我之前在介绍dispatch_async这个特性的时候提到的吗?他的block中方法的执行会放在主线程runloop之后,所以,如果此时runloop周期较长的时候,可能会有一些时差产生。
- dispatch_group
当我们需要监听一个并发队列中,所有任务都完成了,就可以用到这个group,因为并发队列你并不知道哪一个是最后执行的,所以以单独一个任务是无法监听到这个点的,如果把这些单任务都放到同一个group,那么,我们就能通过dispatch_group_notify方法知道什么时候这些任务全部执行完成了。
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, ^{NSLog(@"0");}); 4 dispatch_group_async(group, queue, ^{NSLog(@"1");}); 5 dispatch_group_async(group, queue, ^{NSLog(@"2");}); 6 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 7 NSLog(@"down"); 8 });
在例子中,我把3个log分别放在并发队列中,通过把这个并发队列任务统一加入group中,group每次runloop的时候都会调用一个方法dispatch_group_wait(group, DISPATCH_TIME_NOW),用来检查group中的任务是否已经完成,如果已经完成了,那么会执行dispatch_group_notify的block,输出’down’看一下运行结果:
1 2016-03-07 14:21:58.647 GCD[9424:156388] 2 2 2016-03-07 14:21:58.647 GCD[9424:156382] 0 3 2016-03-07 14:21:58.647 GCD[9424:156385] 1 4 2016-03-07 14:21:58.650 GCD[9424:156324] down
- dispatch_barrier_async
此方法的作用是在并发队列中,完成在它之前提交到队列中的任务后打断,单独执行其block,并在执行完成之后才能继续执行在他之后提交到队列中的任务:
1 dispatch_queue_t concurrentDiapatchQueue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT); 2 dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"0");}); 3 dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"1");}); 4 dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"2");}); 5 dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"3");}); 6 dispatch_barrier_async(concurrentDiapatchQueue, ^{ 7 sleep(1); 8 NSLog(@"4"); 9 }); 10 dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"5");}); 11 dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"6");}); 12 dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"7");}); 13 dispatch_async(concurrentDiapatchQueue, ^{NSLog(@"8");});
输出的结果为:
2016-03-07 14:45:32.410 GCD[10079:169655] 1 2016-03-07 14:45:32.410 GCD[10079:169658] 2 2016-03-07 14:45:32.410 GCD[10079:169656] 0 2016-03-07 14:45:32.410 GCD[10079:169661] 3 2016-03-07 14:45:33.414 GCD[10079:169661] 4 2016-03-07 14:45:33.415 GCD[10079:169661] 5 2016-03-07 14:45:33.415 GCD[10079:169658] 6 2016-03-07 14:45:33.415 GCD[10079:169655] 8 2016-03-07 14:45:33.415 GCD[10079:169662] 7
4之后的任务在我线程sleep之后才执行,这其实就起到了一个线程锁的作用,在多个线程同时操作一个对象的时候,读可以放在并发进行,当写的时候,我们就可以用dispatch_barrier_async方法,效果杠杠的。
- dispatch_sync
dispatch_sync 会在当前线程执行队列,并且阻塞当前线程中之后运行的代码,所以,同步线程非常有可能导致死锁现象,我们这边就举一个死锁的例子,直接在主线程调用以下代码:
dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"有没有同步主线程?"); });
*根据FIFO(先进先出)的原则,block里面的代码应该在主线程此次runloop后执行,但是由于他是同步队列,所有他之后的代码会等待其执行完成后才能继续执行,2者相互等待,所以就出现了死锁。
我们再举一个比较特殊的例子:*
dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); dispatch_sync(queue, ^{sleep();NSLog(@"");}); dispatch_sync(queue, ^{sleep();NSLog(@"");}); dispatch_sync(queue, ^{sleep();NSLog(@"");}); NSLog(@"");
其打印结果为:
1 2016-03-07 17:15:48.124 GCD[14198:272683] 1 2 2016-03-07 17:15:49.125 GCD[14198:272683] 2 3 2016-03-07 17:15:50.126 GCD[14198:272683] 3 4 2016-03-07 17:15:50.126 GCD[14198:272683] 4
从线程编号中我们发现,同步方法没有去开新的线程,而是在当前线程中执行队列,会有人问,上文说dispatch_get_global_queue不是并发队列,并发队列不是应该会在开启多个线程吗?这个前提是用异步方法。GCD其实是弱化了线程的管理,强化了队列管理,这使我们理解变得比较形象
- dispatch_apply
这个方法用于无序查找,在一个数组中,我们能开启多个线程来查找所需要的值,我这边也举个例子:
NSArray *array=[[NSArray alloc]initWithObjects:@"",@"",@"",@"",@"",@"",@"", nil]; dispatch_queue_t queue=dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); dispatch_apply([array count], queue, ^(size_t index) { NSLog(@"%zu=%@",index,[array objectAtIndex:index]); }); NSLog(@"阻塞");
输出结果
2016-03-07 17:36:50.726 GCD[14318:291701] 1=1 2016-03-07 17:36:50.726 GCD[14318:291705] 0=0 2016-03-07 17:36:50.726 GCD[14318:291783] 3=3 2016-03-07 17:36:50.726 GCD[14318:291782] 2=2 2016-03-07 17:36:50.726 GCD[14318:291784] 5=5 2016-03-07 17:36:50.726 GCD[14318:291627] 4=4 2016-03-07 17:36:50.726 GCD[14318:291785] 6=6 2016-03-07 17:36:50.727 GCD[14318:291627] 阻塞
通过输出log,我们发现这个方法虽然会开启多个线程来遍历这个数组,但是在遍历完成之前会阻塞主线程。
- dispatch_suspend & dispatch_resume
队列挂起和恢复,这个没什么好说的,直接上代码:
1 dispatch_queue_t concurrentDiapatchQueue=dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT); 2 dispatch_async(concurrentDiapatchQueue, ^{ 3 for (int i=0; i<100; i++) 4 { 5 NSLog(@"%i",i); 6 if (i==50) 7 { 8 NSLog(@"-----------------------------------"); 9 dispatch_suspend(concurrentDiapatchQueue); 10 sleep(3); 11 dispatch_async(dispatch_get_main_queue(), ^{ 12 dispatch_resume(concurrentDiapatchQueue); 13 }); 14 } 15 } 16 });
我们甚至可以在不同的线程对这个队列进行挂起和恢复,因为GCD是对队列的管理。
- Semaphore
我们可以通过设置信号量的大小,来解决并发过多导致资源吃紧的情况,以单核CPU做并发为例,一个CPU永远只能干一件事情,那如何同时处理多个事件呢,聪明的内核工程师让CPU干第一件事情,一定时间后停下来,存取进度,干第二件事情以此类推,所以如果开启非常多的线程,单核CPU会变得非常吃力,即使多核CPU,核心数也是有限的,所以合理分配线程,变得至关重要,那么如何发挥多核CPU的性能呢?如果让一个核心模拟传很多线程,经常干一半放下干另一件事情,那效率也会变低,所以我们要合理安排,将单一任务或者一组相关任务并发至全局队列中运算或者将多个不相关的任务或者关联不紧密的任务并发至用户队列中运算,所以用好信号量,合理分配CPU资源,程序也能得到优化,当日常使用中,信号量也许我们只起到了一个计数的作用,真的有点大材小用。
1 dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);//为了让一次输出10个,初始信号量为10 2 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 3 for (int i = 0; i <100; i++) 4 { 5 dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//每进来1次,信号量-1;进来10次后就一直hold住,直到信号量大于0; 6 dispatch_async(queue, ^{ 7 NSLog(@"%i",i); 8 sleep(2); 9 dispatch_semaphore_signal(semaphore);//由于这里只是log,所以处理速度非常快,我就模拟2秒后信号量+1; 10 }); 11 }
- dispatch_once
这个函数一般是用来做一个单例,也是非常常用的,下面是一个简单用例:
static SingletonTimer * instance; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ instance = [[SingletonTimer alloc] init]; }); return instance;