前言
該篇介紹并發(fā)及刻,串行,并行竞阐,異步和同步詳解和使用缴饭,以及其他方法,在理解多線程之前骆莹,必須先知道這幾個(gè)概念颗搂。
并發(fā): 單核設(shè)備,執(zhí)行多個(gè)線程操作幕垦,并非同步執(zhí)行丢氢,既當(dāng)有一個(gè)線程在執(zhí)行時(shí)傅联,其他線程處于掛起狀態(tài),會(huì)相互搶占資源疚察,或者一個(gè)線程中蒸走,不同隊(duì)列,在一個(gè)隊(duì)列中執(zhí)行時(shí)其他隊(duì)列阻塞稍浆。
并行:多核設(shè)備载碌,執(zhí)行多個(gè)線程操作嵌牺,并且同時(shí)執(zhí)行胸哥,不會(huì)相互搶占資源涕俗。
串行:任務(wù)在同一個(gè)線程執(zhí)行采记,且執(zhí)行完一個(gè)才能執(zhí)行下一個(gè)
同步:不開(kāi)辟新的線程比默,在同一個(gè)線程按順序執(zhí)行
異步:開(kāi)辟新的線程艇潭,在新的線程中執(zhí)行
1 侣诺、異步和同步執(zhí)行的方法介紹
dispatch_async(dispatch_queue_t queue, dispatch_block_t block)
// 在調(diào)度隊(duì)列上提交異步執(zhí)行塊并立即返回拳锚。
dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work)
// 以在調(diào)度隊(duì)列上進(jìn)行異步執(zhí)行并立即返回到自己定義的方法中益楼。
dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block)
// 將塊排隊(duì)以在指定時(shí)間執(zhí)行猾漫。
dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *context, dispatch_function_t work)
// 將應(yīng)用程序定義的函數(shù)排入隊(duì)列,以便在指定時(shí)間執(zhí)行感凤。
dispatch_function_t
// 提交給派遣隊(duì)列的函數(shù)原型悯周。
dispatch_block_t
// 提交給調(diào)度隊(duì)列的塊的原型,它不帶參數(shù)且沒(méi)有返回值陪竿。
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
// 提交塊對(duì)象以便執(zhí)行禽翼,并在該塊完成同步執(zhí)行后返回。
dispatch_sync_f(dispatch_queue_t queue, void *context, dispatch_function_t work)
// 提交應(yīng)用程序定義的函數(shù)以在調(diào)度隊(duì)列上同步執(zhí)行同步族跛。
dispatch_queue_get_label
// 獲取隊(duì)列的標(biāo)簽
2 闰挡、異步和同步執(zhí)行的實(shí)現(xiàn)
在應(yīng)用過(guò)程中,同步和異步礁哄,并行和串行是組合使用的长酗,并且需要結(jié)合是在主線程還是全局線程。我們對(duì)各種情況進(jìn)行分析桐绒。
- 串行(DISPATCH_QUEUE_SERIAL)
- 并行(DISPATCH_QUEUE_CONCURRENT)
- dispatch_queue_global(全局隊(duì)列夺脾,并發(fā)執(zhí)行)
- dispatch_queue_main(主隊(duì)列,串行執(zhí)行)
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
// 創(chuàng)建串行隊(duì)列
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
// 創(chuàng)建并發(fā)隊(duì)列
同步+串行
即不開(kāi)辟新的線程茉继,在同一個(gè)線程中劳翰,按順序執(zhí)行。當(dāng)執(zhí)行完一個(gè)之后才能執(zhí)行下一個(gè)馒疹。
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"線程:2>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"線程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:4>>>%@",[NSThread currentThread]);
打印之后的結(jié)果為:
2019-04-16 16:38:37.974577+0800[24573:3900925] 線程:1>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974789+0800[24573:3900925] 線程:2>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974810+0800[24573:3900925] 線程:3>>><NSThread: 0x100703540>{number = 1, name = main}
2019-04-16 16:38:37.974827+0800[24573:3900925] 線程:4>>><NSThread: 0x100703540>{number = 1, name = main}
可以看到佳簸,線程是順序執(zhí)行的,且都在主線程中執(zhí)行,沒(méi)有開(kāi)辟線程,驗(yàn)證我們的結(jié)論生均。
同步+并行
即不開(kāi)辟新的線程听想,在同一個(gè)線程中,只有一個(gè)線程马胧,也得按順序一個(gè)接一個(gè)汉买,因?yàn)楸旧砭筒荒芏鄤?chuàng)建線程,也就不存在并發(fā)佩脊。
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"線程:2>>>%@",[NSThread currentThread]);
});
dispatch_sync(queue, ^{
NSLog(@"線程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:05:26.950802+0800 block_test[25020:3949022] 線程:1>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951073+0800 block_test[25020:3949022] 線程:2>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951094+0800 block_test[25020:3949022] 線程:3>>><NSThread: 0x100503b00>{number = 1, name = main}
2019-04-16 17:05:26.951146+0800 block_test[25020:3949022] 線程:4>>><NSThread: 0x100503b00>{number = 1, name = main}
可以看到蛙粘,都是在同一個(gè)線程順序執(zhí)行
異步+串行
即會(huì)開(kāi)辟且只開(kāi)辟一個(gè)新的線程,并且在新的線程中按順序一個(gè)一個(gè)執(zhí)行威彰,并且與舊線程并行執(zhí)行
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"線程:2>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"線程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:18:32.885431+0800[25146:3971063] 線程:1>>><NSThread: 0x102812890>{number = 2, name = (null)}
2019-04-16 17:18:32.885464+0800[25146:3971032] 線程:4>>><NSThread: 0x100506350>{number = 1, name = main}
2019-04-16 17:18:32.885637+0800[25146:3971063] 線程:2>>><NSThread: 0x102812890>{number = 2, name = (null)}
2019-04-16 17:18:32.885658+0800[25146:3971063] 線程:3>>><NSThread: 0x102812890>{number = 2, name = (null)}
可以看到出牧,結(jié)果開(kāi)辟了且只有一個(gè)線程,在這個(gè)線程中順序執(zhí)行歇盼,并且與主線程并行執(zhí)行舔痕,在主線程之后。
異步+并行
即會(huì)開(kāi)辟且只開(kāi)辟一個(gè)新的線程豹缀,并且在新的線程中按順序一個(gè)一個(gè)執(zhí)行伯复,并且與舊線程并行執(zhí)行
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"線程:2>>>%@",[NSThread currentThread]);
});
dispatch_async(queue, ^{
NSLog(@"線程:3>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:4>>>%@",[NSThread currentThread]);
2019-04-16 17:10:51.809839+0800[25074:3958365] 線程:4>>><NSThread: 0x100703550>{number = 1, name = main}
2019-04-16 17:10:51.809845+0800[25074:3958399] 線程:1>>><NSThread: 0x100629510>{number = 2, name = (null)}
2019-04-16 17:10:51.810150+0800[25074:3958399] 線程:2>>><NSThread: 0x100629510>{number = 2, name = (null)}
2019-04-16 17:10:51.810171+0800[25074:3958399] 線程:3>>><NSThread: 0x100629510>{number = 2, name = (null)}
可以看到,結(jié)果開(kāi)辟了多個(gè)線程邢笙,在這個(gè)線程中并發(fā)執(zhí)行啸如,并且與舊線程并行執(zhí)行。
3氮惯、死鎖
GCD同步異步组底,并行和串行中,可以實(shí)現(xiàn)嵌套的使用筐骇,但是在嵌套使用時(shí),不理解的人在隨便亂用GCD嵌套使用會(huì)造成程序執(zhí)行中斷的嚴(yán)重問(wèn)題江滨,造成這個(gè)問(wèn)題的原因就是死鎖铛纬。之前提到過(guò),隊(duì)列是FIFO先進(jìn)先出的形式唬滑,一個(gè)線程可能有多個(gè)隊(duì)列告唆,在同一個(gè)線程的同一個(gè)隊(duì)列中,先進(jìn)入隊(duì)列的任務(wù)要先出去晶密,后面的隊(duì)列才能出去擒悬,如圖所示:
接下來(lái)我們來(lái)用代碼看看幾種會(huì)出現(xiàn)問(wèn)題的可能性和分析具體原因:
案例一
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"開(kāi)始");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:2>>>%@",[NSThread currentThread]);
}
return 0;
}
當(dāng)在主線程執(zhí)行完,發(fā)現(xiàn)執(zhí)行崩潰稻艰,這是為什么呢懂牧?我們剛才說(shuō)過(guò)隊(duì)列是順序執(zhí)行,意味著一個(gè)隊(duì)列還沒(méi)結(jié)束,后面的隊(duì)列就無(wú)法結(jié)束僧凤。當(dāng)在主線程執(zhí)行同步隊(duì)列時(shí)畜侦,就表示主線程的主隊(duì)列又創(chuàng)建了一個(gè)任務(wù)。線程1和線程2都在主線程中的同一個(gè)隊(duì)列里躯保,并且這個(gè)任務(wù)是順序執(zhí)行旋膳,表示線程1創(chuàng)建的同時(shí)需要立馬就執(zhí)行,但是線程2還沒(méi)執(zhí)行完畢途事,因此線程1沒(méi)辦法先進(jìn)線程原則執(zhí)行所以線程1需要等待線程2執(zhí)行验懊。但是線程2要執(zhí)行完也需要經(jīng)過(guò)線程1,因此線程2也要等待線程1執(zhí)行完尸变。這樣互相等著造成線程無(wú)法釋放义图,阻塞主線程導(dǎo)致死鎖。
案例二
NSLog(@"開(kāi)始");
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"線程:1>>>%@",[NSThread currentThread]);
});
NSLog(@"線程:2>>>%@",[NSThread currentThread]);
2019-04-16 17:44:36.446608+0800[25372:4016930] 開(kāi)始
2019-04-16 17:44:36.447153+0800[25372:4016930] 線程:2>>><NSThread: 0x10050d420>{number = 1, name = main}
2019-04-16 17:44:36.447153+0800[25372:4016930] 線程:1>>><NSThread: 0x10050d420>{number = 1, name = main}
發(fā)現(xiàn)雖然都在主線程振惰,因?yàn)槭钱惒降母韪龋虼司€程2不用等待線程1執(zhí)行,因此不會(huì)造成死鎖骑晶。
案例三
接下來(lái)我們?cè)谥骶€程中痛垛,加入一個(gè)同步的全局隊(duì)列,我們來(lái)看看結(jié)果:
NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
NSLog(@">>>>2>>>%@",[NSThread currentThread]);
});
NSLog(@">>>>3>>>%@",[NSThread currentThread]);
2019-04-18 10:00:31.070717+0800 block_test[43552:5006132] >>>>1>>><NSThread: 0x100705db0>{number = 1, name = main}
2019-04-18 10:00:31.071007+0800 block_test[43552:5006132] >>>>2>>><NSThread: 0x100705db0>{number = 1, name = main}
2019-04-18 10:00:31.071027+0800 block_test[43552:5006132] >>>>3>>><NSThread: 0x100705db0>{number = 1, name = main}
發(fā)現(xiàn)結(jié)果不會(huì)報(bào)錯(cuò)桶蛔,正常按順序打印匙头,因?yàn)橹骶€程的主隊(duì)列和主線程的全局隊(duì)列不在同一個(gè)隊(duì)列中,因此不會(huì)出現(xiàn)死鎖的現(xiàn)象仔雷。當(dāng)執(zhí)行dispatch_sync的時(shí)候蹂析,系統(tǒng)阻塞主隊(duì)列,并且執(zhí)行全局隊(duì)列碟婆,當(dāng)全局隊(duì)列執(zhí)行完电抚,再回到主隊(duì)列中繼續(xù)執(zhí)行。
案例四
為了驗(yàn)證串行和并行是否是死鎖的一個(gè)條件竖共,我們?cè)趤?lái)看兩個(gè)例子蝙叛,首先是在同一個(gè)線程中使用串行隊(duì)列去執(zhí)行。
NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
NSLog(@">>>>2>>>%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@">>>>3>>>%@",[NSThread currentThread]);
});
});
NSLog(@">>>>4>>>%@",[NSThread currentThread]);
結(jié)果直接崩潰公给,說(shuō)明了串行隊(duì)列確實(shí)是會(huì)造成死鎖借帘。
NSLog(@">>>>1>>>%@",[NSThread currentThread]);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
NSLog(@">>>>2>>>%@",[NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@">>>>3>>>%@",[NSThread currentThread]);
});
});
NSLog(@">>>>4>>>%@",[NSThread currentThread]);
2019-04-18 10:07:51.532307+0800 block_test[43623:5018697] >>>>1>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532575+0800 block_test[43623:5018697] >>>>2>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532620+0800 block_test[43623:5018697] >>>>3>>><NSThread: 0x100605db0>{number = 1, name = main}
2019-04-18 10:07:51.532665+0800 block_test[43623:5018697] >>>>4>>><NSThread: 0x100605db0>{number = 1, name = main}
發(fā)現(xiàn)結(jié)果打印出來(lái)的是正常的,正好驗(yàn)證了我們的猜想淌铐。
因此滿足死鎖需要具備兩個(gè)條件:
- 在同一個(gè)線程中肺然,出現(xiàn)多個(gè)同步任務(wù),并且任務(wù)之間存在嵌套關(guān)系
- 存在嵌套關(guān)系的任務(wù)腿准,是在同一個(gè)隊(duì)列中
4际起、dispatch_once
說(shuō)到單例相信大家都不會(huì)陌生,在項(xiàng)目中,經(jīng)常性的使用到單例加叁,在應(yīng)用程序的生命周期內(nèi)僅執(zhí)行一次塊對(duì)象倦沧。網(wǎng)上有非常多的對(duì)于單例的使用這里就不提。
在使用單例中它匕,可以通過(guò)dispatch_once或者dispatch_once_f的方式展融,兩者的區(qū)別只是在于,前者是以block的方式去設(shè)置單例的方法豫柬;后者則是可以添加C函數(shù)的方式設(shè)置告希,dispatch _function _t類型。
+(Person *)sharedInstance
{
static Person *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[Person alloc] init];
});
// dispatch_once_f(&onceToken, nil, test);
return sharedManager;
}
void test(void *context) {
int *c = context;
NSLog(@"%d", *c);
}
dispatch_once也會(huì)造成死鎖
+(TestA *)sharedInstance
{
static TestA *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[TestA alloc] init];
TestB *test = [self sharedInstance2];
});
return sharedManager;
}
+(TestB *)sharedInstance2
{
static TestB *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[TestB alloc] init];
TestA *test = [self sharedInstance];
});
return sharedManager;
}
死鎖的原因是因?yàn)閐ispatch_once底層是用信號(hào)量去控制線程的釋放烧给,首先會(huì)先判斷是否是第一個(gè)運(yùn)行燕偶,如果是的情況,執(zhí)行block內(nèi)部的方法础嫡,并做一個(gè)死循環(huán)去監(jiān)控是否有其他線程進(jìn)入該block函數(shù)中指么,如果有就建立信號(hào)鏈表,用兩個(gè)指針指向信號(hào)頭尾榴鼎,在死循環(huán)中伯诬,逐一的處理信號(hào)。但是發(fā)現(xiàn)兩個(gè)方法互相調(diào)用過(guò)程巫财,信號(hào)量是互相等待盗似,造成死鎖的情況。
順便說(shuō)下平项,當(dāng)非第一個(gè)調(diào)用dispatch_once赫舒,則進(jìn)入另一個(gè)死循環(huán),如果block的請(qǐng)求還沒(méi)有結(jié)束闽瓢,則將后續(xù)的操作添加到鏈表中進(jìn)行處理接癌,處理完跳出死循環(huán)。
5扣讼、dispatch_apply
dispatch_apply函數(shù)是dispatch_sync函數(shù)和Dispatch Group的關(guān)聯(lián)API,將單個(gè)塊提交到調(diào)度隊(duì)列缺猛,并使塊執(zhí)行指定的次數(shù)。該函數(shù)按指定的次數(shù)將指定的Block追加到指定的Dispatch Queue中,并等到全部的處理執(zhí)行結(jié)束
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t));
dispatch_apply是并行的調(diào)度方法届谈,用戶使用該方法,可以進(jìn)行無(wú)序多次循環(huán)的操作弯汰,并且會(huì)阻斷主線程的運(yùn)行等待所有循環(huán)都操作完之后才會(huì)繼續(xù)接下去的主線程操作:
NSLog(@"開(kāi)始");
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_apply(10, queue, ^(size_t size) {
NSLog(@">>>> %zu >>>%@",size,[NSThread currentThread]);
});
NSLog(@"結(jié)束");
2019-04-24 09:32:23.261395+0800 block_test[13160:426551] 開(kāi)始
2019-04-24 09:32:23.262115+0800 block_test[13160:426551] >>>> 0 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262180+0800 block_test[13160:426551] >>>> 4 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262215+0800 block_test[13160:426551] >>>> 5 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262226+0800 block_test[13160:426587] >>>> 1 >>><NSThread: 0x102807010>{number = 2, name = (null)}
2019-04-24 09:32:23.262232+0800 block_test[13160:426551] >>>> 6 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262232+0800 block_test[13160:426595] >>>> 3 >>><NSThread: 0x10070a6b0>{number = 3, name = (null)}
2019-04-24 09:32:23.262251+0800 block_test[13160:426551] >>>> 7 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:32:23.262258+0800 block_test[13160:426587] >>>> 8 >>><NSThread: 0x102807010>{number = 2, name = (null)}
2019-04-24 09:32:23.262265+0800 block_test[13160:426595] >>>> 9 >>><NSThread: 0x10070a6b0>{number = 3, name = (null)}
2019-04-24 09:32:23.262275+0800 block_test[13160:426590] >>>> 2 >>><NSThread: 0x10070aa90>{number = 4, name = (null)}
2019-04-24 09:32:23.282560+0800 block_test[13160:426551] 結(jié)束
從打印的結(jié)果上艰山,可以看出dispatch_apply是在多線程上操作,并且會(huì)再新線程全部結(jié)束后咏闪,再做主線程刷新操作曙搬。
在官方文檔的,提供了DISPATCH_APPLY_AUTO
,并建議使用此參數(shù)作為queue纵装,因?yàn)檫@會(huì)導(dǎo)致任務(wù)在其服務(wù)質(zhì)量類(QOS)最適合當(dāng)前執(zhí)行上下文的隊(duì)列上運(yùn)行征讲。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@">>>> %zu >>>%@",index,[NSThread currentThread]);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"結(jié)束");
});
});
當(dāng)然如果隊(duì)列是串行的方法也是等到串行結(jié)束后再執(zhí)行
NSLog(@"開(kāi)始");
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@">>>> %zu >>>%@",index,[NSThread currentThread]);
});
NSLog(@"結(jié)束");
2019-04-24 09:54:06.183587+0800 block_test[13419:450661] 開(kāi)始
2019-04-24 09:54:06.184322+0800 block_test[13419:450661] >>>> 0 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184432+0800 block_test[13419:450661] >>>> 1 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184464+0800 block_test[13419:450661] >>>> 2 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184496+0800 block_test[13419:450661] >>>> 3 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184511+0800 block_test[13419:450661] >>>> 4 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184550+0800 block_test[13419:450661] >>>> 5 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184593+0800 block_test[13419:450661] >>>> 6 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184607+0800 block_test[13419:450661] >>>> 7 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184622+0800 block_test[13419:450661] >>>> 8 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.184636+0800 block_test[13419:450661] >>>> 9 >>><NSThread: 0x1007035e0>{number = 1, name = main}
2019-04-24 09:54:06.188585+0800 block_test[13419:450661] 結(jié)束
6、dispatch_queue_get_label 和 dispatch_set_target_queue 和 dispatch_queue_set_specific
dispatch_queue_get_label 是用來(lái)獲取隊(duì)列標(biāo)簽橡娄,并且系統(tǒng)也提供一個(gè)關(guān)鍵詞DISPATCH_CURRENT_QUEUE_LABEL
用來(lái)獲取當(dāng)前隊(duì)列的標(biāo)簽诗箍,使用如下:
dispatch_queue_t queue = dispatch_queue_create("this is a queue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
NSLog(@">> 1 >> %s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
});
NSLog(@">> 2 >> %s", dispatch_queue_get_label(queue));
NSLog(@">> 3 >> %s", dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL));
dispatch_set_target_queue 這個(gè)參數(shù),將給一個(gè)隊(duì)列設(shè)置目標(biāo)隊(duì)列挽唉,object是要修改的對(duì)象(不能指定主隊(duì)列和全局隊(duì)列)滤祖,queue是目標(biāo)對(duì)象隊(duì)列。如果希望系統(tǒng)提供適合當(dāng)前對(duì)象的隊(duì)列瓶籽,請(qǐng)指定DISPATCH_TARGET_QUEUE_DEFAULT
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
那么設(shè)置成目標(biāo)對(duì)象的隊(duì)列即object參數(shù)隊(duì)列會(huì)變成什么樣呢匠童?
對(duì)于一個(gè)已經(jīng)存在的隊(duì)列去改變隊(duì)列的優(yōu)先級(jí)和qos的設(shè)定,使其與目標(biāo)隊(duì)列一致塑顺。
dispatch_queue_t targetQueue = dispatch_queue_create("targetQueue", DISPATCH_QUEUE_SERIAL);//目標(biāo)隊(duì)列
dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_SERIAL);//串行隊(duì)列
dispatch_queue_t queue2 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
//設(shè)置參考
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(queue2, targetQueue);
dispatch_async(queue2, ^{
NSLog(@"job3 in %@",[NSThread currentThread]);
});
dispatch_async(queue2, ^{
NSLog(@"job2 in %@",[NSThread currentThread]);
});
dispatch_async(queue1, ^{
NSLog(@"job1 in %@",[NSThread currentThread]);
});
打印出來(lái)的結(jié)果是串行同步輸出:
2019-04-24 10:54:32.406594+0800 test_1[1514:2485209] job3 in <NSThread: 0x1c0660600>{number = 3, name = (null)}
2019-04-24 10:54:32.406735+0800 test_1[1514:2485209] job2 in <NSThread: 0x1c0660600>{number = 3, name = (null)}
2019-04-24 10:54:32.407308+0800 test_1[1514:2485209] job1 in <NSThread: 0x1c0660600>{number = 3, name = (null)}
設(shè)置目標(biāo)隊(duì)列時(shí)汤求,請(qǐng)勿在隊(duì)列層次結(jié)構(gòu)中創(chuàng)建周期。換句話說(shuō)严拒,不要將隊(duì)列A的目標(biāo)設(shè)置為隊(duì)列B扬绪,并將隊(duì)列B的目標(biāo)設(shè)置為隊(duì)列A.例如:
// 以下是錯(cuò)誤的使用方式
dispatch_set_target_queue(queue1, targetQueue);
dispatch_set_target_queue(targetQueue, queue1);
dispatch_queue_set_specific 給當(dāng)前的隊(duì)列設(shè)定一個(gè)標(biāo)識(shí),使用方法:
static void *queueKey1 = "queueKey1";
dispatch_queue_t queue1 = dispatch_queue_create(queueKey1, DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(queue1, queueKey1, &queueKey1, NULL);
NSLog(@"當(dāng)前隊(duì)列是: %@ 糙俗。",dispatch_get_current_queue());
dispatch_sync(queue1, ^{
[NSThread sleepForTimeInterval:1];
if (dispatch_get_specific(queueKey1)) {
//當(dāng)前隊(duì)列是queue1隊(duì)列勒奇,所以能取到queueKey1對(duì)應(yīng)的值,故而執(zhí)行
NSLog(@"當(dāng)前隊(duì)列是: %@ 巧骚。",dispatch_get_current_queue());
}else{
NSLog(@"當(dāng)前隊(duì)列是: %@ 赊颠。",dispatch_get_current_queue());
}
});
6、DispatchWorkItem(dispatch_block_t)派遣工作調(diào)度塊
GCD允許對(duì)調(diào)度回調(diào)block工作進(jìn)行劈彪,以附加或執(zhí)行依賴項(xiàng)的方式封裝觸發(fā)(dispatch_block_t)竣蹦。調(diào)度工作項(xiàng)封裝要在調(diào)度隊(duì)列queue或調(diào)度組group內(nèi)執(zhí)行的工作。您還可以將工作項(xiàng)用作調(diào)度源事件沧奴,注冊(cè)或取消處理程序痘括。
dispatch_block_t dispatch_block_create(dispatch_block_flags_t flags, dispatch_block_t block);
//使用現(xiàn)有塊和給定標(biāo)志在堆上創(chuàng)建新的調(diào)度塊。
dispatch_block_t dispatch_block_create_with_qos_class(dispatch_block_flags_t flags, dispatch_qos_class_t qos_class, int relative_priority, dispatch_block_t block);
//從現(xiàn)有塊和給定標(biāo)志創(chuàng)建新的調(diào)度塊滔吠,并為其分配指定的服務(wù)質(zhì)量類和相對(duì)優(yōu)先級(jí)纲菌。
dispatch_block_t
//提交給調(diào)度隊(duì)列的塊的原型,它不帶參數(shù)且沒(méi)有返回值疮绷。
dispatch_block_flags_t
DISPATCH_ENUM(dispatch_block_flags, unsigned long,
DISPATCH_BLOCK_BARRIER = 0x1,
DISPATCH_BLOCK_DETACHED = 0x2,
DISPATCH_BLOCK_ASSIGN_CURRENT = 0x4,
DISPATCH_BLOCK_NO_QOS_CLASS = 0x8,
DISPATCH_BLOCK_INHERIT_QOS_CLASS = 0x10,
DISPATCH_BLOCK_ENFORCE_QOS_CLASS = 0x20,
);
// 創(chuàng)建派遣標(biāo)志翰舌,來(lái)區(qū)分派遣調(diào)度block的設(shè)置處理事務(wù)
dispatch_block_perform
//從指定的塊和標(biāo)志創(chuàng)建,同步執(zhí)行和釋放調(diào)度塊冬骚。
添加完成處理程序
dispatch_block_notify
//在完成指定的調(diào)度塊的執(zhí)行時(shí)椅贱,計(jì)劃要提交到隊(duì)列的通知塊懂算。
延遲執(zhí)行工作項(xiàng)
dispatch_block_wait
//同步等待,直到指定的調(diào)度塊的執(zhí)行完成或者直到指定的超時(shí)已經(jīng)過(guò)去庇麦。
取消工作項(xiàng)
dispatch_block_cancel
//異步取消指定的調(diào)度塊计技。
dispatch_block_testcancel
//測(cè)試是否已取消給定的調(diào)度塊。
使用的方式山橄,創(chuàng)建dispatch_block_t垮媒,并在隊(duì)列執(zhí)行中,添加block:
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@">>>>>1");
sleep(2);
NSLog(@">>>>>>2");
});
dispatch_async(queue, block);
當(dāng)使用dispatch_block_cancel驾胆,如果block還未執(zhí)行時(shí)允許取消block執(zhí)行的方法涣澡,正在執(zhí)行的block不能取消。例如下丧诺,最后沒(méi)有打印數(shù)據(jù)因?yàn)槿牍穑呀?jīng)取消執(zhí)行block的方法:
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@">>>>>1");
sleep(2);
NSLog(@">>>>>>2");
});
dispatch_async(queue, block);
dispatch_block_cancel(block);
dispatch_block_wait是阻塞當(dāng)前線程去執(zhí)行block的操作,其規(guī)則是計(jì)時(shí)器的方式驳阎,設(shè)定計(jì)時(shí)器dispatch_time_t抗愁,在計(jì)時(shí)器結(jié)束之前,如果block執(zhí)行完畢呵晚,則返回0蜘腌,或者計(jì)時(shí)器計(jì)算后執(zhí)行當(dāng)前線程返回1,可用來(lái)設(shè)置在指定時(shí)間內(nèi)是否請(qǐng)求超時(shí):
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@">>>>>1");
sleep(2);
NSLog(@">>>>>>2");
});
dispatch_async(queue, block);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long result = dispatch_block_wait(block, timeout);
if (result == 0) {
NSLog(@"執(zhí)行成功");
} else {
NSLog(@"執(zhí)行超時(shí)");
}
舉一個(gè)例子饵隙,在一個(gè)dispatch_block_t對(duì)象撮珠,并且設(shè)置2秒超時(shí),并在超時(shí)中做取消block的操作金矛,看看結(jié)果:
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@">>>>>1");
sleep(5);
NSLog(@">>>>>>2");
});
dispatch_async(queue, block);
dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC));
long resutl = dispatch_block_wait(block, timeout);
if (resutl == 0) {
NSLog(@"執(zhí)行成功");
} else {
NSLog(@"執(zhí)行超時(shí)");
dispatch_block_cancel(block);
}
2019-04-24 14:29:18.940770+0800 test_1[1603:2562465] >>>>>1
2019-04-24 14:29:20.941842+0800 test_1[1603:2562400] 執(zhí)行超時(shí)
2019-04-24 14:29:23.946158+0800 test_1[1603:2562465] >>>>>>2
2019-04-24 14:29:23.946454+0800 test_1[1603:2562458] 結(jié)束
打印的結(jié)果是請(qǐng)求超時(shí)了芯急,因?yàn)閎lock內(nèi)部執(zhí)行了5秒,dispatch_block_cancel在執(zhí)行過(guò)程中也沒(méi)辦法關(guān)閉block正好驗(yàn)證我們的答案驶俊。
dispatch_block_notify
是在block執(zhí)行結(jié)束后會(huì)調(diào)用該函數(shù)進(jìn)行后面的處理娶耍,即使調(diào)用了dispatch_block_cancel,也會(huì)進(jìn)入該方法中饼酿,通常用來(lái)阻塞當(dāng)前線程榕酒,等待block操作完,DISPATCH_TIME_NOW故俐,DISPATCH_TIME_FOREVER
表示計(jì)時(shí)器無(wú)限時(shí)間想鹰,即永遠(yuǎn)阻塞必須等到group執(zhí)行完才能執(zhí)行。药版。
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_DETACHED, ^{
NSLog(@">>>>>1");
sleep(5);
NSLog(@">>>>>>2");
});
dispatch_async(queue, block);
dispatch_block_notify(block, queue, ^{
NSLog(@"結(jié)束");
});
7辑舷、Dispatch Group(調(diào)度組)
創(chuàng)建調(diào)度組
dispatch_group_create
//創(chuàng)建一個(gè)可以為其分配塊對(duì)象的新組。
dispatch_group_t
//提交到隊(duì)列以進(jìn)行異步調(diào)用的一組塊對(duì)象刚陡。
OS_dispatch_group
將工作添加到組中
dispatch_group_async
//異步調(diào)度塊以執(zhí)行惩妇,同時(shí)將其與指定的調(diào)度組關(guān)聯(lián)。
dispatch_group_async_f
//將應(yīng)用程序定義的函數(shù)提交給調(diào)度隊(duì)列筐乳,并將其與指定的調(diào)度組關(guān)聯(lián)歌殃。
添加完成處理程序
dispatch_group_notify
//當(dāng)一組先前提交的塊對(duì)象完成時(shí),計(jì)劃將塊對(duì)象提交到隊(duì)列蝙云。
dispatch_group_notify_f
//計(jì)劃在一組先前提交的塊對(duì)象完成時(shí)將應(yīng)用程序定義的函數(shù)提交到隊(duì)列氓皱。
等待任務(wù)完成執(zhí)行
dispatch_group_wait
//同步等待先前提交的塊對(duì)象完成; 如果塊在指定的超時(shí)時(shí)間段之前未完成,則返回勃刨。
手動(dòng)更新組
dispatch_group_enter
//顯式指示塊已進(jìn)入組波材。
dispatch_group_leave
//顯式指示組中的塊已完成執(zhí)行。
在平時(shí)簡(jiǎn)單的使用過(guò)程中身隐,前面的一些方式基本可以完成廷区,但是在針對(duì)復(fù)雜的請(qǐng)求,為了能在多線程中體現(xiàn)和更好的去操作贾铝,GCD提供的組隊(duì)列的方式隙轻,對(duì)用戶的邏輯操作使用dispatch_group_t,可以解決一些問(wèn)題垢揩。
dispatch_group允許您合并一組任務(wù)并同步group上的行為玖绿。將多個(gè)塊附加到dispatch_group_t并將它們安排在同一隊(duì)列或不同隊(duì)列上進(jìn)行異步執(zhí)行。當(dāng)所有塊完成執(zhí)行后叁巨,該組將執(zhí)行其完成處理程序斑匪。您也可以同步等待組中的所有塊完成執(zhí)行。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
for (int index = 0; index < 10; index ++) {
dispatch_group_async(group, queue, ^{
// 里面不能使用異步锋勺,除非與dispatch_group_enter一起用
NSLog(@"開(kāi)始請(qǐng)求 >> %d",index);
});
}
dispatch_group_notify(group, queue, ^{
NSLog(@"———————————— 打印結(jié)果 ——————————————");
});
第二種dispatch_group_enter 和dispatch_group_leave的方式去控制group是否全部執(zhí)行完蚀瘸。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊(duì)列
for (int index = 0; index < 10; index ++) {
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"%d",index);
sleep(2);
NSLog(@"%d >> 結(jié)束",index);
dispatch_group_leave(group);
});
}
dispatch_group_notify(group, queue, ^{
NSLog(@"———————————— 打印結(jié)果 ——————————————");
});
dispatch_group_notify是在執(zhí)行完之后的通知,為了能執(zhí)行完畢宙刘,dispatch_group_enter 和dispatch_group_leave必須成對(duì)出現(xiàn)否則會(huì)崩潰苍姜。
dispatch_group_wait 同樣和dispatch_block_wait一樣,是用來(lái)阻塞當(dāng)前線程的悬包,只有當(dāng)計(jì)時(shí)器結(jié)束(返回1)衙猪,或者group執(zhí)行完畢(返回0)才會(huì)繼續(xù)執(zhí)行線程后的操作,DISPATCH_TIME_NOW,DISPATCH_TIME_FOREVER
表示計(jì)時(shí)器無(wú)限時(shí)間布近,即永遠(yuǎn)阻塞必須等到group執(zhí)行完才能執(zhí)行垫释。
8、Dispatch Semaphore(信號(hào)量)和 Dispatch Barrier
信號(hào)量是什么撑瞧,信號(hào)量是為了控制等待功能可以提前釋放而存在的棵譬,用來(lái)對(duì)線程的控制計(jì)數(shù)器。dispatch_semaphore_wait(等待预伺,信號(hào)量遞減) 或 dispatch_semaphore_signal (發(fā)送订咸,信號(hào)量遞增)阻塞線程的同時(shí)曼尊,可以在用戶認(rèn)為操作完后可以將阻塞線程手動(dòng)釋放而存在的。
調(diào)度信號(hào)量是傳統(tǒng)計(jì)數(shù)信號(hào)量的有效實(shí)現(xiàn)脏嚷。僅當(dāng)需要阻止調(diào)用線程時(shí)骆撇,Dispatch信號(hào)量才會(huì)調(diào)用內(nèi)核。如果調(diào)用信號(hào)量不需要阻塞父叙,則不進(jìn)行內(nèi)核調(diào)用神郊。
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
dispatch_semaphore_signal(sem);
dispatch_async(queue, ^{
NSLog(@"111");
sleep(2);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"222");
sleep(2);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
dispatch_async(queue, ^{
NSLog(@"333");
sleep(2);
});
2019-04-24 17:35:29.780870+0800 test_1[1667:2627449] 111
2019-04-24 17:35:31.786097+0800 test_1[1667:2627449] 222
2019-04-24 17:35:33.800574+0800 test_1[1667:2627450] 333
根據(jù)打印結(jié)果可以知道,當(dāng)執(zhí)行并行異步的時(shí)候趾唱,原本是同步進(jìn)行的線程涌乳,因?yàn)閳?zhí)行了dispatch_semaphore_wait阻塞了線程后面的執(zhí)行,又使用dispatch_semaphore_signal去回復(fù)線程執(zhí)行甜癞,將原本并行的線程夕晓,通過(guò)這種方式變成串行的線程。
對(duì)信號(hào)量的使用在復(fù)雜的需求上經(jīng)常性的會(huì)出現(xiàn)悠咱,后面我會(huì)根據(jù)一些實(shí)戰(zhàn)讓大家更了解运授。
Dispatch Barrier
使用屏障同步調(diào)度隊(duì)列中一個(gè)或多個(gè)任務(wù)的執(zhí)行。向并發(fā)調(diào)度隊(duì)列添加屏障時(shí)乔煞,隊(duì)列會(huì)延遲屏障塊(以及屏障后提交的任何任務(wù))的執(zhí)行吁朦,直到所有先前提交的任務(wù)完成執(zhí)行。在前面的任務(wù)完成執(zhí)行之后渡贾,隊(duì)列自己執(zhí)行屏障塊逗宜。一旦屏障塊完成,隊(duì)列將恢復(fù)其正常執(zhí)行行為空骚。
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_CONCURRENT);
for (int i=0; i<3; i++) {
dispatch_async(queue, ^{
NSLog(@"111 >>> %@", [NSThread currentThread]);
});
}
dispatch_barrier_async(queue, ^{
NSLog(@"barrier結(jié)束了");
});
dispatch_async(queue, ^{
NSLog(@"333 >>> %@", [NSThread currentThread]);
});
2019-04-24 17:57:28.864959+0800 test_1[1696:2637202] 111 >>> <NSThread: 0x1c4079a00>{number = 4, name = (null)}
2019-04-24 17:57:28.864959+0800 test_1[1696:2637201] 111 >>> <NSThread: 0x1c4079880>{number = 3, name = (null)}
2019-04-24 17:57:28.865091+0800 test_1[1696:2637202] 111 >>> <NSThread: 0x1c4079a00>{number = 4, name = (null)}
2019-04-24 17:57:28.865637+0800 test_1[1696:2637199] barrier結(jié)束了
2019-04-24 17:57:28.865707+0800 test_1[1696:2637199] 333 >>> <NSThread: 0x1c04682c0>{number = 5, name = (null)}
從打印的結(jié)果可以看出纺讲,雖然是異步并行的隊(duì)列,但是前面的循環(huán)在沒(méi)執(zhí)行完之前囤屹,后面的都不會(huì)執(zhí)行熬甚,就是因?yàn)閐ispatch_barrier_async起到了作用,會(huì)阻隔后面的操作肋坚。
dispatch_queue_t queue = dispatch_queue_create("123", DISPATCH_QUEUE_SERIAL);
for (int i=0; i<3; i++) {
dispatch_async(queue, ^{
NSLog(@"111 >>> %@", [NSThread currentThread]);
});
}
dispatch_barrier_sync(queue, ^{
NSLog(@"barrier結(jié)束了");
});
dispatch_sync(queue, ^{
NSLog(@"333 >>> %@", [NSThread currentThread]);
});
2019-04-24 18:05:36.530328+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530422+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530460+0800 test_1[1712:2641128] 111 >>> <NSThread: 0x1c0279300>{number = 3, name = (null)}
2019-04-24 18:05:36.530498+0800 test_1[1712:2641110] barrier結(jié)束了
2019-04-24 18:05:36.530642+0800 test_1[1712:2641110] 333 >>> <NSThread: 0x1c006a300>{number = 1, name = main}
即使是同步串行的barrier 依然會(huì)阻塞當(dāng)前線程和后面的隊(duì)列乡括,等待前面隊(duì)列結(jié)束。
結(jié)束語(yǔ)
到此GCD的常用的對(duì)象和方法都介紹完了智厌,養(yǎng)兵千日用兵一時(shí)诲泌,自己根據(jù)上面的理解帶入到開(kāi)發(fā)中。