dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_queue_create("com.gcd-group.www", DISPATCH_QUEUE_CONCURRENT); dispatch_group_async(group, queue, ^{ for (int i = 0; i < 1000; i++) { if (i == 999) { NSLog(@"11111111"); } } }); dispatch_group_async(group, queue, ^{ NSLog(@"22222222"); }); dispatch_group_async(group, queue, ^{ NSLog(@"33333333"); }); dispatch_group_notify(group, queue, ^{ NSLog(@"done"); });
控制台的输出:
因为向Concurrent Dispatch Queue 追加处理,多个线程并行执行,所以追加处理的执行顺序不定。执行顺序会发生变化,但是此执行结果的done一定是最后输出的。
无论向什么样的Dispatch Queue中追加处理,使用Dispatch Group都可以监视这些处理执行的结果。一旦检测到所有处理执行结束,就可以将结束的处理追加到Dispatch Queue中,这就是使用Dispatch Group的原因。
下面试一个使用Dispatch Group异步下载两张图片,然后合并成一张图片的medo(注意,我们总是应该在主线程中更新UI):
#import "ViewController.h" @interface ViewController () @property (nonatomic, strong) UIImage *imageOne; @property (nonatomic, strong) UIImage *imageTwo; @property (nonatomic, weak) UILabel *textLabel; @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; [self operation1]; } - (void)operation1 { UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(200, 450, 0, 0)]; textLabel.text = @"正在下载图片"; [textLabel sizeToFit]; [self.view addSubview:textLabel]; self.textLabel = textLabel; [self group]; NSLog(@"在下载图片的时候,主线程貌似还可以干点什么"); } - (void)group { UIImageView *imageView = [[UIImageView alloc] init]; [self.view addSubview:imageView]; dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_queue_create("cn.gcd-group.www", DISPATCH_QUEUE_CONCURRENT); dispatch_group_async(group, queue, ^{ NSLog(@"正在下载第一张图片"); NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://images2015.cnblogs.com/blog/471463/201509/471463-20150912213125372-589808688.png"]]; NSLog(@"第一张图片下载完毕"); self.imageOne = [UIImage imageWithData:data]; }); dispatch_group_async(group, queue, ^{ NSLog(@"正在下载第二张图片"); NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://images2015.cnblogs.com/blog/471463/201509/471463-20150912212457684-585830854.png"]]; NSLog(@"第二张图片下载完毕"); self.imageTwo = [UIImage imageWithData:data]; }); dispatch_group_notify(group, queue, ^{ UIGraphicsBeginImageContext(CGSizeMake(300, 400)); [self.imageOne drawInRect:CGRectMake(0, 0, 150, 400)]; [self.imageTwo drawInRect:CGRectMake(150, 0, 150, 400)]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); dispatch_async(dispatch_get_main_queue(), ^{ UIImageView *imageView = [[UIImageView alloc] initWithImage:newImage]; [self.view addSubview:imageView]; self.textLabel.text = @"图片合并完毕"; }); }); } @end
#import <Foundation/Foundation.h> @interface ZYPerson : NSObject @property (nonatomic, copy) NSString *name; @end #import "ZYPerson.h" static NSString *_name; @implementation ZYPerson - (void)setName:(NSString *)name { @synchronized(self) { _name = [name copy]; } } - (NSString *)name { @synchronized(self) { return _name; } } @end
这是我在刚学iOS开发,刚涉及并发中的数据竞争时,书本上提到的一种解决方案。如果有多个线程要执行同一份代码,那么有时候可能会出现问题,这种情况下,通常要使用锁来实现某种同步机制。iOS提供了一种加锁的方式,就是采用内置的synchronization block,也就是上面代码所写的。
这种写法会根据给定的对象,自动创建一个锁,并等待块中的代码执行完毕。执行到这段代码结尾处,锁也就释放了。在上面的例子中,同步行为所针对的对象是self。这么写通常没错,但是@synchronized(self)会大大降低代码效率,甚至很多时候,还可以被人感觉到效率明显下降了,因为共用同一个锁的那些同步块,都必须按顺序执行。若在self对象上频繁加锁,那么程序可能就要等另一段与此无关的代码执行完毕,才可以继续执行当前代码,这样做是很没必要的。#import <Foundation/Foundation.h> @interface ZYPerson : NSObject @property (nonatomic, copy) NSString *name; @end #import "ZYPerson.h" @interface ZYPerson () @end static NSString *_name; static dispatch_queue_t _queue; @implementation ZYPerson - (instancetype)init { if (self = [super init]) { _queue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_SERIAL); } return self; } - (void)setName:(NSString *)name { dispatch_sync(_queue, ^{ _name = [name copy]; }); } - (NSString *)name { __block NSString *tempName; dispatch_sync(_queue, ^{ tempName = _name; }); return tempName; } @end
这样写的思路是:把写操作与读操作都安排在同一个同步串行队列里面执行,这样的话,所有针对属性的访问操作就都同步了。
这种方法的确已经足够好了,但还不是最优的,它只可以实现单读、单写。整体来看,我们最终要解决的问题是,在写的过程中不能被读,以免数据不对,但是读与读之间并没有任何的冲突!
多个getter方法(也就是读取)是可以并发执行的,而getter(读)与setter(写)方法是不能并发执行的,利用这个特点,还能写出更快的代码来,这次注意,不用串行队列,而改用并行队列:
#import <Foundation/Foundation.h> @interface ZYPerson : NSObject @property (nonatomic, copy) NSString *name; @end #import "ZYPerson.h" @interface ZYPerson () @end static NSString *_name; static dispatch_queue_t _concurrentQueue; @implementation ZYPerson - (instancetype)init { if (self = [super init]) { _concurrentQueue = dispatch_queue_create("com.person.syncQueue", DISPATCH_QUEUE_CONCURRENT); } return self; } - (void)setName:(NSString *)name { dispatch_barrier_async(_concurrentQueue, ^{ _name = [name copy]; }); } - (NSString *)name { __block NSString *tempName; dispatch_sync(_concurrentQueue, ^{ tempName = _name; }); return tempName; } @end
这样优化,测试一下性能,可以发现这种做法肯定比使用串行队列要快。
在这个代码中,我用了点新的东西,dispatch_barrier_async,可以翻译成栅栏(barrier),它可以往队列里面发送任务(块,也就是block),这个任务有栅栏(barrier)的作用。
在队列中,barrier块必须单独执行,不能与其他block并行。这只对并发队列有意义,并发队列如果发现接下来要执行的block是个barrier block,那么就一直要等到当前所有并发的block都执行完毕,才会单独执行这个barrier block代码块,等到这个barrier block执行完毕,再继续正常处理其他并发block。在上面的代码中,setter方法中使用了barrier block以后,对象的读取操作依然是可以并发执行的,但是写入操作就必须单独执行了。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"11 %@",[NSThread currentThread]); }); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"22 %@",[NSThread currentThread]); }); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"33 %@",[NSThread currentThread]); }); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"44 %@",[NSThread currentThread]); }); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"55 %@",[NSThread currentThread]); }); });
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"11 %@",[NSThread currentThread]); }); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"22 %@",[NSThread currentThread]); }); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"33 %@",[NSThread currentThread]); }); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"44 %@",[NSThread currentThread]); }); }); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"55 %@",[NSThread currentThread]); }); });