先了解一些基本概念:
單核&多核:一個處理器(CPU)有幾個運算核心重绷,來區(qū)別是單核還是多核。
單核和多核的本質區(qū)別就是同一時刻可以運行幾個線程膜毁,單核只能運行一個昭卓,N核可以運行N個。那么問題來了瘟滨,單核CPU是如何多線程“同時”運行的候醒?為什么同時會用引號,因為本質上單核CPU運行多線程不是同時的杂瘸,同時只是一種假象倒淫,它的運行原理是時間片進行的,即操作系統(tǒng)分配時間片給ABCDE...線程败玉,在不同時間片內運行不同線程敌土,由于每個時間片很短所以看起來是多個線程同時進行的,這就是所謂的并發(fā)运翼。
進程&線程:進程是表示資源分配的的基本概念返干,又是調度運行的基本單位,是系統(tǒng)中的并發(fā)執(zhí)行的單位血淌。線程是CPU調度的基本單元矩欠。這樣表述簡直太抽象,簡單說悠夯,運行一個程序就會開啟一個進程癌淮,一個進程可以有多個線程,一個線程只能屬于一個進程疗疟。
并發(fā):擁有處理多個任務的能力该默,不一定要同時,不同代碼塊交替執(zhí)行的性能策彤,可以串行處理也可以并行處理
并行:同時處理多個任務的能力栓袖,不同代碼塊同時執(zhí)行的性能
串行:指多個任務時匣摘,各個任務按順序執(zhí)行,完成一個之后才能執(zhí)行下一個
同步:就是指一個進程在執(zhí)行某個請求的時候裹刮,若該請求需要一段時間才能返回信息音榜,那么這個進程將會一直等待下去,直到收到返回信息才繼續(xù)執(zhí)行下去(注意死鎖原因分析)捧弃;
異步:是指進程不需要一直等下去赠叼,而是繼續(xù)執(zhí)行下面的操作,不管其他進程的狀態(tài)违霞。當有消息返回時系統(tǒng)會通知進程進行處理嘴办,這樣可以提高執(zhí)行的效率。
比如:你叫我去吃飯买鸽,我聽到了就立刻和你去吃飯涧郊,如果我沒有聽到,你就會一直叫我眼五,直到我聽見和你一起去吃飯妆艘,這個過程叫同步;異步過程指你叫我去吃飯看幼,然后你就去吃飯了批旺,而不管我是否和你一起去吃飯。而我得到消息后可能立即就走诵姜,也可能過段時間再走汽煮。
異步、同步 & 并行棚唆、串行的特點
線程逗物、任務和隊列的概念
同步、異步和串行瑟俭、并行針對的對象是什么?
同步契邀、異步是對于線程而言的摆寄。同步,不開啟新線程坯门;異步會開啟新線程微饥。
串行、并行是針對隊列里面的任務來說的古戴。串行就是任務一個一個按順序做欠橘,并行就是多任務同時進行。
同步现恼、異步和串行肃续、并行組合的結果
簡要解釋:
異步并行隊列: 就是這個隊列的任務是并行執(zhí)行的黍檩,也就是每一個任務都會開辟一個新線程,這些任務同時執(zhí)行始锚。
異步串行隊列:重新開辟一個新線程刽酱,這個串行隊列里面的任務會在這個線程按順序一個一個執(zhí)行。
同步并行隊列(串行隊列):首先不會開辟新線程瞧捌,所以不管是串行隊列還是并行隊列棵里,隊列里面的任務都在當前線程執(zhí)行,所以所有任務按順序一個一個執(zhí)行姐呐。
關于死鎖的形成:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"=================1");
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"=================2");
});
NSLog(@"=================3");
}
看這段代碼就是一個死鎖殿怜,為什么會造成死鎖呢?
GCD Queue 分為三種:
1曙砂、The main queue :主隊列头谜,主線程就是在個隊列中。
2麦轰、Global queues : 全局并發(fā)隊列乔夯。
3、用戶隊列:是用函數 dispatch_queue_create 創(chuàng)建的自定義隊列
dispatch_sync 和 dispatch_async 區(qū)別:
dispatch_async(queue,block) async 異步隊列款侵,dispatch_async 函數會立即返回, block會在后臺異步執(zhí)行末荐。
dispatch_sync(queue,block) sync 同步隊列,dispatch_sync 函數不會立即返回新锈,及阻塞當前線程,等待 block同步執(zhí)行完成甲脏。
分析上面代碼:
viewDidLoad 在主線程中, 及在dispatch_get_main_queue() 中妹笆,執(zhí)行到sync 時向dispatch_get_main_queue()插入 同步 thread1.
sync 會等到 后面block 執(zhí)行完成才返回块请, sync又在dispatch_get_main_queue() 隊列中,它是串行隊列拳缠,sync 是后加入的符隙,前一個是主線程,所以 sync 想執(zhí)行 block 必須等待主線程執(zhí)行完成禁荒,主線程等待 sync 返回痊班,去執(zhí)行后續(xù)內容。
造成死鎖哲鸳,sync 等待mainThread 執(zhí)行完成臣疑, mianThread 等待sync 函數返回。
串行隊列中添加了block徙菠,block一直等到前面的任務處理完才會執(zhí)行讯沈,從而導致了死鎖。我這樣理解婿奔,mianThread這時候有兩個任務缺狠,一個是dispatch_sync问慎,另一個是block。dispatch_sync收到返回信息的時候這個任務才會結束儒老,但是dispatch_sync收到返回信息是在block執(zhí)行結束蝴乔,所以簡單說,就是sync這是一個在主線程中進行的任務驮樊,想要執(zhí)行block必須等主線程完成這個任務薇正,但是這個任務的完成必須要block執(zhí)行結束,然后就死鎖了囚衔。
了解了這些基本概念后挖腰,下面重點深入探討同步&異步、串行&并行的使用及場景练湿。
Critical Section 臨界區(qū)
就是一段代碼不能被并發(fā)執(zhí)行猴仑,也就是,兩個線程不能同時執(zhí)行這段代碼肥哎。這很常見辽俗,因為代碼去操作一個共享資源,例如一個變量若能被并發(fā)進程訪問篡诽,那么它很可能會變質(譯者注:它的值不再可信)崖飘。
Race Condition 競態(tài)條件
這種狀況是指基于特定序列或時機的事件的軟件系統(tǒng)以不受控制的方式運行的行為,例如程序的并發(fā)任務執(zhí)行的確切順序杈女。競態(tài)條件可導致無法預測的行為朱浴,而不能通過代碼檢查立即發(fā)現(xiàn)。
Deadlock 死鎖
兩個(有時更多)東西——在大多數情況下达椰,是線程——所謂的死鎖是指它們都卡住了翰蠢,并等待對方完成或執(zhí)行其它操作。第一個不能完成是因為它在等待第二個的完成啰劲。但第二個也不能完成梁沧,因為它在等待第一個的完成。
Thread Safe 線程安全
線程安全的代碼能在多線程或并發(fā)任務中被安全的調用蝇裤,而不會導致任何問題(數據損壞趁尼,崩潰,等)猖辫。線程不安全的代碼在某個時刻只能在一個上下文中運行。一個線程安全代碼的例子是 NSDictionary 砚殿。你可以在同一時間在多個線程中使用它而不會有問題啃憎。另一方面,NSMutableDictionary 就不是線程安全的似炎,應該保證一次只能有一個線程訪問它辛萍。
Context Switch 上下文切換
一個上下文切換指當你在單個進程里切換執(zhí)行不同的線程時存儲與恢復執(zhí)行狀態(tài)的過程悯姊。這個過程在編寫多任務應用時很普遍,但會帶來一些額外的開銷贩毕。
- 在加載控制器的時候悯许,一些比較費時的操作都異步執(zhí)行,這樣不會阻塞主線程push控制器辉阶。
下面是一個關于在 dispatch_async 上如何以及何時使用不同的隊列類型的快速指導:
- 自定義串行隊列:當你想串行執(zhí)行后臺任務并追蹤它時就是一個好選擇先壕。這消除了資源爭用,因為你知道一次只有一個任務在執(zhí)行谆甜。注意若你需要來自某個方法的數據垃僚,你必須內聯(lián)另一個 Block 來找回它或考慮使用 dispatch_sync。
- 主隊列(串行):這是在一個并發(fā)隊列上完成任務后更新 UI 的共同選擇规辱。要這樣做谆棺,你將在一個 Block 內部編寫另一個 Block 。以及罕袋,如果你在主隊列調用 dispatch_async 到主隊列改淑,你能確保這個新任務將在當前方法完成后的某個時間執(zhí)行。
- 并發(fā)隊列:這是在后臺執(zhí)行非 UI 工作的共同選擇浴讯。
不知道何時適合使用 dispatch_after 朵夏?
- 自定義串行隊列:在一個自定義串行隊列上使用 dispatch_after 要小心。你最好堅持使用主隊列兰珍。
- 主隊列(串行):是使用 dispatch_after 的好選擇侍郭;Xcode 提供了一個不錯的自動完成模版。
- 并發(fā)隊列:在并發(fā)隊列上使用 dispatch_after 也要小心掠河;你會這樣做就比較罕見亮元。還是在主隊列做這些操作吧。
dispatch_once()
以線程安全的方式執(zhí)行且僅執(zhí)行其代碼塊一次唠摹。試圖訪問臨界區(qū)(即傳遞給 dispatch_once 的代碼)的不同的線程會在臨界區(qū)已有一個線程的情況下被阻塞爆捞,直到臨界區(qū)完成為止。常用于單例勾拉。
Dispatch barriers
Dispatch barriers 是一組函數煮甥,在并發(fā)隊列上工作時扮演一個串行式的瓶頸。使用 GCD 的障礙(barrier)API 確保提交的 Block 在那個特定時間上是指定隊列上唯一被執(zhí)行的條目藕赞。這就意味著所有的先于調度障礙提交到隊列的條目必能在這個 Block 執(zhí)行前完成成肘。
當這個 Block 的時機到達,調度障礙執(zhí)行這個 Block 并確保在那個時間里隊列不會執(zhí)行任何其它 Block 斧蜕。一旦完成双霍,隊列就返回到它默認的實現(xiàn)狀態(tài)。 GCD 提供了同步和異步兩種障礙函數。
注意到正常部分的操作就如同一個正常的并發(fā)隊列洒闸。但當障礙執(zhí)行時染坯,它本質上就如同一個串行隊列。也就是丘逸,障礙是唯一在執(zhí)行的事物单鹿。在障礙完成后,隊列回到一個正常并發(fā)隊列的樣子深纲。
下面是你何時會——和不會——使用障礙函數的情況:
自定義串行隊列:一個很壞的選擇仲锄;障礙不會有任何幫助,因為不管怎樣囤萤,一個串行隊列一次都只執(zhí)行一個操作昼窗。
全局并發(fā)隊列:要小心;這可能不是最好的主意涛舍,因為其它系統(tǒng)可能在使用隊列而且你不能壟斷它們只為你自己的目的澄惊。
自定義并發(fā)隊列:這對于原子或臨界區(qū)代碼來說是極佳的選擇。任何你在設置或實例化的需要線程安全的事物都是使用障礙的最佳候選富雅。
dispatch_barrier_async和dispatch_barrier_sync
分析下面代碼掸驱,把dispatch_barrier_sync改成dispatch_barrier_async,看打印
dispatch_queue_t queue = dispatch_queue_create("thread", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(3);
NSLog(@"test1");
});
dispatch_async(queue, ^{
NSLog(@"test2");
});
dispatch_sync(queue, ^{
NSLog(@"test3");
});
dispatch_barrier_sync(queue, ^{ ///分界線在這里 請注意是同步的
sleep(1);
for (int i = 0; i<50; i++) {
if (i == 10 ) {
NSLog(@"point1");
}else if(i == 20){
NSLog(@"point2");
}else if(i == 40){
NSLog(@"point3");
}
}
});
NSLog(@"hello");
dispatch_async(queue, ^{
NSLog(@"test4");
});
NSLog(@"world");
dispatch_async(queue, ^{
NSLog(@"test5");
});
dispatch_async(queue, ^{
NSLog(@"test6");
});
dispatch_barrier_sync 打印結果:
dispatch_barrier_async 打印結果:
hello没佑、world位置的區(qū)別就是兩種方式的區(qū)別毕贼。
可以看出,不管那種方式queue隊列中任務執(zhí)行順序都是barrier前->barrier里->barrier后蛤奢。區(qū)別在于鬼癣,dispatch_barrier_sync阻礙了主線程任務的執(zhí)行,而dispatch_barrier_async則沒有阻礙啤贩。
下面是一個快速總覽待秃,關于在何時以及何處使用 dispatch_sync :
- 自定義串行隊列:在這個狀況下要非常小心!如果你正運行在一個隊列并調用 dispatch_sync 放在同一個隊列痹屹,那你就百分百地創(chuàng)建了一個死鎖章郁。
- 主隊列(串行):同上面的理由一樣,必須非常小心志衍!這個狀況同樣有潛在的導致死鎖的情況暖庄。
- 并發(fā)隊列:這才是做同步工作的好選擇,不論是通過調度障礙楼肪,或者需要等待一個任務完成才能執(zhí)行進一步處理的情況培廓。
Dispatch Groups(調度組)
Dispatch Group 會在整個組的任務都完成時通知你。這些任務可以是同步的春叫,也可以是異步的医舆,即便在不同的隊列也行俘侠。而且在整個組的任務都完成時,Dispatch Group 可以用同步的或者異步的方式通知你蔬将。因為要監(jiān)控的任務在不同隊列,那就用一個 dispatch_group_t 的實例來記下這些不同的任務央星。
- 第一種是 dispatch_group_wait 霞怀,它會阻塞當前線程,直到組里面所有的任務都完成或者等到某個超時發(fā)生莉给。
- 另一種是dispatch_group_notify毙石,有點就是不會阻塞當前線程。
使用簡介:
dispatch_group_t downloadGroup = dispatch_group_create();
dispatch_group_enter(downloadGroup);
dispatch_group_leave(downloadGroup);
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{
});
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{ // 6
});
dispatch_apply
表現(xiàn)得就像一個 for 循環(huán)颓遏,但它能并發(fā)地執(zhí)行不同的迭代徐矩。這個函數是同步的,所以和普通的 for 循環(huán)一樣叁幢,它只會在所有工作都完成后才會返回滤灯。
for循環(huán)是走完一次迭代后再走下一次迭代,按順序一次一次走完曼玩,dispatch_apply是像瀑布一樣一起執(zhí)行所有迭代鳞骤。
那么問題來了:為什么我們經常用for循環(huán),卻不用dispatch_apply黍判?
當在 Block 內計算任何給定數量的工作的最佳迭代數量時豫尽,必須要小心,因為過多的迭代和每個迭代只有少量的工作會導致大量開銷以致它能抵消任何因并發(fā)帶來的收益顷帖。而被稱為跨越式(striding)的技術可以在此幫到你美旧,即通過在每個迭代里多做幾個不同的工作。
注意:你創(chuàng)建并行運行線程而付出的開銷贬墩,很可能比直接使用 for 循環(huán)要多榴嗅。若每個迭代是非常大的集合(即每個迭代要操作很多東西),那才應該考慮使用 dispatch_apply震糖,否則可能得不償失录肯。