2017-07-28iOS開發(fā)
導(dǎo)語:在iOS中,多線程方案有四種:pthread沉眶、NSThread打却、NSOperation & NSOperationQueue 和 GCD杉适,但是開發(fā)中GCD使用得最多谎倔,本文主要總結(jié)一下我使用GCD的情況。
一猿推、GCD(Grand Central Dispatch)概述
1片习、基本概念
GCD允許程序將任務(wù)切分為多個(gè)單一任務(wù),提交至Dispatch Queue蹬叭,然后系統(tǒng)調(diào)度線程藕咏,實(shí)現(xiàn)并發(fā)或者串行地執(zhí)行任務(wù)。GCD隱藏了內(nèi)部線程的調(diào)度秽五,開發(fā)者只需要關(guān)注創(chuàng)建或獲取隊(duì)列孽查,然后將Block追加到隊(duì)列中即可。
在iOS中有兩種隊(duì)列坦喘,分別是串行隊(duì)列和并發(fā)隊(duì)列
串行隊(duì)列:同一時(shí)間隊(duì)列中只有一個(gè)任務(wù)在執(zhí)行盲再,每個(gè)任務(wù)只有在前一個(gè)任務(wù)執(zhí)行完成后才能開始執(zhí)行。主隊(duì)列(通過dispatch_get_main_queue()獲取瓣铣,提交至主隊(duì)列的任務(wù)會在主線程中執(zhí)行) 就是串行隊(duì)列答朋,也可以使用dispatch_queue_create創(chuàng)建串行隊(duì)列。
串行隊(duì)列.png
并發(fā)隊(duì)列:這些任務(wù)會按照被添加的順序開始執(zhí)行棠笑。但是任意時(shí)刻有多個(gè)Block(任務(wù))運(yùn)行梦碗,這個(gè)完全是取決于GCD。并發(fā)隊(duì)列可以使用dispatch_queue_create創(chuàng)建蓖救,也可以獲取進(jìn)程中的全局隊(duì)列洪规,全局隊(duì)列有:高、中(默認(rèn))循捺、低三個(gè)優(yōu)先級隊(duì)列斩例。可以調(diào)用dispatch_get_global_queue函數(shù)傳入相應(yīng)優(yōu)先級來訪問隊(duì)列巨柒。
并發(fā)隊(duì)列.png
同步執(zhí)行:阻塞當(dāng)前線程樱拴,直到當(dāng)前block中任務(wù)執(zhí)行完畢才返回柠衍。同步并不創(chuàng)建新線程。不能使用sync將任務(wù)添加到主隊(duì)列晶乔,這樣會造成死鎖珍坊。
異步執(zhí)行:不會阻塞當(dāng)前線程,函數(shù)會立即返回, block會在后臺異步執(zhí)行正罢;異步必定會開啟新線程阵漏。
說明1:有些博客中將并發(fā)隊(duì)列說成并行隊(duì)列,這是不對的翻具。因?yàn)?b>并行是多個(gè)事件在同一時(shí)刻發(fā)生履怯,而并發(fā)是多個(gè)事件在同一時(shí)間間隔發(fā)生;并行完全依賴處理器的核數(shù)裆泳。而并發(fā)才能充分的利用處理器的每一個(gè)核叹洲,以達(dá)到最高的處理性能。
說明2:隊(duì)列不等于線程工禾。 作為開發(fā)者的我們运提,只是將Block添加進(jìn)合適的GCD隊(duì)列,真正的線程的調(diào)度是由系統(tǒng)完成的闻葵;無論同步(sync)還是異步(async)向主隊(duì)列提交Block民泵,最終Block都是在主線程中執(zhí)行;同步(sync)往非主隊(duì)列中提交Block槽畔,會在當(dāng)前線程中執(zhí)行栈妆; 如果是異步(async)往非主隊(duì)列中提交Block,則會在分線程中執(zhí)行厢钧。
2鳞尔、串行隊(duì)列/并發(fā)隊(duì)列 和 同步/異步的組合 ( 重點(diǎn) )
有四種組合方式
1)串行隊(duì)列 + 同步組合(常用)
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);// ? ?dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", NULL);//dispatch_sync(serialQueue, ^{ ? ?NSLog(@"串行隊(duì)列 + 同步:%@",[NSThread currentThread]);});dispatch_sync(serialQueue, ^{ ? ?NSLog(@"串行隊(duì)列 + 同步:%@",[NSThread currentThread]);});dispatch_sync(serialQueue, ^{ ? ?NSLog(@"串行隊(duì)列 + 同步:%@",[NSThread currentThread]);});
串行隊(duì)列 + 同步組合結(jié)果.png
說明1:串行隊(duì)列 (自己創(chuàng)建的串行線程)+ 同步組合下,不會新建線程坏快,依然在當(dāng)前線程上執(zhí)行任務(wù)铅檩。不可以在主線程中使用sync方法,會造成死鎖莽鸿。
說明2:比較常用昧旨,同步鎖的替代方法。
2)串行隊(duì)列 + 異步組合
dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", DISPATCH_QUEUE_SERIAL);// ? ?dispatch_queue_t serialQueue = dispatch_queue_create("com.serial.queue", NULL);//dispatch_async(serialQueue, ^{ ? ?NSLog(@"串行隊(duì)列 + 異步:%@",[NSThread currentThread]);});dispatch_async(serialQueue, ^{ ? ?NSLog(@"串行隊(duì)列 + 異步:%@",[NSThread currentThread]);});dispatch_async(serialQueue, ^{ ? ?NSLog(@"串行隊(duì)列 + 異步:%@",[NSThread currentThread]);});
串行隊(duì)列 + 異步組合結(jié)果.png
說明:串行隊(duì)列(無論是自己創(chuàng)建的祥得,還是獲取主隊(duì)列) + 異步組合下兔沃,會新建線程,但只開啟一條線程级及;
3)并發(fā)隊(duì)列 + 同步組合
dispatch_queue_tconcurrentQueue =dispatch_queue_create("com.concurrent.queue",DISPATCH_QUEUE_CONCURRENT);dispatch_sync(concurrentQueue,^{ ? ?NSLog(@"并發(fā)隊(duì)列 + 同步1:%@",[NSThread currentThread]);});dispatch_sync(concurrentQueue,^{ ? ?NSLog(@"并發(fā)隊(duì)列 + 同步2:%@",[NSThread currentThread]);});dispatch_sync(concurrentQueue,^{ ? ?NSLog(@"并發(fā)隊(duì)列 + 同步3:%@",[NSThread currentThread]);});
并發(fā)隊(duì)列 + 同步組合結(jié)果.png
說明: 并發(fā)隊(duì)列(無論是自己創(chuàng)建的乒疏,還是獲取全局隊(duì)列) + 同步組合下,并沒有新建線程饮焦,任務(wù)依然在當(dāng)前線程上執(zhí)行怕吴。
4)并發(fā)隊(duì)列 + 異步組合(常用)
dispatch_queue_tconcurrentQueue =dispatch_queue_create("com.concurrent.queue",DISPATCH_QUEUE_CONCURRENT);dispatch_async(concurrentQueue,^{ ? ?NSLog(@"并發(fā)隊(duì)列 + 異步:%@",[NSThread currentThread]);});dispatch_async(concurrentQueue,^{ ? ?NSLog(@"并發(fā)隊(duì)列 + 異步:%@",[NSThread currentThread]);});dispatch_async(concurrentQueue,^{ ? ?NSLog(@"并發(fā)隊(duì)列 + 異步:%@",[NSThread currentThread]);});
并發(fā)隊(duì)列 + 異步組合結(jié)果.png
說明:并發(fā)隊(duì)列(無論是自己創(chuàng)建的窍侧,還是獲取全局隊(duì)列) + 異步組合下,會新建線程转绷,iOS 系統(tǒng)中可以開多條線程伟件。
同步異步
串行隊(duì)列1、不會新建線程议经,依然在當(dāng)前線程上執(zhí)行任務(wù)斧账;2、類似同步鎖煞肾,是同步鎖的替代方案1咧织、會新建線程,但只開啟一條線程籍救;2习绢、每次使用 dispatch_queue_create創(chuàng)建串行隊(duì)列,就會創(chuàng)建一條新線程钧忽;多次創(chuàng)建毯炮,會創(chuàng)建多條線程逼肯,多條線程間并發(fā)執(zhí)行耸黑。
并發(fā)隊(duì)列不會新建線程,依然在當(dāng)前線程上執(zhí)行任務(wù)1篮幢、會新建線程大刊,可以開多條線程;2三椿、iOS7-SDK 時(shí)代一般是5缺菌、6條, iOS8-SDK 以后可以50搜锰、60條
總結(jié)1:不可以在主線程中使用sync方法伴郁,否則會造成死鎖。
總結(jié)2:串行隊(duì)列 + 同步組合 可以替代同步鎖蛋叼;
總結(jié)3:為了提高效率焊傅,如多線程下載圖片等,并發(fā)隊(duì)列 + 異步比較常用狈涮。
二狐胎、GCD使用1:異步處理
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);dispatch_async(globalQueue, ^{ ? ?// 一個(gè)異步的任務(wù),如網(wǎng)絡(luò)請求歌馍,耗時(shí)的文件操作等等 ? ?... ? ?dispatch_async(dispatch_get_main_queue(), ^{ ? ? ? ?// UI刷新 或其他主線程操作 ? ? ? ?... ? ?});});
說明:該用法最常見握巢,如開啟一個(gè)異步的網(wǎng)絡(luò)請求,待數(shù)據(jù)返回后在主線程刷新UI等松却。
三暴浦、GCD使用2:單例
dispatch_once實(shí)現(xiàn)單例溅话,以實(shí)現(xiàn)QSAccountManager單例為例。源碼如下:
1歌焦、實(shí)現(xiàn)
//QSAccountManager.m@implementationQSAccountManagerstaticQSAccountManager *_shareManager =nil;+ (instancetype)shareManager{staticdispatch_once_tonce;dispatch_once(&once, ^{ ? ? ? ?_shareManager = [[selfalloc] init]; ? ?});return_shareManager;}+ (instancetype)allocWithZone:(struct_NSZone *)zone{staticdispatch_once_tonceToken;dispatch_once(&onceToken, ^{ ? ? ? ?_shareManager = [superallocWithZone:zone]; ? ?});return_shareManager;}- (nonnullid)copyWithZone:(nullableNSZone*)zone{return_shareManager;}@end
說明1:dispatch_once函數(shù)中公荧,參數(shù)1是代碼塊是否被調(diào)用的謂詞,參數(shù)2是被調(diào)用的代碼塊同规。該函數(shù)中的代碼塊只會被執(zhí)行一次循狰,而且還是線程安全的。
說明2:要保證單例類只有一個(gè)唯一的實(shí)例券勺,還需要實(shí)現(xiàn)allocWithZone和copyWithZone方法绪钥,這保證使用init和copy方法返回也是唯一實(shí)例。
2关炼、使用
QSAccountManager *account1 = [QSAccountManagershareManager];QSAccountManager *account2 = [QSAccountManager new];QSAccountManager *account3 = [[QSAccountManager alloc]init];QSAccountManager *account4 = [account3 copy];NSLog(@"account1 = %@",account1);NSLog(@"account2 = %@",account2);NSLog(@"account3 = %@",account3);NSLog(@"account4 = %@",account4);
單例輸出結(jié)果.png
四程腹、GCD使用3:代替同步鎖
atomic的內(nèi)存管理語義是原子性的,僅保證了屬性的setter和getter方法是原子性的儒拂,但是執(zhí)行效率低寸潦,可以使用GCD實(shí)現(xiàn)。
@synchronized(self)同步塊機(jī)制社痛,會根據(jù)給定的對象见转,自動創(chuàng)建一個(gè)鎖,并等待塊中的代碼執(zhí)行完畢蒜哀,才釋放鎖斩箫。執(zhí)行效率低。
替代方案:將數(shù)據(jù)的讀取和寫放入串行同步隊(duì)列撵儿,保證數(shù)據(jù)同步乘客,線程安全。
替代方案:結(jié)合GCD的柵欄塊(barrier)和 ?并發(fā)隊(duì)列 實(shí)現(xiàn)數(shù)據(jù)同步淀歇,線程安全易核。(比串行同步隊(duì)列方式更高效)
1、代替atomic實(shí)現(xiàn)線程安全的setter和getter方法
//串行隊(duì)列_syncQueue = dispatch_queue_create("com.jzp.syncQueue",NULL);//假設(shè)屬性是someString- (NSString*)someString { ? ?__blockNSString*localSomeString;dispatch_sync(_syncQueue, ^{ ? ? ? ?localSomeString = _someString; ? ?});returnlocalSomeString;}- (void)setSomeString:(NSString*)someString {dispatch_sync(_syncQueue, ^{ ? ? ? ?_someString = someString; ? ?});}
2浪默、實(shí)現(xiàn)線程安全的NSMutableArray
主要依靠柵欄塊單獨(dú)執(zhí)行的特性牡直,在并發(fā)隊(duì)列中如果發(fā)現(xiàn)接下來要處理的塊是個(gè)欄柵塊,那么就一直等到當(dāng)前所有并發(fā)塊都執(zhí)行完畢浴鸿,才會單獨(dú)執(zhí)行這個(gè)欄柵塊井氢。待欄柵塊執(zhí)行過后,再按正常方式繼續(xù)向下處理岳链。
這部分實(shí)現(xiàn)詳見?iOS實(shí)錄12:NSMutableArray使用中忽視的問題中“一花竞、線程安全的NSMutableArray”。
說明:dispatch_barrier_sync和dispatch_barrier_async只在自己創(chuàng)建的并發(fā)隊(duì)列上有效,在全局(Global)并發(fā)隊(duì)列约急、串行隊(duì)列上零远,效果跟dispatch_(a)sync效果一樣。
五厌蔽、GCD使用4:dispatch_group實(shí)現(xiàn)線程同步
1牵辣、簡單模式
dispatch_queue_tqueue= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);dispatch_group_tgroup= dispatch_group_create();dispatch_group_async(group,queue, ^{// 任務(wù)1});dispatch_group_async(group,queue, ^{// 任務(wù)2});// 等待group中多個(gè)異步任務(wù)執(zhí)行完畢,會發(fā)出同步信號// 方式1(會阻塞當(dāng)前線程奴饮,group上任務(wù)都完成或超時(shí)等待就執(zhí)行)dispatch_group_wait(group, DISPATCH_TIME_FOREVER);// ...// 方式2(不會阻塞當(dāng)前線程,group上任務(wù)都完成纬向,執(zhí)行block中代碼)dispatch_group_notify(group, dispatch_get_main_queue(), ^{// 任務(wù)完成后,在主隊(duì)列中做一些操作});
說明:將block(任務(wù))放入隊(duì)列中執(zhí)行戴卜,并和調(diào)度組 group相關(guān)聯(lián)逾条;如果提交到dispatch queue中的block全都執(zhí)行完畢,會執(zhí)行dispatch_group_notify中的block代碼; 或在group上任務(wù)完成前投剥,dispatch_group_wait會阻塞當(dāng)前線程(所以不能放在主線程調(diào)用)一直等待师脂;當(dāng)group上任務(wù)完成,或者等待時(shí)間超過設(shè)置的超時(shí)時(shí)間會結(jié)束等待江锨。
2吃警、多異步任務(wù)的同步
成對使用dispatch_group_enter和dispatch_group_leave,可以將異步任務(wù)加入group中啄育;
當(dāng)這些異步任務(wù)處理完成后酌心,dispatch_group_notify和dispatch_group_wait會收到同步信號;
異步任務(wù)如請求灸撰,通過該機(jī)制實(shí)現(xiàn)批量請求的處理谒府。
dispatch_group_t batch_request_group = dispatch_group_create();dispatch_group_enter(batch_request_group);[self.request1 startWithCompleteBlock:^(BOOLisSuccess,id_Nullable responseObj,NSString* _Nonnull errorDesc) {//TODO 數(shù)據(jù)解析....dispatch_group_leave(batch_request_group);}];dispatch_group_enter(batch_request_group);[self.request2 startWithCompleteBlock:^(BOOLisSuccess,id_Nullable responseObj,NSString* _Nonnull errorDesc) {//TODO 數(shù)據(jù)解析....dispatch_group_leave(batch_request_group);}];dispatch_group_enter(batch_request_group);[self.request3 startWithCompleteBlock:^(BOOLisSuccess,id_Nullable responseObj,NSString* _Nonnull errorDesc) {//TODO 數(shù)據(jù)解析....dispatch_group_leave(batch_request_group);}];dispatch_group_notify(batch_request_group, dispatch_get_main_queue(), ^{//三個(gè)請求都結(jié)束了,繼續(xù)處理});
六浮毯、GCD使用其他
1、dispatch_apply
按 指定的次數(shù) 將指定的Block追加到 指定的Dispatch Queue中, 并等到全部處理執(zhí)行結(jié)束泰鸡。有并行的運(yùn)行機(jī)制债蓝,效率一般快于for循環(huán)的類串行機(jī)制。
/** @param10指定重復(fù)次數(shù)盛龄,這里指定10次 @param gQueue 追加對象的Dispatch Queue @paramindex帶有參數(shù)的Block,index的作用是為了按執(zhí)行的順序區(qū)分各個(gè)Block */dispatch_apply(10, gQueue, ^(size_tindex) { ? ?NSLog(@"%zu",index);});
2饰迹、dispatch_after
延遲執(zhí)行
// NSEC_PER_SEC,每秒有多少納秒余舶。// USEC_PER_SEC啊鸭,每秒有多少毫秒。// NSEC_PER_USEC匿值,每毫秒有多少納秒赠制。// DISPATCH_TIME_NOW 從現(xiàn)在開始// DISPATCH_TIME_FOREVE 永久// time為5sdispatch_time_ttime = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(5.0* NSEC_PER_SEC));dispatch_queue_tqueue= dispatch_get_main_queue();dispatch_after(time,queue, ^{// 在queue里面延遲執(zhí)行的一段代碼// ...});
3、小心死鎖
死鎖情況1:在主線程中使用sync方法
dispatch_sync(dispatch_get_main_queue(), ^{// 任務(wù)...});
死鎖情況2:在串行隊(duì)列添加同步任務(wù)挟憔;
// 在串行隊(duì)列添加同步任務(wù)dispatch_sync(serialQueue, ^{// 任務(wù)dispatch_sync(serialQueue, ^{// 任務(wù)});};