在上一篇中,咱们主要讲了Dispatch Queue相关的内容。这篇主要讲一下一些和实际相关的使用实例,Dispatch Groups和Dispatch Semaphore。安全
在咱们开发过程当中常常会用到在多少秒后执行某个方法,一般咱们会用这个- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
函数。不过如今咱们可使用一个新的方法。多线程
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); dispatch_after(delayTime, dispatch_get_main_queue(), ^{ //do your task });
这样咱们就定义了一个延迟2秒后执行的任务。不过在这里有一点须要说明的是,不管你用的是- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
仍是dispatch_after
这个方法。并非说在你指定的延迟后当即运行,这些方法都是基于单线程的,它只是将你延迟的操做加入到队列里面去。因为队列里面都是FIFO,因此必须在你这个任务以前的操做完成后才会执行你的方法。这个延迟只是大概的延迟。若是你在主线程里面调用这个方法,若是你主线程如今正在处理一个很是耗时的任务,那么你这个延迟可能就会误差很大。这个时候你能够再开个线程,在里面执行你的延迟操做。并发
//放到全局默认的线程里面,这样就没必要等待当前调用线程执行完后再执行你的方法 dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC); dispatch_after(delayTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ //do your task });
这个想必你们都很是的熟悉,这个在单例初始化的时候是苹果官方推荐的方法。这个函数能够保证在应用程序中只执行指定的任务一次。即便在多线程的环境下执行,也能够保证百分之百的安全。app
static id instance; static dispatch_once_t predicate; dispatch_once(&predicate, ^{ //your init }); return instance; }
这里面的predicate
必须是全局或者静态对象。在多线程下同时访问时,这个方法将被线程同步等待,直到指定的block执行完成。async
这个方法是执行循环次数固定的迭代,若是在并发的queue里面能够提升性能。好比一个固定次数的for循环函数
for (int i = 0; i < 1000; i ++) { NSLog(@"---%d---", i); }
若是只是在一个线程里面或者在一个串行的队列中是同样的,一个个执行。
如今咱们用dispatch_apply来写这个循环:性能
dispatch_apply([array count], defaultQueue, ^(size_t i) { NSLog(@"----%@---", array[i]); }); NSLog(@"end");
这个方法执行后,它将像这个并发队列中不断的提交执行的block。这个i是从0开始的,最后一个是[array count] - 1
。测试
使用这个方法有几个注意点:spa
end
。在实际开发中,咱们可能须要在一组操做所有完成后,才作其余操做。好比上传一组图片,或者下载多个文件。但愿在所有完成时给用户一个提示。若是这些操做在串行化的队列中执行的话,那么你能够很明确的知道,当最后一个任务执行完成后,就所有完成了。这样的操做也并木有发挥多线程的优点。咱们能够在并发的队列中进行这些操做,可是这个时候咱们就不知道哪一个是最后一个完成的了。这个时候咱们能够借助dispatch_group:线程
dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, defaultQueue, ^{ //task1 NSLog(@"1"); }); dispatch_group_async(group, defaultQueue, ^{ //task2 NSLog(@"2"); }); dispatch_group_async(group, defaultQueue, ^{ //task3 NSLog(@"3"); }); dispatch_group_async(group, defaultQueue, ^{ //task4 NSLog(@"4"); }); dispatch_group_async(group, defaultQueue, ^{ //task5 NSLog(@"5"); }); dispatch_group_notify(group, queue, ^{ NSLog(@"finish"); });
咱们首先建立一个group
而后往里面加入咱们要执行的操做,在dispatch_group_notify
这个函数里面添加所有完成的操做。上面代码执行的时候,输出的1,2,3,4,5的顺序是不必定的,可是输出的finish必定是在1,2,3,4,5以后。
对于添加到group的操做还有另一个方法:
dispatch_group_enter(group); dispatch_group_enter(group); dispatch_async(defaultQueue, ^{ NSLog(@"1"); dispatch_group_leave(group); }); dispatch_async(defaultQueue, ^{ NSLog(@"2"); dispatch_group_leave(group); }); dispatch_group_notify(group, queue, ^{ NSLog(@"finish"); });
咱们能够用dispatch_group_enter
来表示添加任务,dispatch_group_leave
来表示有个任务已经完成了。用这个方法必定要注意必须成双成对。
在多线程中一个比较重要的东西就是线程同步的问题。若是多个线程只是对某个资源只是读的过程,那么就不存在这个问题了。若是某个线程对这个资源须要进行写的操做,那这个时候就会出现数据不一致的问题了。
__block NSString *strTest = @"test"; dispatch_async(defaultQueue, ^{ if ([strTest isEqualToString:@"test"]) { NSLog(@"--%@--1-", strTest); [NSThread sleepForTimeInterval:1]; if ([strTest isEqualToString:@"test"]) { [NSThread sleepForTimeInterval:1]; NSLog(@"--%@--2-", strTest); } else { NSLog(@"====changed==="); } } }); dispatch_async(defaultQueue, ^{ NSLog(@"--%@--3-", strTest); }); dispatch_async(defaultQueue, ^{ strTest = @"modify"; NSLog(@"--%@--4-", strTest); });
看看这个模拟的场景,咱们让各个线程去访问这个变量,其中有个操做是要修改这个变量。咱们把第一个操做先判断有木有改变,而后故意延迟一下,这个时候咱们看下输出结果:
2015-01-03 15:42:21.351 测试[1652:60015] --test--3- 2015-01-03 15:42:21.351 测试[1652:60013] --modify--4- 2015-01-03 15:42:21.351 测试[1652:60014] --test--1- 2015-01-03 15:42:22.355 测试[1652:60014] ====changed===
咱们能够看到,再次判断的时候,已经被修改了,若是咱们在实际的业务中这样去判断某些关键性的变量,可能就会出现严重的问题。下面看看咱们如何使用dispatch_barrier_async
来进行同步:
//并发队列 dispatch_queue_t concurrentQueue = dispatch_queue_create("com.gcd.concurrentQueue", DISPATCH_QUEUE_CONCURRENT); __block NSString *strTest = @"test"; dispatch_async(concurrentQueue, ^{ if ([strTest isEqualToString:@"test"]) { NSLog(@"--%@--1-", strTest); [NSThread sleepForTimeInterval:1]; if ([strTest isEqualToString:@"test"]) { [NSThread sleepForTimeInterval:1]; NSLog(@"--%@--2-", strTest); } else { NSLog(@"====changed==="); } } }); dispatch_async(concurrentQueue, ^{ NSLog(@"--%@--3-", strTest); }); dispatch_barrier_async(concurrentQueue, ^{ strTest = @"modify"; NSLog(@"--%@--4-", strTest); }); dispatch_async(concurrentQueue, ^{ NSLog(@"--%@--5-", strTest); });
如今看下输出结果:
2015-01-03 16:00:27.552 测试[1786:65947] --test--1- 2015-01-03 16:00:27.552 测试[1786:65965] --test--3- 2015-01-03 16:00:29.553 测试[1786:65947] --test--2- 2015-01-03 16:00:29.553 测试[1786:65947] --modify--4- 2015-01-03 16:00:29.553 测试[1786:65947] --modify--5-
如今咱们能够发现操做4用dispatch_barrier_async
加入操做后,前面的操做3以前都操做完成以前这个strTest
都没有变。然后面的操做都是改变后的值。这样咱们的数据冲突的问题就解决了。
如今说明下这个函数干的事情,当这个函数加入到队列后,里面block并非当即执行的,它会先等待以前正在执行的block所有完成后,才执行,而且在它以后加入到队列中的block也在它操做结束后才能恢复以前的并发执行。咱们能够把这个函数理解为一条分割线,以前的操做,以后加入的操做。还有一个点要说明的是这个queue
必须是用dispatch_queue_create
建立出来的才行。
dispatch_semaphore_t
相似信号量,能够用来控制访问某一资源访问数量。
使用过程:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); __block NSString *strTest = @"test"; dispatch_async(concurrentQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); if ([strTest isEqualToString:@"test"]) { NSLog(@"--%@--1-", strTest); [NSThread sleepForTimeInterval:1]; if ([strTest isEqualToString:@"test"]) { [NSThread sleepForTimeInterval:1]; NSLog(@"--%@--2-", strTest); } else { NSLog(@"====changed==="); } } dispatch_semaphore_signal(semaphore); }); dispatch_async(concurrentQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"--%@--3-", strTest); dispatch_semaphore_signal(semaphore); }); dispatch_async(concurrentQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); strTest = @"modify"; NSLog(@"--%@--4-", strTest); dispatch_semaphore_signal(semaphore); }); dispatch_async(concurrentQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"--%@--5-", strTest); dispatch_semaphore_signal(semaphore); });
这样咱们同样能够保证,线程的数据安全。