GCD (dispatch_group虚循、dispatch_barrier、基于線程安全的多讀單寫)

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

屏幕快照 2017-05-18 下午4.34.37.png

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í)行变逃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末必逆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子揽乱,更是在濱河造成了極大的恐慌名眉,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凰棉,死亡現(xiàn)場離奇詭異损拢,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)撒犀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評論 2 382
  • 文/潘曉璐 我一進(jìn)店門福压,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人或舞,你說我怎么就攤上這事隧膏。” “怎么了嚷那?”我有些...
    開封第一講書人閱讀 152,998評論 0 344
  • 文/不壞的土叔 我叫張陵胞枕,是天一觀的道長。 經(jīng)常有香客問我魏宽,道長腐泻,這世上最難降的妖魔是什么决乎? 我笑而不...
    開封第一講書人閱讀 55,323評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮派桩,結(jié)果婚禮上构诚,老公的妹妹穿的比我還像新娘。我一直安慰自己铆惑,他們只是感情好范嘱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著员魏,像睡著了一般丑蛤。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撕阎,一...
    開封第一講書人閱讀 49,079評論 1 285
  • 那天受裹,我揣著相機(jī)與錄音,去河邊找鬼虏束。 笑死棉饶,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的镇匀。 我是一名探鬼主播照藻,決...
    沈念sama閱讀 38,389評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼汗侵!你這毒婦竟也來了幸缕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,019評論 0 259
  • 序言:老撾萬榮一對情侶失蹤晃择,失蹤者是張志新(化名)和其女友劉穎冀值,沒想到半個(gè)月后也物,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宫屠,經(jīng)...
    沈念sama閱讀 43,519評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評論 2 325
  • 正文 我和宋清朗相戀三年滑蚯,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了浪蹂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,100評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡告材,死狀恐怖坤次,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情斥赋,我是刑警寧澤缰猴,帶...
    沈念sama閱讀 33,738評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站疤剑,受9級特大地震影響滑绒,放射性物質(zhì)發(fā)生泄漏闷堡。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評論 3 307
  • 文/蒙蒙 一疑故、第九天 我趴在偏房一處隱蔽的房頂上張望杠览。 院中可真熱鬧,春花似錦纵势、人聲如沸踱阿。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,289評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽软舌。三九已至,卻和暖如春育瓜,著一層夾襖步出監(jiān)牢的瞬間葫隙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,517評論 1 262
  • 我被黑心中介騙來泰國打工躏仇, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留恋脚,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評論 2 354
  • 正文 我出身青樓焰手,卻偏偏與公主長得像糟描,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子书妻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評論 2 345

推薦閱讀更多精彩內(nèi)容