一、開端
隊列與任務(wù)創(chuàng)建
-
dispatch_queue_t
自定義GCD隊列,區(qū)分串行隊列與并行隊列 -
dispatch_async(queue, block)
執(zhí)行異步任務(wù) -
dispatch_sync(queue, block)
執(zhí)行同步任務(wù)
GCD常用方法
-
dispatch_barrier_async(queue, block)
分割執(zhí)行異步任務(wù)塊 -
dispatch_group_t
隊列組淤井,分組執(zhí)行異步/同步任務(wù) -
dispatch_semaphore_t
信號量吴裤,通常用來保證線程安全哨查,或保持線程同步
GCD其他方法
-
dispatch_after(dispatch_time_t, queue, block)
指定時間之后執(zhí)行隊列中的任務(wù) -
dispatch_once(&dispatch_once_t, block)
保證任務(wù)只被執(zhí)行一次究驴,同時也能保證線程安全 -
dispatch_apply(count, queue, block)
快速迭代隊列任務(wù),不論并行/串行隊列阻肩,都是逐個遍歷任務(wù)來操作带欢,類似同步操作
二、詳述
前面對于常用的GCD方法做了一個簡要的展示烤惊,對于詳細的使用情況乔煞,這里一一來展開說明。
概念
先說說基本的任務(wù)和隊列:
任務(wù) 就是最基本的執(zhí)行單元柒室,在線程和隊列中渡贾,任務(wù)執(zhí)行被分為異步執(zhí)行 與 同步執(zhí)行。
同步執(zhí)行:
任務(wù)被添加到指定隊列后雄右,按順序執(zhí)行完當前任務(wù)后才會繼續(xù)執(zhí)行其他任務(wù)空骚,在此之前會等待任務(wù)執(zhí)行結(jié)束。此外擂仍,只能在當前線程中執(zhí)行任務(wù)囤屹,不具備開啟新線程能力。異步任務(wù):
任務(wù)被添加到指定隊列后逢渔,不會立即處理該任務(wù)肋坚,不做等待,繼續(xù)執(zhí)行后續(xù)任務(wù)肃廓。此外智厌,可以在新線程中執(zhí)行任務(wù),具備開啟線程的能力盲赊。
隊列 相當于一個容器铣鹏,用來存放和調(diào)度任務(wù)的,任務(wù)的同步角钩、異步執(zhí)行都是需要基于其所在的隊列屬性吝沫,隊列的不同,任務(wù)所具備開啟線程的能力也就不同递礼;隊列分為串行隊列與并行隊列惨险。
串行隊列:
每次只有一個任務(wù)被執(zhí)行。所有在此隊列中的任務(wù)脊髓,都是一個接一個的執(zhí)行(基于同步辫愉、異步執(zhí)行規(guī)則)。此外将硝,在此隊列中只會開啟一個線程來執(zhí)行其所有的任務(wù)恭朗。并行隊列:
同時可以執(zhí)行多個任務(wù),執(zhí)行順序由隊列(系統(tǒng))調(diào)度(基于同步依疼、異步執(zhí)行規(guī)則)痰腮。此外,在此隊列中可以同時開啟多個線程律罢,同時處理多個任務(wù)膀值,線程數(shù)量的上限基于系統(tǒng)限制。
1. 入口
-
隊列的建立
//并行隊列 dispatch_queue_t queue = dispatch_queue_create("queue.concurrent",DISPATCH_QUEUE_CONCURRENT); //串行隊列 dispatch_queue_t queue = dispatch_queue_create("queue.serial", DISPATCH_QUEUE_SERIAL);
上面是最直接的隊列創(chuàng)建方法误辑,在GCD中有另外2種特殊的隊列沧踏,不能手動創(chuàng)建,只能直接獲冉矶ぁ:
主隊列:
dispatch_get_main_queue();
全局隊列:
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
-
創(chuàng)建任務(wù)
- 同步任務(wù)
//queue為并行/串行隊列翘狱,創(chuàng)建/獲取方式參考上面隊列部分 dispatch_sync(queue, ^{ NSLog(@"do sync task here"); });
- 異步任務(wù)
//queue為并行/串行隊列,創(chuàng)建/獲取方式參考上面隊列部分 dispatch_async(queue, ^{ NSLog(@"do async task here"); });
-
任務(wù)與隊列關(guān)系
區(qū)別 并行隊列 串行隊列 主隊列 同步任務(wù) 不開啟新線程砰苍,串行執(zhí)行任務(wù) 不開啟新線程潦匈,串行執(zhí)行任務(wù) 主線程調(diào)用:觸發(fā)死鎖
其他線程調(diào)用:與普通串行隊列同步任務(wù)情況相同異步任務(wù) 開啟新線程,并行執(zhí)行任務(wù) 只開啟一條新線程赚导,串行執(zhí)行任務(wù) 沒有開啟新線程历等,串行執(zhí)行任務(wù)
代碼此處不再贅述,網(wǎng)上有很多此類說明辟癌,具體可以參考這篇文章寒屯,相當全面:iOS多線程:『GCD』詳盡總結(jié)
2. 擴展
GCD的基本用法之外,還有許多常用的方法黍少,在本文的開頭已經(jīng)列舉出來了寡夹,下面簡述一下,作為記錄參考厂置。
-
dispatch_barrier_async
GCD柵欄方法菩掏,用于分割上下兩塊任務(wù)操作,每塊都可以包含多個異步任務(wù)操作昵济。被分割的2塊任務(wù)組可以看做為2個同步執(zhí)行的任務(wù)組智绸,只有當?shù)谝粔K任務(wù)全部執(zhí)行完畢后野揪,才會開始第二塊任務(wù)執(zhí)行。此方法用于并行異步任務(wù)處理中瞧栗,同步任務(wù)處理沒有意義斯稳。dispatch_queue_t queue = dispatch_queue_create("queue.serial", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ NSLog(@"task1 --->%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"task2 --->%@",[NSThread currentThread]); }); dispatch_barrier_async(queue, ^{ NSLog(@"barrier --->%@",[NSThread currentThread]); }); dispatch_async(queue, ^{ NSLog(@"task3 --->%@",[NSThread currentThread]); }); dispatch_sync(queue, ^{ NSLog(@"task4 --->%@",[NSThread currentThread]); });
執(zhí)行結(jié)果:
task2 ---><NSThread: 0x60400007c800>{number = 1, name = main} task1 ---><NSThread: 0x600000466700>{number = 3, name = (null)} barrier ---><NSThread: 0x604000467880>{number = 4, name = (null)} task4 ---><NSThread: 0x60400007c800>{number = 1, name = main} task3 ---><NSThread: 0x604000467880>{number = 4, name = (null)}
-
dispatch_group
GCD隊列組,此方法常用于耗時任務(wù)組的等待操作迹恐,有點類似dispatch_barrier_async
方法挣惰,等待一大塊的任務(wù)執(zhí)行完畢后才繼續(xù)執(zhí)行后續(xù)隊列任務(wù)。- 2種調(diào)用方式:
- 通過
dispatch_group_async
將任務(wù)所在隊列放到隊列組中殴边,接著通過dispatch_group_notify
來回到指定的線程執(zhí)行任務(wù)憎茂。 - 通過
dispatch_group_enter
、dispatch_group_leave
組合來操作任務(wù)所在隊列進入/離開隊列組锤岸,接著使用dispatch_group_notify
來回到指定線程執(zhí)行任務(wù)竖幔。
- 通過
此處等待組隊列任務(wù)執(zhí)行完成的方法還有一種:
dispatch_group_wait
,與dispatch_group_notify
有區(qū)別是偷。dispatch_group_wait
用于阻塞當前線程赏枚,等待指定group中的所有隊列任務(wù)執(zhí)行完成后,才會繼續(xù)執(zhí)行dispatch_group_wait
后面的任務(wù)晓猛,其作用與dispatch_group_notify
一致饿幅。 - 2種調(diào)用方式:
-
dispatch_group_async
dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_queue_create("queue.concurrent", DISPATCH_QUEUE_CONCURRENT); void(^groupBlock)(NSString *taskName) = ^(NSString* taskName) { for (int i = 0; i<2; i++) { [NSThread sleepForTimeInterval:2.]; NSLog(@"%@ --> %@",taskName,[NSThread currentThread]); } }; dispatch_group_async(group, queue, ^{ groupBlock(@"task1"); }); dispatch_group_async(group, queue, ^{ groupBlock(@"task2"); }); dispatch_group_notify(group, dispatch_get_main_queue(), ^{ groupBlock(@"end group task"); }); /* * 此方法效果與dispatch_group_notify一致,用于等待group中隊列任務(wù)執(zhí)行完畢后戒职,繼續(xù)執(zhí)行其后的其他任務(wù) dispatch_group_wait(group, DISPATCH_TIME_FOREVER); groupBlock(@"end group task"); */
-
dispatch_group_enter
與dispatch_group_leave
dispatch_group_t group = dispatch_group_create(); dispatch_queue_t queue = dispatch_queue_create("queue.concurrent", DISPATCH_QUEUE_CONCURRENT); void(^groupBlock)(NSString *taskName, BOOL groupTask) = ^(NSString* taskName, BOOL groupTask) { for (int i = 0; i<2; i++) { [NSThread sleepForTimeInterval:2.]; NSLog(@"%@ --> %@",taskName,[NSThread currentThread]); } if (groupTask) { //如果為group任務(wù)栗恩,則離開group隊列 dispatch_group_leave(group); } }; dispatch_group_enter(group);//進入group隊列 dispatch_group_async(group, queue, ^{ groupBlock(@"task1",YES); }); dispatch_group_enter(group);//進入group隊列 dispatch_group_async(group, queue, ^{ groupBlock(@"task2",YES); }); //等待上面的任務(wù)全部完成后,會繼續(xù)往下執(zhí)行洪燥,在此之前磕秤,此處阻塞了線程 dispatch_group_wait(group, DISPATCH_TIME_FOREVER); groupBlock(@"end group task",NO);
以上展示了2種執(zhí)行組隊列任務(wù)的方式,分別用了
dispatch_group_notify
與dispatch_group_wait
來等待組隊列執(zhí)行完畢捧韵。
-
dispatch_semaphore_t
GCD信號量市咆,通過操作信號量的增減,可以達到線程操作安全的目的再来。此方式提供了3個函數(shù)方法:
-
dispatch_semaphore_create
創(chuàng)建并初始化信號量 -
dispatch_semaphore_signal
發(fā)送一個信號蒙兰,信號量增加1 -
dispatch_semaphore_wait
減少1個信號量,當信號量小于0時芒篷,將會阻塞所在線程搜变,否則繼續(xù)執(zhí)行(注:為0時依舊繼續(xù)執(zhí)行)
此方式常用于:
- 保持線程同步,將異步任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
- 保證線程安全针炉,為線程加鎖
線程同步
__block int num = 0; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); dispatch_queue_t queue = dispatch_queue_create("queue.concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_async(queue, ^{ num = 100; dispatch_semaphore_signal(semaphore); }); dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); NSLog(@"num = %d",num);
輸出結(jié)果:
num = 100
可以看出挠他,原本異步執(zhí)行的任務(wù),卻在主線程輸出任務(wù)之前執(zhí)行了篡帕,說明在輸出
num
之前殖侵,線程處于阻塞狀態(tài)贸呢。此處如果將創(chuàng)建時的信號量改為1,則無法達到同步線程目的拢军,異步執(zhí)行的任務(wù)依舊在主線程輸出num值之后執(zhí)行楞陷。
線程安全
__block NSInteger saleNum = 0; NSInteger maxCount = 100; dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_queue_t sale_queue1 = dispatch_queue_create("sale1", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t sale_queue2 = dispatch_queue_create("sale2", DISPATCH_QUEUE_CONCURRENT); void(^saleBlock)(void) = ^() { while (saleNum<maxCount) { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//加鎖 if (saleNum<maxCount) { saleNum++; NSLog(@"已售出:%ld -> %@",saleNum,[NSThread currentThread]); [NSThread sleepForTimeInterval:0.2]; } else { NSLog(@"已售完 -> %@",[NSThread currentThread]); dispatch_semaphore_signal(semaphore);//解鎖 break; } dispatch_semaphore_signal(semaphore);//解鎖 } }; //售貨員1 dispatch_async(sale_queue1, ^{ saleBlock(); }); //售貨員2 dispatch_async(sale_queue2, ^{ saleBlock(); });
輸出結(jié)果太長,不在此處展示朴沿,最后得到的輸出順序是按照常規(guī)遞增方式來展現(xiàn)的猜谚,即
1败砂,2赌渣,3,....昌犹,100坚芜,已售完
。其只使用了1個信號的增量斜姥,來控制庫存加法的異步任務(wù)鸿竖,在同一時間只能由一個線程執(zhí)行,這樣就保證了該庫存數(shù)據(jù)的準確性铸敏。這里的主要思路是:總信號量為1缚忧,在進入執(zhí)行加法任務(wù)前,先通過
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
方法減少一個信號量杈笔,使總信號量為0闪水,保證當前線程無阻塞可以繼續(xù)執(zhí)行,如果在此同時另外一條線程插入進來開始訪問此任務(wù)蒙具,那么信號量將繼續(xù)減少(因為第二條線程也會走一次dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER)
方法)球榆,變?yōu)樨摂?shù),則該線程阻塞禁筏,這時持钉,只能先等待第一條線程執(zhí)行完此加法任務(wù)后,通過dispatch_semaphore_signal(semaphore)
方法增加一個信號量篱昔,解鎖第二條線程阻塞的情況每强,同時第二條線程將繼續(xù)執(zhí)行加法,如此循環(huán)下去州刽。 -
- 其他方法
-
dispatch_after
GCD延時執(zhí)行方法舀射,可以指定多久后執(zhí)行某個任務(wù),執(zhí)行此方法后怀伦,在指定時間之后才會將任務(wù)追加到隊列中脆烟,并不是到指定時間后才開始執(zhí)行任務(wù),所以指定的執(zhí)行時間并不是絕對準確的房待。NSLog(@"task0 --> %@", [NSThread currentThread]); dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"task1 --> %@", [NSThread currentThread]); });
打印結(jié)果很明顯能看到在task0之后邢羔,延遲了一段時間才執(zhí)行了task1
-
dispatch_once
GCD只會也只能執(zhí)行一次該任務(wù)的方法驼抹,常用語單例創(chuàng)建中,在整個程序運行過程中只會執(zhí)行一次拜鹤。static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ //此處執(zhí)行單例創(chuàng)建方法 NSLog(@"此段代碼只會執(zhí)行一次!"); });
此處結(jié)果不是很容易能看出來框冀,如果放到一個類的創(chuàng)建方法中,多次執(zhí)行就能很容易看到實際執(zhí)行的次數(shù)只有一次敏簿。
-
dispatch_apply
GCD中快速迭代方法明也,有點類似dispatch_group_wait
,會等待dispatch_apply
中的全部任務(wù)執(zhí)行完畢惯裕。dispatch_queue_t queue = dispatch_queue_create("queue.concurrent", DISPATCH_QUEUE_CONCURRENT); dispatch_apply(5, queue, ^(size_t i) { NSLog(@"current index:%zd --> %@",i,[NSThread currentThread]); }); NSLog(@"end task %@",[NSThread currentThread]);//最后才會輸出此處代碼
輸出結(jié)果為正常的串行/并行隊列中同步/異步任務(wù)調(diào)用順序温数,只不過需要等待
dispatch_apply
執(zhí)行完畢后才會執(zhí)行后續(xù)任務(wù)。
-
三蜻势、延伸思考
在串行撑刺、并行隊列中,同步握玛、異步執(zhí)行任務(wù)時够傍,如果涉及到嵌套操作,那么其執(zhí)行的順序以及開啟的線程狀態(tài)與數(shù)量都有什么樣的結(jié)果呢挠铲?-
死鎖觸發(fā)有幾種情況冕屯?
- 主隊列中執(zhí)行同步任務(wù):
dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"do something here"); });
雖然此任務(wù)是新開的一個同步任務(wù),處于主隊列中拂苹,但是實際上是嵌套在另一個主隊列同步任務(wù)中(當前正在執(zhí)行的任務(wù)中)安聘,當調(diào)用
dispatch_sync
方法時,會將此block加入到主隊列尾部醋寝,等待主隊列中的任務(wù)(當前正在執(zhí)行的任務(wù))執(zhí)行完畢返回后搞挣,才會繼續(xù)執(zhí)行block中的任務(wù)。根據(jù)規(guī)則音羞,串行隊列同步執(zhí)行任務(wù)會阻塞當前線程囱桨,直到該任務(wù)執(zhí)行完畢,此處當前線程為主線程嗅绰,那么調(diào)用block中的任務(wù)時主線程會被阻塞舍肠,意味著主隊列中的當前任務(wù)不能繼續(xù)執(zhí)行,而block中的任務(wù)必須等待主隊列中的當前任務(wù)執(zhí)行完畢才能繼續(xù)執(zhí)行窘面,進而形成了一個相互等待狀態(tài)翠语,線程就發(fā)生了死鎖。
- 同一個串行隊列中嵌套執(zhí)行同步任務(wù)
dispatch_queue_t queue = dispatch_queue_create("queue.serial", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue, ^{ NSLog(@"task1 --->%@",[NSThread currentThread]); dispatch_sync(queue, ^{ NSLog(@"task2 --->%@",[NSThread currentThread]); }); });
此操作將會卡在task2輸出之前:
task1 ---><NSThread: 0x604000079dc0>{number = 1, name = main} (lldb) //卡死
此處所處情況與主隊列中執(zhí)行同步任務(wù)情況相同财边,只不過更加具體和明顯肌括。
此處task1沒有被卡死,是因為隊列
queue
中沒有其他任務(wù)正在執(zhí)行酣难,那么task1任務(wù)加入到隊列queue
后直接被執(zhí)行谍夭;當執(zhí)行到第二個dispatch_sync
方法時黑滴,會將task2任務(wù)追加到隊列queue
尾部,此時task1任務(wù)實際上并沒有執(zhí)行完畢紧索,但是因為調(diào)用了task2任務(wù)袁辈,那么此處task1任務(wù)所在線程將會阻塞等待task2任務(wù)執(zhí)行完畢,但是由于task1任務(wù)并未執(zhí)行返回結(jié)果珠漂,導致task2任務(wù)在此處同樣處于等待狀態(tài)晚缩。如此一來,2個任務(wù)相互等待對方執(zhí)行完畢媳危,直接導致死鎖荞彼。以上需要注意的是,所有觸發(fā)死鎖的同步任務(wù)都處于同一個串行隊列中济舆,異步任務(wù)在添加任務(wù)后不會等待任執(zhí)行完畢卿泽,而是繼續(xù)往下執(zhí)行莺债,所以無法觸相互等待狀態(tài)就不會發(fā)生死鎖狀態(tài)滋觉。