1.简介
GCD
是在线程池模式
基础上开发的一套多核编程的较新的解决方案:
- 底层由C语言实现,API使用时简洁明了;
- 为多核处理器而设计,会自动利用更多的CPU内核进行运算,执行效率高;
- 自动管理线程生命周期,无需手写线程管理的代码,只需告诉它我们想要执行的任务。
2.同步与异步
同步和异步是将任务以 block 形式提交到指定队列中时所使用的两种方式。
1
| dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
|
dispatch_sync
用于同步地执行任务。函数会一直等到 blcok 执行完才返回,这会阻塞当前线程。
1
| dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
|
dispatch_async
用于异步地执行任务。函数在提交完任务后不等待 block 执行完就立刻返回了,不会阻塞当前线程。
3.队列
1.串行与并发队列
当有多个任务时,可以将它们放在队列中执行,队列分为串行和并发两种:
串行队列:任务是按照FIFO的顺序执行的,先提交的任务先执行,后提交的任务须等前一个任务执行完成才能开始。对于不同的串行队列,系统会为它们创建不同的线程来执行任务。
并发队列:任务也是按照FIFO的顺序执行,不同的是,任务不是必须等到前一个执行完才开始,同一时间可能会有多个任务在执行。GCD 会动态分配多条线程来执行这些任务,具体几条线程取决于当前内存状况和线程池中线程数等因素。
#特殊的串行队列: main_queue
1 2 3
| //主队列本身是系统提供的一种特殊的串行队列 //放在主队列中的任务都会在主线程中执行 dispatch_queue_t mainQueue = dispatch_get_main_queue();
|
#特殊的并发队列: global_queue
1 2 3 4
| //全局队列是系统提供的一种特殊并发队列 //第一个参数指定queue的优先级,分为HIGH\DEFAULT\LOW\BACKGROUND四种; //第二个参数,目前只能为0或NULL; dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_ PRIORITY_DEFAULT,0);
|
2.任务+队列 的组合
. |
并发队列 |
串行队列 |
主队列 |
同步 |
不能开启新线程,串行执行任务 |
不能开启新线程,串行执行任务 |
不能开启新线程,串行执行任务 |
异步 |
可以开启新线程,并发执行任务 |
可以开启新线程,串行执行任务 |
不能开启新线程,串行执行任务 |
注释:不能开启新线程,就要继续在当前线程中运行。
#示例3.2.1:
1 2 3 4 5 6 7 8 9
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_sync(concurrentQueue, ^{ NSLog(@"+++Thread:%@,main?:%d",[NSThread currentThread],[NSThread isMainThread]); }); return YES; }
|
启动后输出结果为:
1
| +++Thread:<NSThread: 0x6000000dca40>{number = 1, name = main},main?:1
|
分析:示例中向并发队列concurrentQueue
中同步的提交了一个打印日志的任务,虽然并发队列具有开辟新线程的能力,但是结合上dispatch_sync
之后,就不行了,任务会继续运行在当前的主线程上。输出结果中“name = main” 且 “main?:1”也印证了这一点。
再注意:可以开启新线程,也不一定就真的会开辟新线程执行新的任务,系统要根据线程池中的状况来决定是开辟新线程,还是继续在当前空闲的线程上执行任务。
#示例3.2.2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_async(concurrentQueue, ^{ NSLog(@"+++Thread1:%@,main?:%d",[NSThread currentThread],[NSThread isMainThread]); dispatch_async(concurrentQueue, ^{ NSLog(@"+++Thread2:%@,main?:%d",[NSThread currentThread],[NSThread isMainThread]); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"+++Thread3:%@,main?:%d",[NSThread currentThread],[NSThread isMainThread]); }); });
}); return YES; }
|
运行后日志为:
1 2 3
| +++Thread1:<NSThread: 0x600003bd5500>{number = 3, name = (null)},main?:0 +++Thread2:<NSThread: 0x600003bd5500>{number = 3, name = (null)},main?:0 +++Thread3:<NSThread: 0x600003bb1900>{number = 1, name = main},main?:1
|
step1
处,异步+并发,已具备开启新线程的能力,从日志来看 Thread1 “main?:0”,已经不在主线程,开启了新线程;
step2
处,异步+并发,也具备开启新线程的能力,从日志来看线程 Thread2 的内存地址与 step1 时一样,说明继续运行在当前线程上,并没有开辟新线程~
不过,如果把step4
处的注释去掉,模拟一次复杂运算,则输出结果就不一样了:
1 2 3
| +++Thread1:<NSThread: 0x600000c98ec0>{number = 3, name = (null)},main?:0 +++Thread2:<NSThread: 0x600000cab140>{number = 4, name = (null)},main?:0 +++Thread3:<NSThread: 0x600000cfd900>{number = 1, name = main},main?:1
|
Thread2
与Thread1
的地址已经不一样了。这是因为Thread1
中有大量运算,Thread2
要开启一个新线程执行任务。
所以,同/异步 + 串/并行的组合,只是说明了任务所在队列,任务的提交方式和是否具备开辟新线程的能力。当具备开辟新线程的能力时,是否真的开辟新线程要由线程池的状况来确定。
3.死锁问题
在当前串行
队列中再同步
地提交一个新任务到队列中会造成死锁!
#示例3.3.1:
主队列中使用“同步+主队列”的组合,造成死锁的场景:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; NSLog(@"++++任务开始"); dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_sync(mainQueue, ^{ NSLog(@"++++处理任务4"); });
NSLog(@"++++任务结束"); }
|
上面的代码行后,只会输出“++++任务开始”,并卡死在dispatch_sync
这一行。这是因为:viewDidAppear
是在主队列中,这是一个串行队列,里面的任务按照FIFO的顺序依次执行。整个代码块中大的任务顺序为123,但是执行到 step2 时,同步地向主队列中追加了一个任务4,因此任务顺序变为1234。dispatch_sync
提交任务的特点是会阻塞当前线程,并立刻开始执行其中的任务。问题就来了,主线程本来要按顺序执行1、2、3,结果执行到 2 时被强行阻塞,并被要求立刻去执行任务4。而串行队列中任务4位于3之后,不能在3尚未执行时先执行4。所以形成一种局面:执行到step2时,串行主队列在等待任务4完成,任务4又在等待主队列完成任务3,从而形成了死锁问题。
#示例3.3.2:
1 2 3 4 5 6 7 8 9 10
| dispatch_queue_t serialQueue = dispatch_queue_create("seria", DISPATCH_QUEUE_SERIAL); dispatch_async(serialQueue, ^{ NSLog(@"1"); dispatch_sync(serialQueue, ^{ NSLog(@"2"); }); NSLog(@"3"); }); NSLog(@"4");
|
上述示例中会先打印日志“4”,后打印“1”,之后在 dispatch_sync
这一行卡死。这其实与示例1 类似:因为 block1 是异步执行,所以函数立刻返回并打印“4”;但是 block1 内任务是在一个串行队列中,执行到dispatch_sync
处时线程被阻塞,并被要求立刻在当前串行队列中再执行追加到尾部的新任务 block2。这当然也行不通,因为当前线程已被block2阻塞,block2又在等待队列先完成任务3。这样也造成了相互等待的情况,形成了死锁。
4.线程间的通信
通常为了避免UI阻塞,我们可以在异步线程中做一些耗时的任务,完成后回到主线程中更新UI。
1 2 3 4 5 6 7 8 9 10 11 12 13
| dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSURL *url = [NSURL URLWithString:@"xxx.jpg"]; NSData *data = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^{ [self.imageView setImage:image]; }); });
|
例子在主线程中调用,dispatch_async配合全局并发队列,开辟了一个新线程用来加载图片资源;加载完成后再通过dispatch_get_main_queue返回到主队列中更新视图。因为主队列默认在主线程中,所以更新视图的任务会在主线程中进行。
5.线程与队列
线程
和队列
是两个不同的概念~
队列
是一种任务组织方式,任务被以 block 的形式提交到队列
中,并最终在某个线程上执行,具体在哪个线程上执行是由队列类型
和提交任务时的方式
决定的。如#3.2小节中列出的那样,如果是同步
的方式提交任务,则任务会继续在当前线程上执行;如果是异步+并发队列
的方式提交的任务,那么内核会根据系统资源创建新线程来执行任务。
线程
是 CPU 调度和分派任务的基本单位,一个线程
中可以执行多个队列
中的任务。线程是可以重复利用的,这由线程池和系统资源状况来决定。
主线程
不等于主队列
。主线程只有一个,主线程上既可以执行主队列
中的任务,也可以执行全局并发队列
或你自定义队列
中的任务。
#示例:主线程执行非主队列的任务
1 2 3 4 5 6 7 8 9 10 11
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@"+++1.主线程?:%@",[NSThread isMainThread] ? @"YES" : @"NO" ); dispatch_sync(dispatch_get_global_queue(0, 0), ^{ NSLog(@"+++2.主线程?:%@",[NSThread isMainThread] ? @"YES" : @"NO" ); }); return YES; }
|
主队列
的任务一定在主线程
中执行,全局并发队列或自定义队列中的任务可以在子线程
中执行。
1 2 3 4 5 6 7 8 9 10 11
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSLog(@"+++1.主线程?:%@",[NSThread isMainThread] ? @"YES" : @"NO" ); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"+++2.主线程?:%@",[NSThread isMainThread] ? @"YES" : @"NO" ); }); return YES; }
|
很多跟 UI 相关的操作,一般都会放到main_queue
主队列中进行,比如常见的AFN
从子线程回到主队列更新图片。这是因为UIKit
本身不是线程安全的,这些操作需要在系统提供的主队列
这么一个串行队列
中进行,保证操作的顺序和完整性。
4.栅栏
1.dispatch_barrier_async
作用:让队列中排在它前面任务先执行完毕,再执行自己,最后再执行后续任务。
场景:向并发队列中加入5个异步任务:1、2、3、4、5。由于是异步+并发,所以任务实际执行时并不一定按照 12345 的顺序来。如果我们要求执行完任务 3 之后才能执行任务 4 和 5,此时就可以使用栅栏
这个功能。
#示例4.1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| - (void)barrier{ dispatch_queue_t cQueue = dispatch_queue_create("Name1",DISPATCH_QUEUE_CONCURRENT); dispatch_async(cQueue, ^(){ NSLog(@"++++任务1:%@",[NSThread currentThread]); }); dispatch_async(cQueue, ^(){ NSLog(@"++++任务2:%@",[NSThread currentThread]); }); dispatch_barrier_async(cQueue, ^(){ NSLog(@"++++任务3栅栏:%@",[NSThread currentThread]); }); NSLog(@"++++1"); dispatch_async(cQueue, ^(){ NSLog(@"++++任务4:%@",[NSThread currentThread]); }); NSLog(@"++++2"); dispatch_async(cQueue, ^(){ NSLog(@"++++任务5:%@",[NSThread currentThread]); }); }
|
日志:
1 2 3 4 5 6 7
| ++++1 ++++2 ++++任务2:<NSThread: 0x600003453f00>{number = 4, name = (null)} ++++任务1:<NSThread: 0x600003455f00>{number = 3, name = (null)} ++++任务3栅栏:<NSThread: 0x600003455f00>{number = 3, name = (null)} ++++任务4:<NSThread: 0x600003455f00>{number = 3, name = (null)} ++++任务5:<NSThread: 0x600003453f00>{number = 4, name = (null)}
|
从日志来看,任务的执行顺序正常:
- 栅栏之前的任务1和2先执行;
- 随后是栅栏本身的任务3;
- 最后是栅栏之后的任务4和5。
注意:栅栏只是用来控制同一并发队列中栅栏前后任务的整体执行顺序;至于栅栏前面的任务与后面的任务它们具体在哪个线程执行,这不是栅栏能决定的。
结合日志中任务所在线程的内存地址来看:
- 栅栏之前的
任务1
和任务2
并没在同一个线程中执行;
- 栅栏之后的
任务4
和任务5
也没有在同一个线程中执行;
- 甚至栅栏前的
任务1
和栅栏任务3
及栅栏之后的任务4
使用了同一个线程;
所以,栅栏后的任务具体是在哪个线程上执行,还是要看这些任务的提交方式和队列类型,之后交给线程池来决定。
但是,如果任务都是通过栅栏提交的,那么栅栏就能保证它们按照来时的顺序先后执行,且是在同一线程上:
#示例4.2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| - (void)barrier2{ dispatch_queue_t cQueue = dispatch_queue_create("Name1",DISPATCH_QUEUE_CONCURRENT); dispatch_async(cQueue, ^(){ NSLog(@"++++任务1:%@",[NSThread currentThread]); }); dispatch_async(cQueue, ^(){ NSLog(@"++++任务2:%@",[NSThread currentThread]); }); dispatch_barrier_async(cQueue, ^(){ NSLog(@"++++任务3栅栏:%@",[NSThread currentThread]); }); NSLog(@"++++1"); dispatch_barrier_async(cQueue, ^(){ NSLog(@"++++任务4:%@",[NSThread currentThread]); }); NSLog(@"++++2"); dispatch_barrier_async(cQueue, ^(){ NSLog(@"++++任务5:%@",[NSThread currentThread]); }); }
|
日志:
1 2 3 4 5 6 7
| ++++1 ++++2 ++++任务1:<NSThread: 0x60000132cb80>{number = 5, name = (null)} ++++任务2:<NSThread: 0x60000132a840>{number = 6, name = (null)} ++++任务3栅栏:<NSThread: 0x60000132a840>{number = 6, name = (null)} ++++任务4:<NSThread: 0x60000132a840>{number = 6, name = (null)} ++++任务5:<NSThread: 0x60000132a840>{number = 6, name = (null)}
|
可以看到,提交到栅栏中的任务3、4、5是按照提交时的顺序执行的,且在同一个线程中。
另外,该函数需要同dispatch_queue_create()
生成的并发队列一起用。如果示例4.1中换成全局队列则效果如下:
1 2 3 4 5 6 7
| ++++ ++++ ++++ ++++ ++++ ++++ ++++
|
任务1、2在栅栏3之后执行了,栅栏并未起效!!!,所以实际中须注意这一点~
2.dispatch_barrier_sync
将示例4.1中栅栏替换为dispatch_barrier_sync
后,日志如下:
1 2 3 4 5 6 7
| ++++ ++++ ++++ ++++ ++++ ++++ ++++
|
对比日志,两种情况下:
1、任务都是按照栅栏3之前的先执行,再是3本身,最后是45;
2、“++++数字”日志都是按照12的顺序执行,只是在异步栅栏中“++++数字” 与 “++++任务x”的打印了顺序不同。
两者的区别:
1、同步栅栏:将栅栏任务插入队列时,需等待栅栏任务结束后,才会继续插入并执行被写在它后面的任务;
2、异步栅栏:将栅栏任务插入队列后,不会等待栅栏任务结束,而是继续把后续任务插入队列中,栅栏任务结束后才执行后面的任务;
3、栅栏不论异步还是同步,都只会给自己并发队列中的任务设置栅栏,不会阻碍主线程的代码。
5.延时:dispatch_after
1 2 3 4 5
| dispatch_after( dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ });
|
6.dispatch_once
作用:广泛使用在单例中,用以保证初始化方法中的任务只执行一次。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| static ASDFSingleton *mSingleton = nil;
+ (instancetype)shareInstance { return [[self alloc] init]; }
+ (instancetype)allocWithZone:(struct _NSZone *)zone{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ mSingleton = [super allocWithZone:zone]; }); return mSingleton; }
|
7.dispatch_apply
作用:按指定的次数将任务追加到指定的队列中,并等到全部的处理执行结束。
#示例7.1:
1 2 3 4 5 6 7 8 9 10 11
| dispatch_queue_t concurrentQueue = dispatch_queue_create("Name",DISPATCH_QUEUE_CONCURRENT); NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
NSLog(@"++++开始");
dispatch_apply([array count], concurrentQueue, ^(size_t index) { NSLog(@"++%zu,%@,ON:%@", index, [array objectAtIndex:index], [NSThread currentThread]); });
NSLog(@"++++结束");
|
输出日志:
1 2 3 4 5 6 7 8 9 10 11 12
| ++++开始 ++1,b,ON:<NSThread: 0x6000010407c0>{number = 4, name = (null)} ++0,a,ON:<NSThread: 0x60000100d980>{number = 1, name = main} ++2,c,ON:<NSThread: 0x6000010407c0>{number = 4, name = (null)} ++3,d,ON:<NSThread: 0x60000100d980>{number = 1, name = main} ++4,e,ON:<NSThread: 0x600001055700>{number = 5, name = (null)} ++5,f,ON:<NSThread: 0x6000010407c0>{number = 4, name = (null)} ++7,h,ON:<NSThread: 0x600001055700>{number = 5, name = (null)} ++6,g,ON:<NSThread: 0x60000100d980>{number = 1, name = main} ++8,i,ON:<NSThread: 0x6000010407c0>{number = 4, name = (null)} ++9,j,ON:<NSThread: 0x60000100d980>{number = 1, name = main} ++++结束
|
多运行几次,可以从日志中发现:
- 打印的索引值并非完全按照 0~9 的升序排列;
- 遍历元素的操作并非都在同一个线程中;
如果希望索引值完全按照 0~9 的升序排列,可以使用串行队列,这样就类似于for
循环了。
#示例7.2:
1 2 3 4 5 6 7 8 9 10 11
| dispatch_queue_t concurrentQueue = dispatch_queue_create("Name",DISPATCH_QUEUE_SERIAL); NSArray *array = @[@"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j"];
NSLog(@"++++开始");
dispatch_apply([array count], concurrentQueue, ^(size_t index) { NSLog(@"++%zu,%@,ON:%@", index, [array objectAtIndex:index], [NSThread currentThread]); });
NSLog(@"++++结束");
|
日志:
1 2 3 4 5 6 7 8 9 10 11 12
| ++++开始 ++0,a,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++1,b,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++2,c,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++3,d,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++4,e,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++5,f,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++6,g,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++7,h,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++8,i,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++9,j,ON:<NSThread: 0x600001999600>{number = 1, name = main} ++++结束
|
不论使用串行还是并发队列,dispatch_apply
函数都不会立刻返回,它会阻塞当前线程并在循环完成后再继续执行后续代码。所以可以推测它使用了同步的方式提交遍历元素的任务,这一点类似于for
循环。
8.任务组
1.dispatch_group
场景:在并发队列的N个任务执行完毕后,继续执行某任务。
#示例8.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 39 40 41 42 43
| dispatch_queue_t dispatchQueue1 = dispatch_queue_create("concurrentQueue1",DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t dispatchQueue2 = dispatch_queue_create("concurrentQueue2",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t dispatchGroup = dispatch_group_create();
NSLog(@"++++1:%@",[NSThread currentThread]);
dispatch_group_async(dispatchGroup, dispatchQueue1, ^(){ for (int i = 0; i < 1000; i++) { if (888 == i) { NSLog(@"++++任务1:%@",[NSThread currentThread]); } } });
dispatch_group_async(dispatchGroup, dispatchQueue1, ^(){ [NSThread sleepForTimeInterval:5]; NSLog(@"++++任务2:%@",[NSThread currentThread]); });
dispatch_group_async(dispatchGroup, dispatchQueue2, ^(){ [NSThread sleepForTimeInterval:3]; NSLog(@"++++任务3:%@",[NSThread currentThread]); });
dispatch_group_notify(dispatchGroup,dispatch_get_main_queue(), ^(){ NSLog(@"++++任务完成:%@",[NSThread currentThread]); });
NSLog(@"++++稍等一哈:%@",[NSThread currentThread]); dispatch_async(dispatchQueue2, ^{ dispatch_group_wait(dispatchGroup, DISPATCH_TIME_FOREVER); NSLog(@"++++哈~终于等到你:%@",[NSThread currentThread]); });
NSLog(@"++++2:%@",[NSThread currentThread]);
|
日志:
1 2 3 4 5 6 7 8
| ++++1:<NSThread: 0x60000313a800>{number = 1, name = main} ++++稍等一哈:<NSThread: 0x60000313a800>{number = 1, name = main} ++++任务1:<NSThread: 0x600003164780>{number = 5, name = (null)} ++++2:<NSThread: 0x60000313a800>{number = 1, name = main} ++++任务3:<NSThread: 0x600003153cc0>{number = 6, name = (null)} ++++任务2:<NSThread: 0x600003176480>{number = 3, name = (null)} ++++任务完成:<NSThread: 0x60000313a800>{number = 1, name = main} ++++哈~终于等到你:<NSThread: 0x600003176880>{number = 4, name = (null)}
|
注意事项:
1、一个Group可以和多个queue关联。
2、如果提交到队列中的与Group关联起来的任务全都执行完毕,则会调用dispatch_group_notify并且dispatch_group_wait会停止等待;
3、假设一个队列中有2个任务,只有第二个与Group进行了关联,则只要第二个任务完成,不论第一个任务是否已完成,都会收到dispatch_group_notify通知。
4、dispatch_group_wait会阻塞当前线程,直到任务全部完成或等待时间超过设置的超时时间,所以不能在主线程调用。
2.dispatch_group_enter
dispatch_group_enter
+dispatch_group_leave
成对用,作用与dispatch_group_async
类似:
#示例8.2.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
| dispatch_queue_t queue = dispatch_queue_create("concurrentQueue1", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
NSLog(@"++++1");
dispatch_group_enter(group); dispatch_async(queue, ^{ for (int i = 0; i < 1000; i++) { if (888 == i) { NSLog(@"++++任务1"); } } dispatch_group_leave(group); });
dispatch_group_enter(group); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:5]; NSLog(@"++++任务2"); dispatch_group_leave(group); });
dispatch_group_notify(group, dispatch_get_main_queue(), ^(){ NSLog(@"++++任务完成"); });
|
与 dispatch_group_async 的区别(线程同步问题):
如果 dispatch_group_async
里执行的是异步代码,dispatch_group_notify
会直接触发而不会等待异步任务完成;而 dispatch_group_enter
、和 dispatch_group_leave
则不会有这个问题,它们只需要在任务开始前 enter
结束后 leave
即可达到线程同步的效果。
#示例8.2.2:dispatch_group_async
执行异步任务时的效果:
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
| dispatch_queue_t dispatchQueue1 = dispatch_queue_create("concurrentQueue1",DISPATCH_QUEUE_CONCURRENT);
dispatch_queue_t dispatchQueue2 = dispatch_queue_create("concurrentQueue2",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t dispatchGroup = dispatch_group_create();
dispatch_group_async(dispatchGroup, dispatchQueue1, ^(){ dispatch_async(dispatchQueue2, ^{ [NSThread sleepForTimeInterval:5]; NSLog(@"++++任务1"); }); });
dispatch_group_async(dispatchGroup, dispatchQueue1, ^(){ dispatch_async(dispatchQueue2, ^{ [NSThread sleepForTimeInterval:8]; NSLog(@"++++任务2"); }); });
dispatch_group_notify(dispatchGroup,dispatch_get_main_queue(), ^(){ NSLog(@"++++任务完成"); });
|
输出日志为:
notify
在任务开始时就触发了~
#示例8.2.3:dispatch_group_enter
与 leave
执行异步任务时的效果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| dispatch_queue_t queue = dispatch_queue_create("concurrentQueue1",DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:5]; NSLog(@"++++任务1"); dispatch_group_leave(group); });
dispatch_group_enter(group); dispatch_async(queue, ^{ [NSThread sleepForTimeInterval:8]; NSLog(@"++++任务2"); dispatch_group_leave(group); });
dispatch_group_notify(group,dispatch_get_main_queue(), ^(){ NSLog(@"++++任务完成"); });
|
日志:
notify
在任务结束时才触发!
3.其他方案
通过组合GCD已有的其他API,等效实现dispatch_group
的功能:
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
| NSUInteger totalTasks = 10;
volatile NSUInteger completedTasks = 0;
NSUInteger maxConcurrentTasks = 3;
dispatch_queue_t completionQueue = dispatch_queue_create("com.example.completion", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.concurrent", DISPATCH_QUEUE_CONCURRENT);
void (^taskCompletionBlock)(void) = ^{ NSUInteger count = OSAtomicIncrement64(&completedTasks); if (count == totalTasks) { dispatch_async(completionQueue, ^{ NSLog(@"所有任务都已完成"); }); } };
for (NSUInteger i = 0; i < totalTasks; i++) { dispatch_async(concurrentQueue, ^{ NSLog(@"任务 %lu 开始执行", i); [NSThread sleepForTimeInterval:1.0]; NSLog(@"任务 %lu 完成", i); taskCompletionBlock(); }); if (i % maxConcurrentTasks == 0) { dispatch_barrier_sync(concurrentQueue, ^{ }); } }
|
9.挂起/恢复:
dispatch_suspend
-挂起,dispatch_resume
-恢复,可以暂停、恢复队列上的任务。但suspend
并不保证能立即停止队列上已经在运行的 block。
#示例9.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
| dispatch_queue_t queue = dispatch_queue_create("Queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{ sleep(5); NSLog(@"++++第1个任务 延时5秒咯"); });
dispatch_async(queue, ^{ sleep(5); NSLog(@"++++第2个任务 延时5秒咯"); });
NSLog(@"++++延时1秒"); sleep(1);
NSLog(@"++++挂起队列"); dispatch_suspend(queue);
NSLog(@"++++延时10秒"); sleep(10);
NSLog(@"++++恢复队列"); dispatch_resume(queue);
|
日志:
1 2 3 4 5 6
| 12:58:11.187713 ++++延时1秒 12:58:12.189074 ++++挂起队列 12:58:12.189450 ++++延时10秒 12:58:16.193140 ++++第1个任务 延时5秒咯 12:58:22.190424 ++++恢复队列 12:58:27.192363 ++++第2个任务 延时5秒咯
|
可以看出,在dispatch_suspend
挂起队列后,第一个 block 还是在运行,并且正常输出。
10.dispatch_set_target_queue
1.设置队列优先级
我们自己创建的队列使用的是默认优先级,而系统提供的全局队列则可以指定优先级,通过dispatch_set_target_queue
方法我们可以让自定义队列的优先级与全局队列保持一致,从而达到修改自定义队列优先级的目的。
1 2 3 4 5 6
| dispatch_queue_t serialQueue = dispatch_queue_create("com.xx.xx",DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND,0);
dispatch_set_target_queue(serialQueue, globalQueue);
|
2.任务调度
修改队列中任务的目标队列,把需要执行的任务对象指定到目标队列中去处理。
#示例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| - (void)seriaQues{ dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL); dispatch_async(queue1, ^{ NSLog(@"++++任务1开始"); [NSThread sleepForTimeInterval:3.f]; NSLog(@"++++任务1结束"); }); dispatch_async(queue2, ^{ NSLog(@"++++任务2开始"); [NSThread sleepForTimeInterval:2.f]; NSLog(@"++++任务2结束"); }); dispatch_async(queue3, ^{ NSLog(@"++++任务3开始"); [NSThread sleepForTimeInterval:1.f]; NSLog(@"++++任务3结束"); }); }
|
日志:
1 2 3 4 5 6
| ++++ ++++ ++++ ++++ ++++ ++++
|
多个串行队列异步执行时,block中各任务是并发执行的,它们之间开始和结束的顺序是不确定的。如果想让这些任务按照先后顺序一个个执行,则可以使用dispatch_set_target_queue
:
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
| - (void)seriaMultQue{ dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue2 = dispatch_queue_create("queue2", DISPATCH_QUEUE_SERIAL); dispatch_queue_t queue3 = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL); dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL); dispatch_set_target_queue(queue1, targetQueue); dispatch_set_target_queue(queue2, targetQueue); dispatch_set_target_queue(queue3, targetQueue); dispatch_async(queue1, ^{ NSLog(@"++++任务1开始"); [NSThread sleepForTimeInterval:3.f]; NSLog(@"++++任务1结束"); }); dispatch_async(queue2, ^{ NSLog(@"++++任务2开始"); [NSThread sleepForTimeInterval:2.f]; NSLog(@"++++任务2结束"); }); dispatch_async(queue3, ^{ NSLog(@"++++任务3开始"); [NSThread sleepForTimeInterval:1.f]; NSLog(@"++++任务3结束"); }); }
|
日志:
1 2 3 4 5 6
| ++++ ++++ ++++ ++++ ++++ ++++
|
原先相互之间并发执行的三个任务,现在有序执行了~
另外,执行这个操作后,queue1
上已在执行的任务会继续在queue1
执行,尚未执行的任务会在targetQueue
上执行。
11.设置队列标志
作用:dispatch_queue_set_specific
用来向指定队列里面设置一个标识,配合dispatch_get_specific
使用。
#示例11.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
| - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { static const void *key = "queueKey"; void *context = "myQueueContext"; dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL); dispatch_queue_set_specific(queue1, key,context, NULL); if (dispatch_get_specific(key)) { NSLog(@"++++主队列++queue1?:YES"); }else{ NSLog(@"++++主队列++queue1?:NO"); } dispatch_async(queue1, ^{ if (dispatch_get_specific(key)) { NSLog(@"++++异步串行队列++queue1?:YES"); }else{ NSLog(@"++++异步串行队列++queue1?:NO"); } }); return YES; }
|
日志:
12.dispatch_block_cancel
作用:取消单个任务。
这个任务必须是用dispatch_block_create
创建dispatch_block_t
。再者,此方法只对尚未执行的block有效,对正在执行中的任务无效。
#示例12.1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| dispatch_queue_t conQueue = dispatch_queue_create("com.xxx.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_block_t block1 = dispatch_block_create(0, ^{ sleep(5); NSLog(@"++++block1"); });
dispatch_block_t block2 = dispatch_block_create(0, ^{ NSLog(@"++++block2"); });
dispatch_block_t block3 = dispatch_block_create(0, ^{ NSLog(@"++++block3"); });
dispatch_async(conQueue, block1); dispatch_async(conQueue, block2); dispatch_async(conQueue, block3); dispatch_block_cancel(block3);
|
日志:
13.dispatch_semaphore
信号量:就是一种可用来控制访问资源的数量的标识。当一个线程在进入一段关键代码之前,线程须获取一个信号量,一旦该关键代码段执行完成,那么该线程须释放其持有的信号量。其它想进入该关键代码段的线程须等待可用信号量,如果没有可用信号量,则需等待前面的线程释放信号量。
有一个经典的停车场的例子:信号量的值就相当于剩余车位的数目,dispatch_semaphore_wait
就相当于来了一辆车,dispatch_semaphore_signal
就相当于走了一辆车。停车位的剩余数目在初始化的时候就已经指明了(dispatch_semaphore_create(value:Int))),调用一次dispatch_semaphore_signal
,剩余的车位就增加一个;调用一次dispatch_semaphore_wait
剩余车位就减少一个;当剩余车位为0时,再来车(即调用dispatch_semaphore_wait
)就只能等待。有耐心的车主会一直等下去,没耐心的车主在等待“一段时间”之后就会离开。
1 2 3 4 5 6 7 8
| //创建信号量,参数:信号量的初值,如果小于0则会返回NULL dispatch_semaphore_create(信号量值)
//减少信号量,时间:DISPATCH_TIME_NOW、DISPATCH_TIME_FOREVER dispatch_semaphore_wait(信号量,等待时间)
//释放信号量 dispatch_semaphore_signal(信号量)
|
#示例13.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
| - (void)semaphore{ dispatch_queue_t mQueue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT); dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); NSLog(@"+++1"); dispatch_async(mQueue, ^{ sleep(2); NSLog(@"+++first task"); dispatch_semaphore_signal(semaphore); }); NSLog(@"+++2"); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"+++3"); dispatch_async(mQueue, ^{ sleep(2); NSLog(@"+++second task"); dispatch_semaphore_signal(semaphore); }); NSLog(@"+++4"); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"+++5"); }
|
输出日志:
1 2 3 4 5 6 7
| 13:18:59 +++1 13:18:59 +++2 13:19:01 +++first task 13:19:01 +++3 13:19:01 +++4 13:19:03 +++second task 13:19:03 +++5
|
示例中创建了初始值=0的信号量,随后开始了两个异步打印数字的任务。从日志来看,“+++first task” 在”+++3” 之前,而“+++5”是最后才打印的,这是因为信号量=0时dispatch_semaphore_wait()
处自动阻塞了当前线程,两个打印任务执行完之后dispatch_semaphore_signal()
使信号量=1,当前线程才得以继续执行。
#示例13.2:
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
| dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_queue_t quene = dispatch_queue_create("com.M.D", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(quene, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"+++任务1"); sleep(1); NSLog(@"+++完成任务1"); dispatch_semaphore_signal(semaphore); });
dispatch_async(quene, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"+++任务2"); sleep(1); NSLog(@"+++完成任务2"); dispatch_semaphore_signal(semaphore); });
dispatch_async(quene, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"+++任务3"); sleep(1); NSLog(@"+++完成任务3"); dispatch_semaphore_signal(semaphore); });
|
输出日志:
ps,信号量的使用只保证了并发场景下每次只有一个线程执行任务,并不能保证任务的执行顺序,再运行一次后日志如下:
14.GCD倒计时
#示例14.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
|
- (void) GCDTimer { __block int timeout = 60; dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); _aTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,queue); dispatch_source_set_timer(_aTimer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0); dispatch_source_set_event_handler(_aTimer, ^{ if(timeout<=0){ dispatch_source_cancel(_aTimer); dispatch_async(dispatch_get_main_queue(), ^{ }); } else{ dispatch_async(dispatch_get_main_queue(), ^{ }); timeout--; } }); dispatch_resume(_aTimer); }
|
GCD定时器并不是由NSTimer或者CFRunLoopTimer实现的,无需加入到RunloopMode中,所以不会出现因RunloopMode切换而失效的问题。
15.GCD读写锁
为了保证数据访问或者方法调用时的线程安全,一般我们可以使用锁
来实现同步机制,比如@synchronized()
和NSLock
等。但是,频繁的上锁、释放锁的操作会降低执行效率。所以我们可以考虑使用dispatch_barrier_async
,通过 GCD 对任务的调度来达到相同的效果。
思路:dispatch_barrier_async
能确保在其之前提交到队列的任务先执行,然后执行它自己提交的任务,最后执行其后提交的任务。利用这个特点,我们可以将数据的读写操作放入一个并发队列中,写数据的任务通过栅栏
提交到队列中,读数据的任务则可以同步的提交到队列中。这样就能保证执行写操作时,读操作或其他写操作都被阻塞,而执行读操作时,写操作被阻塞,将读写分开从而避免数据的污染。
#示例15.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 39 40 41 42 43 44 45 46 47
| @interface AppDelegate() @property(nonatomic, strong) NSMutableArray *mArr; @property (nonatomic, strong) dispatch_queue_t mPropertyConcurrentQueue; @end
@implementation AppDelegate
@synthesize mArr = _mArr;
- (NSMutableArray *)mArr{ __block NSMutableArray *tmpArr; dispatch_sync(_mPropertyConcurrentQueue, ^{ tmpArr = _mArr; }); return tmpArr; }
- (void)setMArr:(NSMutableArray *)mArr{ dispatch_barrier_async(_mPropertyConcurrentQueue, ^{ _mArr = mArr; }); }
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { _mPropertyConcurrentQueue = dispatch_queue_create("readWriteQueue", DISPATCH_QUEUE_CONCURRENT); for (int i = 0; i < 10; i++) { dispatch_queue_t tmpQueue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT); if ((i % 2) == 0) { dispatch_async(tmpQueue, ^{ NSLog(@"+++Reset array,thread:%@",[NSThread currentThread]); self.mArr = [NSMutableArray array]; }); }else{ dispatch_async(tmpQueue, ^{ NSLog(@"++++Add num:%d,thread:%@",i,[NSThread currentThread]); [self.mArr addObject:@(i)]; }); } } return YES; } @end
|