目錄:
iOS多線程(一)--pthread着撩、NSThread
iOS多線程(二)--GCD詳解
iOS多線程(三)--NSOperation詳解
1. GCD簡(jiǎn)介
Grand Central Dispatch (GCD) 是Apple開(kāi)發(fā)的一個(gè)多核編程的較新的解決方法鸠澈。它主要用于優(yōu)化應(yīng)用程序以支持多核處理器以及其他對(duì)稱多處理系統(tǒng)即纲。它是一個(gè)在線程池模式的基礎(chǔ)上執(zhí)行的并行任務(wù)稼锅。
GCD有很多優(yōu)點(diǎn)具體如下:
1)GCD可用于多核的并行運(yùn)算
2)GCD會(huì)自動(dòng)利用更多的CPU內(nèi)核(比如雙核、四核)
3)GCD會(huì)自動(dòng)管理線程的生命周期(創(chuàng)建線程切揭、調(diào)度任務(wù)呀狼、銷毀線程)
4)程序員只需要告訴GCD想要執(zhí)行什么任務(wù),不需要編寫(xiě)任何線程管理代碼
2. 任務(wù)和隊(duì)列
學(xué)習(xí)GCD之前里烦,先來(lái)了解GCD中兩個(gè)核心概念:任務(wù)和隊(duì)列凿蒜。
- 任務(wù):就是執(zhí)行操作的意思,換句話說(shuō)就是你在線程中執(zhí)行的那段代碼胁黑。在GCD中是放在block中的废封。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行和異步執(zhí)行。兩者的主要區(qū)別是:是否具備開(kāi)啟新線程的能力丧蘸。
- 同步執(zhí)行(sync):只能在當(dāng)前線程中執(zhí)行任務(wù)漂洋,不具備開(kāi)啟新線程的能力
- 異步執(zhí)行(async):可以在新的線程中執(zhí)行任務(wù),具備開(kāi)啟新線程的能力
- 這里的隊(duì)列指任務(wù)隊(duì)列触趴,即用來(lái)存放任務(wù)的隊(duì)列氮发。隊(duì)列是一種特殊的線性表,采用FIFO(先進(jìn)先出)的原則冗懦,即新任務(wù)總是被插入到隊(duì)列的末尾,而讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開(kāi)始讀取仇祭。每讀取一個(gè)任務(wù)披蕉,則從隊(duì)列中釋放一個(gè)任務(wù)。在GCD中有兩種隊(duì)列:串行隊(duì)列和并行隊(duì)列乌奇。
- 并行隊(duì)列(Concurrent Dispatch Queue):可以讓多個(gè)任務(wù)并行(同時(shí))執(zhí)行(自動(dòng)開(kāi)啟多個(gè)線程同時(shí)執(zhí)行任務(wù))没讲。并行功能只有在異步(dispatch_async)函數(shù)下才有效(應(yīng)該是目前開(kāi)啟了多少線程,才有多少個(gè)任務(wù)才并行執(zhí)行吧)礁苗。注意特殊的并行隊(duì)列-全局并行隊(duì)列:dispatch_get_global_queue來(lái)創(chuàng)建全局并行隊(duì)列爬凑。GCD默認(rèn)提供了全局的并行隊(duì)列,需要傳入兩個(gè)參數(shù)试伙。第一個(gè)參數(shù)表示隊(duì)列優(yōu)先級(jí)嘁信,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二個(gè)參數(shù)暫時(shí)沒(méi)用疏叨,用0即可潘靖。
- 串行隊(duì)列(Serial Dispatch Queue):讓任務(wù)一個(gè)接著一個(gè)地執(zhí)行(一個(gè)任務(wù)執(zhí)行完畢后,再執(zhí)行下一個(gè)任務(wù))蚤蔓。注意特殊的串行隊(duì)列-主隊(duì)列:dispatch_get_main_queue()獲得主隊(duì)列, 所有放在主隊(duì)列中的任務(wù)卦溢,都會(huì)放到主線程中執(zhí)行。
3. GCD的使用
GCD的使用步驟其實(shí)很簡(jiǎn)單,只有兩步单寂。
1.創(chuàng)建一個(gè)隊(duì)列(串行隊(duì)列或并行隊(duì)列)
2.將任務(wù)添加到隊(duì)列中贬芥,然后系統(tǒng)就會(huì)根據(jù)任務(wù)類型執(zhí)行任務(wù)(同步執(zhí)行或異步執(zhí)行)
下邊來(lái)看看隊(duì)列的創(chuàng)建方法和任務(wù)的創(chuàng)建方法。
3.1 隊(duì)列的創(chuàng)建方法
可以使用dispatch_queue_create來(lái)創(chuàng)建對(duì)象宣决,需要傳入兩個(gè)參數(shù)蘸劈,第一個(gè)參數(shù)表示隊(duì)列的唯一標(biāo)識(shí)符,用于DEBUG疲扎,可為空昵时;第二個(gè)參數(shù)用來(lái)識(shí)別是串行隊(duì)列還是并行隊(duì)列。DISPATCH_QUEUE_SERIAL表示串行隊(duì)列椒丧,DISPATCH_QUEUE_CONCURRENT表示并行隊(duì)列壹甥。
// 串行隊(duì)列的創(chuàng)建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行隊(duì)列的創(chuàng)建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
3.2 任務(wù)的創(chuàng)建方法
// 同步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_sync(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 這里放任務(wù)代碼
});
// 異步執(zhí)行任務(wù)創(chuàng)建方法
dispatch_async(queue, ^{
NSLog(@"%@",[NSThread currentThread]); // 這里放任務(wù)代碼
});
3.3 GCD的組合方式
隊(duì)列的不同和任務(wù)執(zhí)行方式的不同可以組成以下6種組合,尤其注意后兩種主隊(duì)列(主隊(duì)列是一種特殊的串行隊(duì)列壶熏,主隊(duì)列里的任務(wù)只在主線程執(zhí)行)的組合:
- 并行隊(duì)列 + 同步執(zhí)行
- 并行隊(duì)列 + 異步執(zhí)行
- 串行隊(duì)列 + 同步執(zhí)行
- 串行隊(duì)列 + 異步執(zhí)行
- 主隊(duì)列 + 同步執(zhí)行
- 主隊(duì)列 + 異步執(zhí)行
不同組合方式效果不同句柠,直接參看表格如下:
并行隊(duì)列 | 串行隊(duì)列 | 主隊(duì)列 | |
---|---|---|---|
同步(sync) | 沒(méi)有開(kāi)啟新線程,串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新線程棒假,串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新線程溯职,串行執(zhí)行任務(wù) |
異步(async) | 有開(kāi)啟新線程,并行執(zhí)行任務(wù) | 有開(kāi)啟新線程(1條)帽哑,串行執(zhí)行任務(wù) | 沒(méi)有開(kāi)啟新線程谜酒,串行執(zhí)行任務(wù) |
相信網(wǎng)上其他帖子的示例已經(jīng)很多了,我這里只列舉下主隊(duì)列的同步執(zhí)行和異步執(zhí)行妻枕,其他的便不再一一贅述:
3.3.1 主隊(duì)列同步執(zhí)行
- (void)syncMain
{
NSLog(@"syncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"syncMain---end");
}
輸出結(jié)果
2016-09-03 19:32:15.356 GCD[11670:1908306] syncMain---begin
我們發(fā)現(xiàn)輸出begin后則沒(méi)有再繼續(xù)往下執(zhí)行了僻族,現(xiàn)在來(lái)具體分析原因:我們?cè)谥麝?duì)列上增加同步任務(wù),同步任務(wù)有個(gè)特點(diǎn)是立即執(zhí)行屡谐,而主隊(duì)列里的任務(wù)一定是在主線程執(zhí)行的述么,但主線程正在執(zhí)行syncMain這個(gè)方法,我們?cè)黾拥街麝?duì)列里的任務(wù)必須要等syncMain方法執(zhí)行完之后才能執(zhí)行愕掏,但我們很容易發(fā)現(xiàn)syncMain方法由于方法體里的同步任務(wù)是無(wú)法執(zhí)行完的度秘,這便是死鎖原因所在。
3.3.2 主隊(duì)列異步執(zhí)行
- (void)asyncMain
{
NSLog(@"asyncMain---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"2------%@",[NSThread currentThread]);
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"3------%@",[NSThread currentThread]);
}
});
NSLog(@"asyncMain---end");
}
輸出結(jié)果:
2016-09-03 19:33:54.995 GCD[11706:1911313] asyncMain---begin
2016-09-03 19:33:54.996 GCD[11706:1911313] asyncMain---end
2016-09-03 19:33:54.996 GCD[11706:1911313] 1------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 1------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 2------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
2016-09-03 19:33:54.997 GCD[11706:1911313] 3------<NSThread: 0x7fb623d015e0>{number = 1, name = main}
分析上面輸出饵撑,由于是在主隊(duì)列上異步執(zhí)行剑梳,不需要立即執(zhí)行,但是在主隊(duì)列上的任務(wù)必須是在主線程執(zhí)行肄梨,所以往主隊(duì)列上添加的三個(gè)異步任務(wù)是順序一一輸出結(jié)果的阻荒。
4 GCD線程之間的通訊
在iOS開(kāi)發(fā)過(guò)程中,我們通常把一些耗時(shí)的操作放在其他線程众羡,比如說(shuō)圖片下載侨赡、文件上傳等耗時(shí)操作,然后在主線程里邊進(jìn)行UI刷新,例如:點(diǎn)擊羊壹、滾動(dòng)蓖宦、拖拽等事件。當(dāng)我們有時(shí)候在其他線程完成了耗時(shí)操作時(shí)油猫,需要回到主線程稠茂,那么就用到了線程之間的通訊。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < 2; ++i) {
NSLog(@"1------%@",[NSThread currentThread]);
}
// 回到主線程
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"2-------%@",[NSThread currentThread]);
});
});
5 GCD的其他使用
5.1 多用派發(fā)隊(duì)列情妖,少用同步鎖
如果有多個(gè)線程要執(zhí)行同一份代碼睬关,則可能出現(xiàn)問(wèn)題。在之前我們使用鎖來(lái)實(shí)現(xiàn)同步機(jī)制毡证,具體實(shí)現(xiàn)有以下兩種方式:
//1. 內(nèi)置的同步塊
- (void)synchronizedMethod {
@synchronized (self) {
//save
}
}
//2. NSLock對(duì)象
_lock = [[NSLock alloc] init];
- (void)synchronizedMethod {
[_lock lock];
//safe
[_lock unlock];
}
但是濫用@synchronized (self)同步塊非常危險(xiǎn)电爹,因?yàn)樗型綁K都會(huì)搶奪同一個(gè)鎖,比如一個(gè)類里有很多屬性都需要同步時(shí)料睛,那么每個(gè)屬性的同步塊都得等其他所有的同步塊執(zhí)行完丐箩,但我們只是想要每個(gè)屬性各自獨(dú)立的同步即可。而且synchronized要求被其鎖住的對(duì)象一定要正確恤煞,不能應(yīng)該鎖A屎勘,但其實(shí)鎖了B。
然后直接使用NSLock對(duì)象的話居扒,一旦遇到死鎖也是會(huì)非常的麻煩概漱。
針對(duì)以上兩種情況,GCD提供了一種非常簡(jiǎn)單的解決方案喜喂,就是串行同步隊(duì)列犀概,將讀取操作和寫(xiě)入操作都寫(xiě)入同一個(gè)隊(duì)列,即可保證數(shù)據(jù)同步夜惭。
5.2 多用派發(fā)隊(duì)列,少用performSelector系列方法
performSelector系列有以下方法:
// NSObject (NSDelayedPerforming)
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
//NSObject (返回對(duì)象是id铛绰,如果遇到返回對(duì)象是void或者基礎(chǔ)數(shù)據(jù)類型就需要經(jīng)過(guò)一番轉(zhuǎn)換诈茧,而且最多能帶兩個(gè)參數(shù))
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
下面我們來(lái)看performSelector方法的使用,如下例;
SEL selector;
if (/*conditon 1*/) {
selector = @selector(fun1);
}else if(/*conditon 2*/) {
selector = @selector(fun2);
}else {
selector = @selector(fun3);
}
代碼看起來(lái)沒(méi)什么問(wèn)題捂掰,但是編譯器卻給出了警告信息:
warning:performSelector may cause a leak beacause its selector
原因在于編譯器不知道將要調(diào)用的選擇子敢会,因此不了解其方法名及其返回值,所以沒(méi)辦法運(yùn)用ARC的內(nèi)存管理規(guī)則來(lái)判斷返回值是不是該釋放这嚣,因此ARC采用了比較謹(jǐn)慎的辦法就是不添加釋放操作鸥昏。但這么做可能導(dǎo)致內(nèi)存泄露,因?yàn)榉祷刂翟诒环祷貢r(shí)被方法保留了姐帚。
想把某任務(wù)放到主線程執(zhí)行吏垮,可以有以下兩種選擇方式,但我們還是應(yīng)該選擇GCD
//1.
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];
//2.
dispatch_async(dispatch_get_main_queue(), ^{
//[self doSomething]
});
5.3 dispatch_after
延后執(zhí)行也有兩種選擇方式,我們依然應(yīng)該選擇GCD
//1.
[self performSelectorOnMainThread:@selector(doSomething) withObject:nil waitUntilDone:NO];
//2.
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW,(int64_t)(5.0*NSEC_PER_SEC));
dispatch_after(time, dispatch_get_main_queue(), ^{
//[self doSomething]
});
5.4 dispatch_group膳汪,根據(jù)系統(tǒng)資源狀態(tài)來(lái)執(zhí)行任務(wù)
dispatch_group能把任務(wù)分組唯蝶,調(diào)用者可以等待這組任務(wù)執(zhí)行完畢,也可以在提供回調(diào)函數(shù)后繼續(xù)往下執(zhí)行遗嗽,這組任務(wù)完成后粘我,調(diào)用者會(huì)得到通知。這個(gè)功能有許多用途痹换,最重要以及最值得注意的是要將并發(fā)執(zhí)行的多個(gè)任務(wù)合為一個(gè)組征字,于是調(diào)用者就能知道這組任務(wù)什么時(shí)候才能全部執(zhí)行完畢。
dispatch_group有以下幾個(gè)相關(guān)方法:
5.4.1 dispatch_group_create
dispatch_group_t group = dispatch_group_create(); //創(chuàng)建任務(wù)分組
5.4.2 dispatch_group_async
void dispatch_group_async(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
- group ——對(duì)應(yīng)的任務(wù)組娇豫,之后可以通過(guò)dispatch_group_wait或者dispatch_group_notify監(jiān)聽(tīng)任務(wù)組內(nèi)任務(wù)的執(zhí)行情況
- queue ——block任務(wù)執(zhí)行的線程隊(duì)列匙姜,任務(wù)組內(nèi)不同任務(wù)的隊(duì)列可以不同
- block —— 執(zhí)行任務(wù)的block
5.4.3 dispatch_group_enter
用于添加對(duì)應(yīng)任務(wù)組中的未執(zhí)行完畢的任務(wù)數(shù),執(zhí)行一次锤躁,未執(zhí)行完畢的任務(wù)數(shù)加1搁料,當(dāng)未執(zhí)行完畢任務(wù)數(shù)為0的時(shí)候,才會(huì)使dispatch_group_wait解除阻塞和dispatch_group_notify的block執(zhí)行
void dispatch_group_enter(dispatch_group_t group);
5.4.4 dispatch_group_leave
用于減少任務(wù)組中的未執(zhí)行完畢的任務(wù)數(shù)系羞,執(zhí)行一次郭计,未執(zhí)行完畢的任務(wù)數(shù)減1,dispatch_group_enter和dispatch_group_leave要匹配椒振,不然系統(tǒng)會(huì)認(rèn)為group任務(wù)沒(méi)有執(zhí)行完畢
void dispatch_group_leave(dispatch_group_t group);
5.4.5 dispatch_group_wait
等待組任務(wù)完成昭伸,會(huì)阻塞當(dāng)前線程,當(dāng)任務(wù)組執(zhí)行完畢時(shí)澎迎,才會(huì)解除阻塞當(dāng)前線程
long dispatch_group_wait(dispatch_group_t group,
dispatch_time_t timeout);
- group ——需要等待的任務(wù)組
- timeout ——等待的超時(shí)時(shí)間(即等多久)庐杨,單位為dispatch_time_t。如果設(shè)置為DISPATCH_TIME_FOREVER,則會(huì)一直等待(阻塞當(dāng)前線程)夹供,直到任務(wù)組執(zhí)行完畢
5.4.6 dispatch_group_notify
待任務(wù)組執(zhí)行完畢時(shí)調(diào)用灵份,不會(huì)阻塞當(dāng)前線程
void dispatch_group_notify(dispatch_group_t group,
dispatch_queue_t queue,
dispatch_block_t block);
- group ——需要監(jiān)聽(tīng)的任務(wù)組
- queue ——block任務(wù)執(zhí)行的線程隊(duì)列,和之前group執(zhí)行的線程隊(duì)列無(wú)關(guān)
- block ——任務(wù)組執(zhí)行完畢時(shí)需要執(zhí)行的任務(wù)block
5.4.7 使用示例
最常用的用法如下:
dispatch_group_t group = dispatch_group_create();
NSLog(@"group one start");
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(@"group one finish");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"group finished");
});
輸出結(jié)果如下:
2017-09-11 16:09:00.125 DispatchGroupDemo[254:11537] group one start
2017-09-11 16:09:00.127 DispatchGroupDemo[254:11569] group one finish
2017-09-11 16:09:00.135 DispatchGroupDemo[254:11537] group finished
我們?cè)谏厦娴膁emo上深挖一下哮洽,如果是在組里執(zhí)行異步任務(wù)呢填渠?
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"group one start");
dispatch_group_async(group, queue, ^{
dispatch_async(queue, ^{
sleep(1); //這里線程睡眠1秒鐘,模擬異步請(qǐng)求
NSLog(@"group one finish");
});
});
dispatch_group_notify(group, queue, ^{
NSLog(@"group finished");
});
輸出結(jié)果如下:
2017-09-11 16:12:42.732 DispatchGroupDemo[263:12355] group one start
2017-09-11 16:12:42.734 DispatchGroupDemo[263:12379] group finished
2017-09-11 16:12:43.741 DispatchGroupDemo[263:12378] group one finish
我們發(fā)現(xiàn)輸出結(jié)果并不是我們想要的鸟辅,在group中嵌套了一個(gè)異步任務(wù)時(shí)氛什,group并沒(méi)有等待group內(nèi)的異步任務(wù)執(zhí)行完畢才進(jìn)入dispatch_group_notify中,這是因?yàn)榉肆梗赿ispatch_group_async中又啟了一個(gè)異步線程枪眉,而異步線程是直接返回的,所以group就認(rèn)為是執(zhí)行完畢了再层。
為了解決這個(gè)問(wèn)題贸铜,需要引入dispatch_group_enter和dispatch_group_leave的使用了堡纬。
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"group one start");
dispatch_group_enter(group);
dispatch_async(queue, ^{
dispatch_async(queue, ^{
sleep(1); //這里線程睡眠1秒鐘,模擬異步請(qǐng)求
NSLog(@"group one finish");
dispatch_group_leave(group);
});
});
dispatch_group_notify(group, queue, ^{
NSLog(@"group finished");
});
輸出結(jié)果如下:
2017-09-11 16:16:39.149 DispatchGroupDemo[281:13448] group one start
2017-09-11 16:16:40.156 DispatchGroupDemo[281:13469] group one finish
2017-09-11 16:16:40.157 DispatchGroupDemo[281:13469] group finished
5.5 dispatch柵欄方法
有時(shí)需要異步執(zhí)行兩組操作萨脑,而且第一組操作執(zhí)行完之后隐轩,才能開(kāi)始執(zhí)行第二組操作。這樣我們就需要一個(gè)相當(dāng)于柵欄一樣的一個(gè)方法將兩組異步執(zhí)行的操作組給分割起來(lái)渤早,當(dāng)然這里的操作組里可以包含一個(gè)或多個(gè)任務(wù)职车。
假設(shè)我們?cè)扔?個(gè)任務(wù)要執(zhí)行,我們現(xiàn)在要插入一個(gè)任務(wù)0鹊杖,這個(gè)任務(wù)0要在1悴灵、2、3都并發(fā)執(zhí)行完了之后才能執(zhí)行骂蓖,而4积瞒、5、6號(hào)任務(wù)要在這個(gè)任務(wù)0結(jié)束后才允許并發(fā)登下。對(duì)于這樣一種需求茫孔,很多朋友的第一反應(yīng)就是用個(gè)group就解決了。確實(shí)如此被芳,但是系統(tǒng)提供了一種更加簡(jiǎn)單地方法缰贝,那就是dispatch柵欄方法。
這就需要用到dispatch_barrier_async/dispatch_barrier_sync方法在兩個(gè)操作組間形成柵欄畔濒。
下面我們具體來(lái)看看dispatch_barrier_async和dispatch_barrier_sync的區(qū)別:
//dispatch_barrier_async示例
- (IBAction)testBarrierAsync:(id)sender {
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
NSLog(@"group one start");
dispatch_async(queue, ^{
NSLog(@"task 1 finish");
});
dispatch_async(queue, ^{
NSLog(@"task 2 finish");
});
dispatch_barrier_async(queue, ^{
NSLog(@"== %@ == barrier finished",[NSThread currentThread]);
});
NSLog(@"aaa");
dispatch_async(queue, ^{
NSLog(@"task 3 finish");
});
NSLog(@"bbb");
dispatch_async(queue, ^{
NSLog(@"task 4 finish");
});
輸出結(jié)果如下:
2017-09-12 19:24:10.409 TestGCD[1142:139438] group one start
2017-09-12 19:24:10.410 TestGCD[1142:139438] aaa
2017-09-12 19:24:10.410 TestGCD[1142:139438] bbb
2017-09-12 19:24:11.419 TestGCD[1142:139792] task 2 finish
2017-09-12 19:24:11.419 TestGCD[1142:139770] task 1 finish
2017-09-12 19:24:11.420 TestGCD[1142:139770] == <NSThread: 0x17027ce00>{number = 4, name = (null)} == barrier finished
2017-09-12 19:24:11.420 TestGCD[1142:139770] task 3 finish
2017-09-12 19:24:11.421 TestGCD[1142:139770] task 4 finish
我們現(xiàn)在將上面的示例里的dispatch_barrier_async改成dispatch_barrier_sync試試剩晴,其他的代碼不變,則會(huì)發(fā)現(xiàn)輸出變成了:
輸出結(jié)果如下:
2017-09-12 19:25:30.066 TestGCD[1142:139438] group one start
2017-09-12 19:25:30.067 TestGCD[1142:139927] task 1 finish
2017-09-12 19:25:30.068 TestGCD[1142:139927] task 2 finish
2017-09-12 19:25:30.069 TestGCD[1142:139438] == <NSThread: 0x17007f000>{number = 1, name = main} == barrier finished
2017-09-12 19:25:30.069 TestGCD[1142:139438] aaa
2017-09-12 19:25:30.070 TestGCD[1142:139438] bbb
2017-09-12 19:25:30.072 TestGCD[1142:139941] task 3 finish
2017-09-12 19:25:30.073 TestGCD[1142:139927] task 4 finish
根據(jù)輸出結(jié)果我們可以看出侵状,barrier塊是肯定在1赞弥,2執(zhí)行完之后,3趣兄,4執(zhí)行完之前輸出的绽左,但aaa和bbb的輸出位置卻不一樣⊥叮總結(jié)下dispatch_barrier_async與dispatch_barrier_sync的異同點(diǎn)如下妇菱。
相同點(diǎn):
- 都會(huì)等待在它前面插入隊(duì)列的任務(wù)(1、2)先執(zhí)行完
- 都會(huì)等待他們自己的任務(wù)(barrier)執(zhí)行完再執(zhí)行后面的任務(wù)(3暴区、4)
不同點(diǎn):
- dispatch_barrier_sync需要等待自己的任務(wù)(barrier)結(jié)束之后才會(huì)繼續(xù)程序,然后插入被寫(xiě)在它后面的任務(wù)(3辛臊、4)仙粱,然后執(zhí)行后面的任務(wù)
- dispatch_barrier_async將自己的任務(wù)(barrier)插入到queue之后,不會(huì)等待自己的任務(wù)結(jié)束彻舰,它會(huì)繼續(xù)把后面的任務(wù)(3伐割、4)插入到queue候味。dispatch_barrier_async的不等待(異步)特性體現(xiàn)在將任務(wù)插入隊(duì)列的過(guò)程,它的等待特性體現(xiàn)在任務(wù)真正執(zhí)行的過(guò)程隔心。
關(guān)于barrier這塊的使用我在實(shí)際寫(xiě)示例時(shí)遇到一個(gè)問(wèn)題白群,當(dāng)將dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);這句換成系統(tǒng)的全局隊(duì)列時(shí),輸出結(jié)果不盡人意硬霍,目前我也沒(méi)有弄懂為什么帜慢,在此處暫時(shí)留一個(gè)疑問(wèn)。
看葉孤城的iOS進(jìn)階指南時(shí)唯卖,他給出了這樣的解釋:
在global queue中使用barrier沒(méi)有意義粱玲,為什么?因?yàn)閎arrier實(shí)現(xiàn)的基本條件是拜轨,要寫(xiě)在同一隊(duì)列中抽减。舉個(gè)例子,你現(xiàn)在創(chuàng)建了兩個(gè)并行隊(duì)列橄碾,你在其中一個(gè)隊(duì)列中插入了一個(gè)barrier任務(wù)卵沉,那么你不可能期待他在第二個(gè)隊(duì)列里生效,對(duì)吧法牲,同樣的史汗,每一次使用global queue,系統(tǒng)分配給你的可能是不同的并行隊(duì)列皆串,你在其中插入一個(gè)barrier任務(wù)淹办,又有什么意義呢?
5.7 dispatch_semaphore信號(hào)量
信號(hào)量的用法相當(dāng)簡(jiǎn)單恶复,一共有是三個(gè)方法怜森。
dispatch_semaphore_create //創(chuàng)建一個(gè)信號(hào)量
dispatch_semphore_signal //發(fā)送一個(gè)信號(hào)
dispatch_semaphore_wait //等待信號(hào)
dispatch_semaphore的使用場(chǎng)景是處理并發(fā)控制,類似于NSOperationQueue的maxConcurrentOperationCount屬性谤牡,意思就是設(shè)定NSOperationQueue里的NSOperation同時(shí)運(yùn)行的最大數(shù)量副硅。
信號(hào)量同樣可以實(shí)現(xiàn)這樣的功能,
- 首先創(chuàng)建一個(gè)信號(hào)量:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
創(chuàng)建方法里會(huì)傳入一個(gè)long型參數(shù)翅萤,這個(gè)東西你可以想象是一個(gè)庫(kù)存恐疲,有了庫(kù)存才可以出貨。 - dispatch_semaphore_wait,每運(yùn)行一次套么,就會(huì)清一個(gè)庫(kù)存培己,如果庫(kù)存為0,則根據(jù)傳入的等待時(shí)間胚泌,決定等待增加庫(kù)存的時(shí)間省咨,如果設(shè)置為DISPATCH_TIME_FOREVER,那么意思就是永遠(yuǎn)等待增加庫(kù)存玷室,否則就永遠(yuǎn)不會(huì)往下面走
- dispatch_semaphore_signal零蓉,每運(yùn)行一次笤受,增加一個(gè)庫(kù)存
現(xiàn)在我們嘗試用dispatch_semaphore來(lái)實(shí)現(xiàn)并發(fā)的數(shù)量控制:
@implementation CustomOperationQueue
- (id)initWithConcurrentCount:(int)count {
self = [super init];
if(self) {
if(count < 1) count = 5;
semaphore = disptach_semaphore_create(count);
queue = diapatch_queue_create("com.www",DISPATCH_QUEUE_CONCURRENT);
}
return self;
}
- (id)init {
return [self initWithConcurrentCount:5];
}
- (void)addTask:(CallBackBlock)block {
dispatch_async(queue,^{
dispatch_semaphore_wait(semaphore,DISPATCH_TIME_FOREVER);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),0,^{
block();
dispatch_semaphore_signal(semaphore);
});
});
}
來(lái)解讀這段代碼:
- 創(chuàng)建了一個(gè)初始庫(kù)存是5的信號(hào)量
- 在addTask方法里,由于初始庫(kù)存是5敌蜂,所以第一次添加了一個(gè)任務(wù)之后箩兽,dispatch_semaphore_wait直接放行,并減少一個(gè)庫(kù)存章喉。當(dāng)完成一個(gè)任務(wù)之后汗贫,還回去一個(gè)庫(kù)存,調(diào)用dispatch_semaphore_signal增加一個(gè)庫(kù)存
- 那么囊陡,當(dāng)我們每個(gè)任務(wù)耗時(shí)都特別長(zhǎng)時(shí)芳绩,一直消耗庫(kù)存卻沒(méi)有增加庫(kù)存,那么添加到第6個(gè)任務(wù)時(shí)撞反,庫(kù)存為0妥色,那么wait會(huì)一直等待不執(zhí)行第六個(gè)任務(wù),直到庫(kù)存再次大于0時(shí)才會(huì)執(zhí)行
5.7 dispatch_apply快速迭代
dispatch_apply類似一個(gè)for循環(huán)遏片,會(huì)在指定的dispatch queue中運(yùn)行block任務(wù)n次嘹害,如果隊(duì)列是并發(fā)隊(duì)列,則會(huì)并發(fā)執(zhí)行block任務(wù)吮便,dispatch_apply是一個(gè)同步調(diào)用笔呀,block任務(wù)執(zhí)行n次后才返回。
在某些場(chǎng)景下髓需,使用dispatch_apply會(huì)有很大的性能提升许师。例如你的代碼需要以每個(gè)像素為基準(zhǔn)來(lái)處理計(jì)算image圖片。同時(shí)dispatch apply能夠避免一些線程爆炸的情況發(fā)生(創(chuàng)建很多線程)僚匆,具體看下例:
//危險(xiǎn)微渠,可能導(dǎo)致線程爆炸以及死鎖
for (int i = 0; i < 999; i++){
dispatch_async(q, ^{...});
}
dispatch_barrier_sync(q, ^{});
// 較優(yōu)選擇, GCD 會(huì)管理并發(fā)
dispatch_apply(999, q, ^(size_t i){...});
但需要注意的是dispatch_apply是一個(gè)同步調(diào)用咧擂,會(huì)阻塞當(dāng)前線程逞盆,我們編碼時(shí)應(yīng)實(shí)時(shí)注意不要阻塞主線程。具體dispatch_apply的使用我也寫(xiě)了一個(gè)簡(jiǎn)單的示例如下:
dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
dispatch_apply(1000, queue, ^(size_t index){
NSLog(@"====%zu",index);
});
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"主線程更新");
});
});
NSLog(@"test");
以上結(jié)果會(huì)先輸出test松申,然后循環(huán)1000次輸出"主線程更新"云芦。某些情況下使用dispatch_apply的效果與使用dispatch_group一樣。
5.8 dispatch_once來(lái)執(zhí)行只需運(yùn)行一次的線程安全代碼
單例模式的實(shí)現(xiàn)方法可以用如下同步塊的方法實(shí)現(xiàn):
+ (id)shareInstance {
static TestClass *shareInstance = nil;
@synchronized (self) {
if (!shareInstance) {
shareInstance = [[TestClass alloc] init];
}
}
return shareInstance;
}
但GCD出現(xiàn)后贸桶,提供了一種更為簡(jiǎn)單的方式:
+ (id)shareInstance {
static TestClass *shareInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
shareInstance = [[TestClass alloc] init];
});
return shareInstance;
}
相對(duì)于同步塊舅逸,dispatch可以簡(jiǎn)化代碼且絕對(duì)的保證線程安全,根本無(wú)需使用重量級(jí)的加鎖皇筛、取鎖機(jī)制低匙。需要注意的是只需執(zhí)行一次的塊來(lái)說(shuō)羽圃,每次調(diào)用函數(shù)傳入的標(biāo)記必須是完全相同的呢蔫,因此開(kāi)發(fā)會(huì)將標(biāo)記變量聲明在static活global作用域里褥影。
5.9 不要使用dispatch_get_current_queue
此函數(shù)已經(jīng)廢棄,只應(yīng)做調(diào)試之用离例。
6 參考資料
終于寫(xiě)到尾聲啦换团,整個(gè)過(guò)程參考了非常多的文章資料,以及代碼示例均經(jīng)過(guò)實(shí)際驗(yàn)證宫蛆。
好文推薦:iOS GCD 死鎖理解
主要參考:iOS多線程--徹底學(xué)會(huì)多線程之『GCD』