1样傍、Dispatch Group
在追加到Dispatch Queue中的多個(gè)任務(wù)處理完畢之后想執(zhí)行結(jié)束處理横缔,這種需求會經(jīng)常出現(xiàn)。如果只是使用一個(gè)Serial Dispatch Queue(串行隊(duì)列)時(shí)衫哥,只要將想執(zhí)行的處理全部追加到該串行隊(duì)列中并在最后追加結(jié)束處理即可茎刚,但是在使用Concurrent Queue 時(shí),可能會同時(shí)使用多個(gè)Dispatch Queue時(shí)撤逢,源代碼就會變得很復(fù)雜膛锭。
在這種情況下粮坞,就可以使用Dispatch Group。
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");
});
控制臺的輸出:done是最后輸出的初狰,其他三個(gè)都是隨機(jī)輸出的
因?yàn)橄駽oncurrent Dispatch Queue 追加處理莫杈,多個(gè)線程并行執(zhí)行,所以追加處理的執(zhí)行順序不定奢入。執(zhí)行順序會發(fā)生變化筝闹,但是此執(zhí)行結(jié)果的done一定是最后輸出的。
無論向什么樣的Dispatch Queue中追加處理腥光,使用Dispatch Group都可以監(jiān)視這些處理執(zhí)行的結(jié)果丁存。一旦檢測到所有處理執(zhí)行結(jié)束,就可以將結(jié)束的處理追加到Dispatch Queue中柴我,這就是使用Dispatch Group的原因。
-----下面試一個(gè)使用Dispatch Group異步下載兩張圖片扩然,然后合并成一張圖片的medo(注意艘儒,我們總是應(yīng)該在主線程中更新UI):
#import "ViewController.h"
@interface ViewController (){
NSData *_twoData;
NSData *_oneData;
}
@property (nonatomic, strong) UILabel *textLabel;
@property (nonatomic, strong) UIImageView *oneImgView;
@property (nonatomic, strong) UIImageView *twoImgView;
@property (nonatomic, strong) UIImageView *compositeImg;//合成圖片、
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.view.backgroundColor = [UIColor whiteColor];
[self operation1];
self.oneImgView = [[UIImageView alloc]initWithFrame:CGRectMake(20, 100,(self.view.frame.size.width-60)/2, 150)];
[self.view addSubview:self.oneImgView];
self.twoImgView = [[UIImageView alloc]initWithFrame:CGRectMake(40+(self.view.frame.size.width-60)/2, 100,(self.view.frame.size.width-60)/2, 150)];
[self.view addSubview:self.oneImgView];
[self.view addSubview:self.twoImgView];
}
- (void)operation1 {
UILabel *textLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 64,self.view.frame.size.width, 30)];
textLabel.text = @"正在下載圖片";
textLabel.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:textLabel];
self.textLabel = textLabel;
[self group];
NSLog(@"在下載圖片的時(shí)候夫偶,主線程貌似還可以干點(diǎn)什么");
}
- (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(@"正在下載第一張圖片");
_oneData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://upload-images.jianshu.io/upload_images/1749933-7dba2836c716ea95.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240"]];
self.oneImgView.image = [UIImage imageWithData:_oneData];
NSLog(@"第一張圖片下載完畢");
});
dispatch_group_async(group, queue, ^{
NSLog(@"正在下載第二張圖片");
_twoData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@"http://images2015.cnblogs.com/blog/471463/201509/471463-20150912212457684-585830854.png"]];
NSLog(@"第二張圖片下載完畢");
self.twoImgView.image = [UIImage imageWithData:_twoData];
});
dispatch_group_notify(group, queue, ^{
UIGraphicsBeginImageContext(CGSizeMake(375, 300));
// [self.oneImgView drawRect:CGRectMake(0, 0, 150, 400)];
[self.oneImgView.image drawInRect:CGRectMake(0, 0, self.view.frame.size.width/2, 250)];
[self.twoImgView.image drawInRect:CGRectMake(self.view.frame.size.width/2,0, self.view.frame.size.width/2, 250)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndPDFContext();
dispatch_async(dispatch_get_main_queue(), ^{
UIImageView *imageView = [[UIImageView alloc] initWithImage:newImage];
imageView.frame = CGRectMake(0, 400, self.view.frame.size.width, 300);
[self.view addSubview:imageView];
self.textLabel.text = @"圖片合并完畢";
NSLog(@"圖片合并完畢");
self.oneImgView.image = [UIImage imageWithData:_oneData];
self.twoImgView.image = [UIImage imageWithData:_twoData];
});
});
}
@end
2界睁、dispatch_barrier_async
在訪問數(shù)據(jù)庫或者文件的時(shí)候,我們可以使用Serial Dispatch Queue可避免數(shù)據(jù)競爭問題兵拢,代碼如下所示:
先看看翻斟,如果我們在平常編碼中,如果要保證某個(gè)屬性可以線程安全的讀寫说铃,如何寫的:
#import <Foundation/Foundation.h>
@interface ZYPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
#import "ZYPerson.h"
-(void)setName:(NSString *)name {
@synchronized(self) {
_name = [name copy];
}
}
-(NSString *)name {
@synchronized(self) {
return _name;
}
}
@end
這是我在剛學(xué)iOS開發(fā)访惜,剛涉及并發(fā)中的數(shù)據(jù)競爭時(shí),書本上提到的一種解決方案腻扇。如果有多個(gè)線程要執(zhí)行同一份代碼债热,那么有時(shí)候可能會出現(xiàn)問題,這種情況下幼苛,通常要使用鎖來實(shí)現(xiàn)某種同步機(jī)制窒篱。iOS提供了一種加鎖的方式,就是采用內(nèi)置的synchronization block舶沿,也就是上面代碼所寫的墙杯。
這種寫法會根據(jù)給定的對象,自動創(chuàng)建一個(gè)鎖括荡,并等待塊中的代碼執(zhí)行完畢高镐。執(zhí)行到這段代碼結(jié)尾處,鎖也就釋放了畸冲。在上面的例子中避消,同步行為所針對的對象是self低滩。這么寫通常沒錯,但是@synchronized(self)會大大降低代碼效率岩喷,甚至很多時(shí)候恕沫,還可以被人感覺到效率明顯下降了,因?yàn)楣灿猛粋€(gè)鎖的那些同步塊纱意,都必須按順序執(zhí)行婶溯。若在self對象上頻繁加鎖,那么程序可能就要等另一段與此無關(guān)的代碼執(zhí)行完畢偷霉,才可以繼續(xù)執(zhí)行當(dāng)前代碼迄委,這樣做是很沒必要的。
@synchronized(self)會大大降低代碼效率类少,因?yàn)樗械耐綁K( @synchronized(self) )都會彼此搶奪同一個(gè)鎖叙身。要是有多個(gè)屬性這么寫,每個(gè)屬性的同步塊( @synchronized(self) )都要等其他所有的同步塊執(zhí)行完畢之后才能執(zhí)行硫狞,這并不是我們想要的結(jié)果信轿,我們只想要每個(gè)屬性各自獨(dú)立的同步。
還有残吩,不得不說财忽,按上面這么做,雖然可以在一定程度上提供“線程安全”泣侮,但卻無法保證訪問該對象時(shí)是絕對線程安全的即彪。事實(shí)上,上面的寫法活尊,就是atomic隶校,也就是原子性屬性xcode自動生成的代碼,這種方法蛹锰,在訪問屬性時(shí)惠况,必定可以從中得到有效值,然而如果在一個(gè)線程上多次調(diào)用getter方法宁仔,每次得到的結(jié)果卻未必相同稠屠,在兩次讀操作之間,其他線程可能會寫入新的屬性值翎苫。
其實(shí)使用GCD可以簡單高效的代替同步塊或者鎖對象权埠,可以使用,串行同步隊(duì)列煎谍,將讀操作以及寫操作都安排在同一個(gè)隊(duì)列里攘蔽,即可保證數(shù)據(jù)同步,代碼如下:
#import <Foundation/Foundation.h>
@interface ZYPerson : NSObject
@property (nonatomic, copy) NSString *name;
@end
#import "ZYPerson.h"
@interface 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
這樣寫的思路是:把寫操作與讀操作都安排在同一個(gè)同步串行隊(duì)列里面執(zhí)行呐粘,這樣的話满俗,所有針對屬性的訪問操作就都同步了转捕。
這種方法的確已經(jīng)足夠好了,但還不是最優(yōu)的唆垃,它只可以實(shí)現(xiàn)單讀五芝、單寫。整體來看辕万,我們最終要解決的問題是枢步,在寫的過程中不能被讀,以免數(shù)據(jù)不對渐尿,但是讀與讀之間并沒有任何的沖突醉途!
多個(gè)getter方法(也就是讀取)是可以并發(fā)執(zhí)行的砖茸,而getter(讀)與setter(寫)方法是不能并發(fā)執(zhí)行的隘擎,利用這個(gè)特點(diǎn),還能寫出更快的代碼來凉夯,這次注意货葬,不用串行隊(duì)列,而改用并行隊(duì)列:
-(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
這樣優(yōu)化恍涂,測試一下性能,可以發(fā)現(xiàn)這種做法肯定比使用串行隊(duì)列要快植榕。
在這個(gè)代碼中再沧,我用了點(diǎn)新的東西,dispatch_barrier_async尊残,可以翻譯成柵欄(barrier)炒瘸,它可以往隊(duì)列里面發(fā)送任務(wù)(塊,也就是block)寝衫,這個(gè)任務(wù)有柵欄(barrier)的作用顷扩。
在隊(duì)列中,barrier塊必須單獨(dú)執(zhí)行慰毅,不能與其他block并行隘截。這只對并發(fā)隊(duì)列有意義,并發(fā)隊(duì)列如果發(fā)現(xiàn)接下來要執(zhí)行的block是個(gè)barrier block汹胃,那么就一直要等到當(dāng)前所有并發(fā)的block都執(zhí)行完畢婶芭,才會單獨(dú)執(zhí)行這個(gè)barrier block代碼塊,等到這個(gè)barrier block執(zhí)行完畢着饥,再繼續(xù)正常處理其他并發(fā)block犀农。在上面的代碼中,setter方法中使用了barrier block以后宰掉,對象的讀取操作依然是可以并發(fā)執(zhí)行的呵哨,但是寫入操作就必須單獨(dú)執(zhí)行了赁濒。
(1)并行:就是隊(duì)列里面的任務(wù)(代碼塊,block)不是一個(gè)個(gè)執(zhí)行孟害,而是并發(fā)執(zhí)行拒炎,也就是可以同時(shí)執(zhí)行的意思
(2)串行:隊(duì)列里面的任務(wù)一個(gè)接著一個(gè)執(zhí)行,要等前一個(gè)任務(wù)結(jié)束纹坐,下一個(gè)任務(wù)才可以執(zhí)行
(3)異步:具有新開線程的能力
(4)同步:不具有新開線程的能力枝冀,只能在當(dāng)前線程執(zhí)行任務(wù)
(5)并行+異步:就是真正的并發(fā),新開有有多個(gè)線程處理任務(wù)耘子,任 務(wù)并發(fā)執(zhí)行(不按順序執(zhí)行)
(6)串行+異步:新開一個(gè)線程果漾,任務(wù)一個(gè)接一個(gè)執(zhí)行,上一個(gè)任務(wù)處理完畢谷誓,下一個(gè)任務(wù)才可以被執(zhí)行
(7)并行+同步:不新開線程绒障,任務(wù)一個(gè)接一個(gè)執(zhí)行
(8)串行+同步:不新開線程,任務(wù)一個(gè)接一個(gè)執(zhí)行
并行+同步
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]);
});
});
打雍赐帷:在一個(gè)線程里面户辱,任務(wù)一個(gè)接一個(gè)的執(zhí)行。
并行+同步糙臼,并發(fā)調(diào)用:
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]);
});
});
打勇洹:新開了多個(gè)線程,任務(wù)并發(fā)執(zhí)行变逃。