前言
GCD是iOS開發(fā)中十分重要的多線程方案之一,通過對大神文章的學習甩苛,此篇文章為代碼的練習和一部分自己學習的總結和記錄会宪。
原文章鏈接:http://www.reibang.com/p/2d57c72016c6
簡介
GCD的全稱是Grand Central Dispatch,可譯為“牛逼的中樞調(diào)度器”元践,純C語言韭脊,提供了非常多強大的函數(shù)。
- GCD是蘋果公司為多核的并行運算提出的解決方案
- GCD會自動利用更多的CPU內(nèi)核(比如雙核单旁、四核)
- GCD會自動管理線程的生命周期(創(chuàng)建線程沪羔、調(diào)度任務、銷毀線程)
GCD任務和隊列
GCD中兩個核心的概念:任務和隊列
任務:就是要執(zhí)行的操作象浑,在GCD中執(zhí)行操作的代碼塊是寫在block中的蔫饰。任務有兩種派發(fā)方式:同步派發(fā)和異步派發(fā)琅豆。
同步派發(fā)和異步派發(fā)的區(qū)別就是是否會阻塞當前線程,同步派發(fā)的任務在當前線程執(zhí)行篓吁,并使當前線程等待派發(fā)的任務執(zhí)行完成才能繼續(xù)執(zhí)行茫因。異步派發(fā)的任務在其他線程執(zhí)行,不會阻塞當前線程杖剪。冻押。當然還有一條優(yōu)先級更高的規(guī)則,凡是派發(fā)到主隊列的任務都會在主線程執(zhí)行摘盆。例如翼雀,在子線程同步派發(fā)任務到主線程,則不會在當前線程執(zhí)行孩擂±窃ǎ或者在主線程異步派發(fā)到主線程,也不會在其他線程執(zhí)行类垦。當以某種派發(fā)方式向某種隊列多次派發(fā)任務后狈邑,執(zhí)行結果如下表所示。
隊列:隊列是一種特殊的線性表蚤认,采用 FIFO(先進先出)的原則米苹,GCD中有兩種隊列,串行隊列和并發(fā)隊列砰琢。兩者都符合 FIFO(先進先出)的原則蘸嘶。兩者的主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數(shù)不同陪汽。
串行隊列:每次只有一個任務被執(zhí)行训唱。讓任務一個接著一個地執(zhí)行。(只開啟一個線程挚冤,一個任務執(zhí)行完畢后况增,再執(zhí)行下一個任務)。
并發(fā)隊列:可以讓多個任務并發(fā)(同時)執(zhí)行训挡。(可以開啟多個線程澳骤,并且同時執(zhí)行任務)。
總結 | 同步派發(fā) | 異步派發(fā) |
---|---|---|
串行隊列 | 當前線程串行執(zhí)行澜薄,阻塞當前線程 | 新建單個線程串行執(zhí)行为肮,不阻塞當前線程。 |
并發(fā)隊列 | 當前線程串行執(zhí)行肤京,阻塞當前線程 | 新建多個線程并發(fā)執(zhí)行颊艳,不阻塞當前線程。 |
主隊列 | 主線程串行執(zhí)行,阻塞當前線程 | 主線程串行執(zhí)行籽暇,不阻塞當前線程 |
異步執(zhí)行(async)雖然具有開啟新線程的能力温治,但是并不一定開啟新線程。
并發(fā)隊列只有在異步執(zhí)行的情況下才會出現(xiàn)并發(fā)戒悠。
GCD的使用步驟
- 創(chuàng)建一個隊列(串行隊列或并發(fā)隊列)
- 將任務追加到任務的等待隊列中熬荆,然后系統(tǒng)就會根據(jù)任務類型執(zhí)行任務(同步執(zhí)行或異步執(zhí)行)
隊列的創(chuàng)建和獲取
//串行隊列創(chuàng)建
dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
//并發(fā)隊列的創(chuàng)建
dispatch_queue_t queue2 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
//主隊列
dispatch_queue_t mainQueue = dispatch_get_main_queue();
//全局并發(fā)隊列 第一個參數(shù)表示隊列的優(yōu)先級,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT绸狐,第二個參數(shù)s用0即可
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
任務的創(chuàng)建方法
GCD提供了同步執(zhí)行任務和異步執(zhí)行任務
//同步執(zhí)行任務的創(chuàng)建方法
dispatch_sync(queue, ^{
//這里放同步執(zhí)行任務代碼
})
//異步執(zhí)行任務的創(chuàng)建方法
dispatch_async(queue, ^{
//這里放異步執(zhí)行任務代碼
});
GCD的使用組合
同步執(zhí)行 并發(fā)隊列
同步執(zhí)行的原則是不會開辟新的線程卤恳,就在當前線程執(zhí)行,同步執(zhí)行并發(fā)隊列的情況寒矿,不會出現(xiàn)并發(fā)突琳,因為同步執(zhí)行不能開辟新的線程,只有當前一個線程符相,所以不存在并發(fā)拆融,而且當前線程只有等待當前隊列正在執(zhí)行的任務執(zhí)行完畢后,在能繼續(xù)執(zhí)行下面的操作啊终,依然是執(zhí)行完一個任務后镜豹,再執(zhí)行下一個任務。
所有任務都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行的蓝牲,同步任務需要等待隊列的任務執(zhí)行結束趟脂。
//同步執(zhí)行并發(fā)隊列
NSLog(@"syncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"syncConcurrent---end");
syncConcurrent---begin
1---<NSThread: 0x600002bd9380>{number = 1, name = main}
1---<NSThread: 0x600002bd9380>{number = 1, name = main}
2---<NSThread: 0x600002bd9380>{number = 1, name = main}
2---<NSThread: 0x600002bd9380>{number = 1, name = main}
3---<NSThread: 0x600002bd9380>{number = 1, name = main}
3---<NSThread: 0x600002bd9380>{number = 1, name = main}
syncConcurrent---end
異步執(zhí)行 并發(fā)隊列
異步執(zhí)行會開辟新的線程,異步執(zhí)行下并發(fā)隊列會具備并發(fā)性例衍,會開辟多個線程昔期,同時執(zhí)行多個任務。所以任務都end后才執(zhí)行佛玄,說明任務的執(zhí)行不需要做等待硼一。
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"asyncConcurrent---end");
2019-08-06 18:39:33.823042+0800 MasonryTest[12796:1249138] asyncConcurrent---begin
asyncConcurrent---end
1---<NSThread: 0x600000ec0340>{number = 4, name = (null)}
2---<NSThread: 0x600000ec5040>{number = 3, name = (null)}
2---<NSThread: 0x600000ed81c0>{number = 5, name = (null)}
1---<NSThread: 0x600000ec0340>{number = 4, name = (null)}
2---<NSThread: 0x600000ec5040>{number = 3, name = (null)}
2---<NSThread: 0x600000ed81c0>{number = 5, name = (null)}
同步執(zhí)行 串行隊列
同步執(zhí)行,并不會開辟新的線程翎嫡,就在當前線程執(zhí)行欠动,串行隊列的特點就是執(zhí)行完一個任務之后永乌,再執(zhí)行下一個任務惑申。
所有任務都在打印的syncConcurrent---begin和syncConcurrent---end之間執(zhí)行(同步任務需要等待隊列的任務執(zhí)行結束)。
//同步執(zhí)行 串行隊列
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"asyncConcurrent---end");
asyncConcurrent---begin
1---<NSThread: 0x600000ae5340>{number = 1, name = main}
1---<NSThread: 0x600000ae5340>{number = 1, name = main}
2---<NSThread: 0x600000ae5340>{number = 1, name = main}
2---<NSThread: 0x600000ae5340>{number = 1, name = main}
3---<NSThread: 0x600000ae5340>{number = 1, name = main}
3---<NSThread: 0x600000ae5340>{number = 1, name = main}
asyncConcurrent---end
異步執(zhí)行 串行隊列
異步執(zhí)行會開辟新的線程翅雏,由于是串行隊列圈驼,所以只開辟了一個線程。
所有任務是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的望几,異步執(zhí)行不做任何等待绩脆,可以繼續(xù)執(zhí)行任務。
任務是按順序執(zhí)行的,串行隊列只有一個任務執(zhí)行完后靴迫,下一個任務才會執(zhí)行惕味。
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"asyncConcurrent---end");
asyncConcurrent---begin
asyncConcurrent---end
1---<NSThread: 0x600002910480>{number = 3, name = (null)}
1---<NSThread: 0x600002910480>{number = 3, name = (null)}
2---<NSThread: 0x600002910480>{number = 3, name = (null)}
2---<NSThread: 0x600002910480>{number = 3, name = (null)}
3---<NSThread: 0x600002910480>{number = 3, name = (null)}
3---<NSThread: 0x600002910480>{number = 3, name = (null)}
同步執(zhí)行 主隊列
同步執(zhí)行 + 主隊列在不同線程中調(diào)用結果也是不一樣,在主線程中調(diào)用會出現(xiàn)死鎖玉锌,而在其他線程中則不會名挥。
在主線程中調(diào)用同步執(zhí)行 主隊列
形成死鎖。崩潰主守。
這是因為我們在主線程中執(zhí)行syncMain方法禀倔,相當于把syncMain任務放到了主線程的隊列中。而同步執(zhí)行會等待當前隊列中的任務執(zhí)行完畢参淫,才會接著執(zhí)行救湖。那么當我們把任務1追加到主隊列中,任務1就在等待主線程處理完syncMain任務涎才。而syncMain任務需要等待任務1執(zhí)行完畢鞋既,才能接著執(zhí)行。
那么耍铜,現(xiàn)在的情況就是syncMain任務和任務1都在等對方執(zhí)行完畢涛救。這樣大家互相等待,所以就卡住了业扒,所以我們的任務執(zhí)行不了检吆,而且syncMain---end也沒有打印。
任務的追加和任務的執(zhí)行互相等待程储,形成了死鎖蹭沛。當我們將任務1追加到主隊列的時候,任務1就在等待主線程處理完syncMain任務章鲤,而syncMain任務有需要等待任務1執(zhí)行完畢后才能接著執(zhí)行摊灭。
- (void)syncMain{
//在主線程中 同步執(zhí)行 主隊列
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_sync(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"asyncConcurrent---end");
}
其他線程中調(diào)用同步執(zhí)行 主隊列
這樣相當于將syncMain任務放到了其他線程中,而任務1败徊,2帚呼,3則追加到了主隊列,在主線程中執(zhí)行皱蹦。因為主隊列現(xiàn)在沒有正在執(zhí)行的任務煤杀,所以會直接執(zhí)行主隊列任務1,然后2沪哺,3依次執(zhí)行沈自,不會形成死鎖。
[NSThread detachNewThreadWithBlock:^{
[self syncMain];
}];
asyncConcurrent---begin
1---<NSThread: 0x600002299380>{number = 1, name = main}
1---<NSThread: 0x600002299380>{number = 1, name = main}
2---<NSThread: 0x600002299380>{number = 1, name = main}
3---<NSThread: 0x600002299380>{number = 1, name = main}
3---<NSThread: 0x600002299380>{number = 1, name = main}
asyncConcurrent---end
當前串行隊列正在執(zhí)行的任務所在的線程”繼續(xù)向當前隊列同步派發(fā)任務辜妓,就會造成死鎖枯途。因為串行隊列是順序執(zhí)行的忌怎,后進入的任務必須等待前邊的任務執(zhí)行完成,而同步派發(fā)的任務則會阻塞當前線程直到自己執(zhí)行完成酪夷。所以榴啸,當前線程如果就是“串行隊列正在執(zhí)行的任務所在的線程”,那么串行隊列正在執(zhí)行的任務就會阻塞晚岭,就無法執(zhí)行后邊同步派發(fā)的任務插掂,同步派發(fā)的任務得不到執(zhí)行,就不會取消當前線程的阻塞狀態(tài)腥例,從而造成了死鎖辅甥。
異步執(zhí)行 主隊列
所有任務都在主線程中執(zhí)行,雖然異步執(zhí)行有能力開辟線程燎竖,但因為是主隊列璃弄,主隊列的任務都在主線程中執(zhí)行。
所有任務是在打印的syncConcurrent---begin和syncConcurrent---end之后才開始執(zhí)行的构回。異步執(zhí)行不需要等待夏块,可以繼續(xù)執(zhí)行任務。
主隊列是串行隊列纤掸,因此隊列中添加的任務需要按順序執(zhí)行脐供。
NSLog(@"asyncConcurrent---begin");
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
});
NSLog(@"asyncConcurrent---end");
asyncConcurrent---begin
asyncConcurrent---end
1---<NSThread: 0x6000005ea900>{number = 1, name = main}
2---<NSThread: 0x6000005ea900>{number = 1, name = main}
2---<NSThread: 0x6000005ea900>{number = 1, name = main}
3---<NSThread: 0x6000005ea900>{number = 1, name = main}
3---<NSThread: 0x6000005ea900>{number = 1, name = main}
總結
同步執(zhí)行不會開辟新的線程,同時會阻塞線程借跪,需要等待追加到隊列中的任務執(zhí)行完畢后才能繼續(xù)追加任務政己。無論是并發(fā)隊列還是串行隊列,隊列中的任務都是順序執(zhí)行的掏愁,需要上一個任務執(zhí)行完后歇由,下一個任務才會執(zhí)行。
異步執(zhí)行具備開辟線程的能力果港,不會阻塞線程沦泌,可以不用等待追加進去的任務執(zhí)行完就可以繼續(xù)向隊列中追加任務,因為異步執(zhí)行不會影響代碼向下繼續(xù)執(zhí)行辛掠。在串行隊列下谢谦,異步執(zhí)行只會開辟一個線程,因為串行隊列的特點是一個任務執(zhí)行完后下一個任務才會執(zhí)行萝衩,因此開辟一個線程就足夠了回挽,串行隊列中的任務按順序依次執(zhí)行。在并發(fā)隊列下欠气,會開辟多個線程厅各,同步執(zhí)行追加進去的任務镜撩。
同步和異步是指派發(fā)任務的方式是否需要等待预柒,同步需要等待队塘,異步不需要等待。串行和并發(fā)是指已經(jīng)在隊列中的任務宜鸯,執(zhí)行時后面的任務的執(zhí)行是否需要等待憔古,串行是按順序執(zhí)行完后再執(zhí)行下一個,并發(fā)則可以不需要等待前面的任務執(zhí)行完時直接進行執(zhí)行淋袖。
主隊列是一個特殊情況鸿市,添加到主隊列中的任務必須要在主線程中執(zhí)行。因此在主線程中調(diào)用同步執(zhí)行主隊列會形成死鎖即碗,主線程需要把任務派發(fā)下去焰情,同步則會阻塞主線程,這樣執(zhí)行任務就需要等待派發(fā)完才能執(zhí)行剥懒,但是派發(fā)任務有需要執(zhí)行任務執(zhí)行完畢后才能派發(fā)内舟,因此派發(fā)任務和執(zhí)行任務出現(xiàn)了互相等待的情況,但是在其他線程中調(diào)用同步執(zhí)行主隊列則不會形成死鎖初橘,此時派發(fā)任務在子線程中進行验游,執(zhí)行任務在主線程中執(zhí)行。異步主隊列仍然是在主線程中執(zhí)行保檐,但是異步不需要等待任務執(zhí)行完在追加新的任務耕蝉,但是任務追加到主隊列依照串行隊列的原則順序執(zhí)行,等待上一個執(zhí)行完后夜只,再執(zhí)行下一個垒在。
GCD線程間的通信
執(zhí)行一些耗時操作的時候,開辟子線程扔亥,執(zhí)行完畢后爪膊,回到主線程更新UI。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i<2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
dispatch_async(dispatch_get_main_queue(), ^{
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
});
});
3---<NSThread: 0x600001c9c9c0>{number = 3, name = (null)}
3---<NSThread: 0x600001c9c9c0>{number = 3, name = (null)}
2---<NSThread: 0x600001cf8ec0>{number = 1, name = main}
GCD的其他方法砸王。
GCD的柵欄方法:dispatch_barrier_async
我們有時需要異步執(zhí)行兩組操作推盛,而且第一組操作執(zhí)行完之后,才能開始執(zhí)行第二組操作谦铃。這樣我們就需要一個相當于柵欄一樣的一個方法將兩組異步執(zhí)行的操作組給分割起來耘成,當然這里的操作組里可以包含一個或多個任務。這就需要用到dispatch_barrier_async方法在兩個操作組間形成柵欄驹闰。
dispatch_barrier_async函數(shù)會等待前邊追加到并發(fā)隊列中的任務全部執(zhí)行完畢之后瘪菌,再將指定的任務追加到該異步隊列中。然后在dispatch_barrier_async函數(shù)追加的任務執(zhí)行完畢之后嘹朗,異步隊列才恢復為一般動作师妙,接著追加任務到該異步隊列并開始執(zhí)行。
假設有6個任務分為兩組屹培,需求是第一組的三個并發(fā)執(zhí)行默穴,第二組的三個等第一組的三個都執(zhí)行完畢后再并發(fā)執(zhí)行怔檩。
從執(zhí)行結果中可以看出,在執(zhí)行完柵欄前面的操作之后蓄诽,才執(zhí)行柵欄操作薛训,最后再執(zhí)行柵欄后邊的操作。
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
for (int i = 0; i<2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i<2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_barrier_sync(queue, ^{
for (int i = 0; i<2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"barrier---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i<2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"4---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_async(queue, ^{
for (int i = 0; i<2; i++) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"5---%@",[NSThread currentThread]); // 打印當前線程
}
});
2---<NSThread: 0x600001761f00>{number = 3, name = (null)}
1---<NSThread: 0x60000175b400>{number = 4, name = (null)}
2---<NSThread: 0x600001761f00>{number = 3, name = (null)}
1---<NSThread: 0x60000175b400>{number = 4, name = (null)}
barrier---<NSThread: 0x600001736880>{number = 1, name = main}
barrier---<NSThread: 0x600001736880>{number = 1, name = main}
4---<NSThread: 0x60000175b400>{number = 4, name = (null)}
5---<NSThread: 0x60000175c2c0>{number = 5, name = (null)}
4---<NSThread: 0x60000175b400>{number = 4, name = (null)}
5---<NSThread: 0x60000175c2c0>{number = 5, name = (null)}
GCD延時執(zhí)行方法:dispatch_after
在指定時間(例如3秒)之后執(zhí)行某個任務仑氛∫野#可以用 GCD 的dispatch_after函數(shù)來實現(xiàn)。
需要注意的是:dispatch_after函數(shù)并不是在指定時間之后才開始執(zhí)行處理锯岖,而是在指定時間之后將任務追加到主隊列中介袜。嚴格來說,這個時間并不是絕對準確的出吹,但想要大致延遲執(zhí)行任務米酬,dispatch_after函數(shù)是很有效的。
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// 2.0秒后異步追加任務代碼到主隊列趋箩,并開始執(zhí)行
NSLog(@"after---%@",[NSThread currentThread]); // 打印當前線程
});
GCD一次性代碼:dispatch_once
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
// 只執(zhí)行1次的代碼(這里面默認是線程安全的)
});
GCD單例
+ (DJSingleton *)shareInstance{
static DJSingleton * s_instance_dj_singleton = nil ;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if (s_instance_dj_manager == nil) {
s_instance_dj_manager = [[DJSingleton alloc] init];
}
});
return (DJSingleton *)s_instance_dj_singleton;
}
GCD快速迭代方法:dispatch_apply
通常我們會用 for 循環(huán)遍歷赃额,但是 GCD 給我們提供了快速迭代的函數(shù)dispatch_apply。dispatch_apply按照指定的次數(shù)將指定的任務追加到指定的隊列中叫确,并等待全部隊列執(zhí)行結束跳芳。
如果是在串行隊列中使用 dispatch_apply,那么就和 for 循環(huán)一樣竹勉,按順序同步執(zhí)行飞盆。可這樣就體現(xiàn)不出快速迭代的意義了次乓。
我們可以利用并發(fā)隊列進行異步執(zhí)行吓歇。比如說遍歷 0~5 這6個數(shù)字,for 循環(huán)的做法是每次取出一個元素票腰,逐個遍歷城看。dispatch_apply 可以 在多個線程中同時(異步)遍歷多個數(shù)字。
還有一點杏慰,無論是在串行隊列测柠,還是并發(fā)隊列中,dispatch_apply 都會等待全部任務執(zhí)行完畢缘滥,這點就像是同步操作轰胁,也像是隊列組中的 dispatch_group_wait方法。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSLog(@"apply---begin");
dispatch_apply(6, queue, ^(size_t index) {
NSLog(@"%zd---%@",index, [NSThread currentThread]);
});
NSLog(@"apply---end");
apply---begin
2---<NSThread: 0x600002f2a880>{number = 1, name = main}
5---<NSThread: 0x600002f45a80>{number = 6, name = (null)}
1---<NSThread: 0x600002f502c0>{number = 4, name = (null)}
3---<NSThread: 0x600002f4a8c0>{number = 5, name = (null)}
0---<NSThread: 0x600002f23500>{number = 3, name = (null)}
4---<NSThread: 0x600002f6c140>{number = 7, name = (null)}
apply---end
GCD 隊列組:dispatch_group
分別異步執(zhí)行2個耗時任務朝扼,然后當2個耗時任務都執(zhí)行完畢后再回到主線程執(zhí)行任務赃阀。這時候我們可以用到 GCD 的隊列組。
- 調(diào)用隊列組的 dispatch_group_async 先把任務放到隊列中擎颖,然后將隊列放入隊列組中榛斯」塾危或者使用隊列組的 dispatch_group_enter、dispatch_group_leave 組合 來實現(xiàn)dispatch_group_async肖抱。
- 調(diào)用隊列組的 dispatch_group_notify 回到指定線程執(zhí)行任務备典∫炀桑或者使用 dispatch_group_wait 回到當前線程繼續(xù)向下執(zhí)行(會阻塞當前線程)意述。
dispatch_group_notify
/**
* 隊列組 dispatch_group_notify
*/
- (void)groupNotify {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步任務1、任務2都執(zhí)行完畢后吮蛹,回到主線程執(zhí)行下邊任務
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
NSLog(@"group---end");
});
}
dispatch_group_wait
會阻塞當前線程
/**
* 隊列組 dispatch_group_wait
*/
- (void)groupWait {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
});
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
});
// 等待上面的任務全部完成后荤崇,會往下繼續(xù)執(zhí)行(會阻塞當前線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"group---end");
}
dispatch_group_enter、dispatch_group_leave
/**
* 隊列組 dispatch_group_enter潮针、dispatch_group_leave
*/
- (void)groupEnterAndLeave
{
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"group---begin");
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任務1
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
}
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_async(queue, ^{
// 追加任務2
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"2---%@",[NSThread currentThread]); // 打印當前線程
}
dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// 等前面的異步操作都執(zhí)行完畢后术荤,回到主線程.
for (int i = 0; i < 2; ++i) {
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"3---%@",[NSThread currentThread]); // 打印當前線程
}
NSLog(@"group---end");
});
}
GCD 信號量:dispatch_semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計數(shù)的信號每篷。類似于過高速路收費站的欄桿瓣戚。可以通過時焦读,打開欄桿子库,不可以通過時,關閉欄桿矗晃。在 Dispatch Semaphore 中仑嗅,使用計數(shù)來完成這個功能,計數(shù)小于 0 時等待张症,不可通過仓技。計數(shù)為 0 或大于 0 時,計數(shù)減 1 且不等待俗他,可通過脖捻。
Dispatch Semaphore 提供了三個函數(shù)
- dispatch_semaphore_create:創(chuàng)建一個 Semaphore 并初始化信號的總量
- dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加 1
- dispatch_semaphore_wait:可以使總信號量減 1兆衅,信號總量小于 0 時就會一直等待(阻塞所在線程)郭变,否則就可以正常執(zhí)行。
Dispatch Semaphore 在實際開發(fā)中主要用于:
- 保持線程同步涯保,將異步執(zhí)行任務轉(zhuǎn)換為同步執(zhí)行任務
- 保證線程安全诉濒,為線程加鎖
/**
* semaphore 線程同步
*/
- (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"semaphore---begin");
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block int number = 0;
dispatch_async(queue, ^{
// 追加任務1
[NSThread sleepForTimeInterval:2]; // 模擬耗時操作
NSLog(@"1---%@",[NSThread currentThread]); // 打印當前線程
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@"semaphore---end,number = %zd",number);
}
- semaphore 初始創(chuàng)建時計數(shù)為 0。
- 異步執(zhí)行將任務 1 追加到隊列之后夕春,不做等待未荒,接著執(zhí)行dispatch_semaphore_wait方法,semaphore 減 1及志,此時 semaphore == -1片排,當前線程進入等待狀態(tài)寨腔。
- 然后,異步任務 1 開始執(zhí)行率寡。任務1執(zhí)行到dispatch_semaphore_signal之后迫卢,總信號量加1,此時 semaphore == 0冶共,正在被阻塞的線程(主線程)恢復繼續(xù)執(zhí)行乾蛤。
- 最后打印semaphore---end,number = 100。
這樣就實現(xiàn)了線程同步捅僵,將異步執(zhí)行任務轉(zhuǎn)換為同步執(zhí)行任務
/**
* 線程安全:使用 semaphore 加鎖
* 初始化火車票數(shù)量家卖、賣票窗口(線程安全)、并開始賣票
*/
- (void)initTicketStatusSave {
NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
NSLog(@"semaphore---begin");
semaphoreLock = dispatch_semaphore_create(1);
self.ticketSurplusCount = 50;
// queue1 代表北京火車票售賣窗口
dispatch_queue_t queue1 = dispatch_queue_create("net.bujige.testQueue1", DISPATCH_QUEUE_SERIAL);
// queue2 代表上好沓火車票售賣窗口
dispatch_queue_t queue2 = dispatch_queue_create("net.bujige.testQueue2", DISPATCH_QUEUE_SERIAL);
__weak typeof(self) weakSelf = self;
dispatch_async(queue1, ^{
[weakSelf saleTicketSafe];
});
dispatch_async(queue2, ^{
[weakSelf saleTicketSafe];
});
}
/**
* 售賣火車票(線程安全)
*/
- (void)saleTicketSafe {
while (1) {
// 相當于加鎖
dispatch_semaphore_wait(semaphoreLock, DISPATCH_TIME_FOREVER);
if (self.ticketSurplusCount > 0) { //如果還有票上荡,繼續(xù)售賣
self.ticketSurplusCount--;
NSLog(@"%@", [NSString stringWithFormat:@"剩余票數(shù):%d 窗口:%@", self.ticketSurplusCount, [NSThread currentThread]]);
[NSThread sleepForTimeInterval:0.2];
} else { //如果已賣完,關閉售票窗口
NSLog(@"所有火車票均已售完");
// 相當于解鎖
dispatch_semaphore_signal(semaphoreLock);
break;
}
// 相當于解鎖
dispatch_semaphore_signal(semaphoreLock);
}
}
通過信號量的控制馒闷,保證兩個售票窗口不會同時修改火車票數(shù)量酪捡,從而實現(xiàn)線程安全。