更新文章:
死鎖 一般都是由于產(chǎn)生了資源競爭瓶盛,而 GCD 死鎖 的充分條件是:
在串行隊列的任務(wù)執(zhí)行過程中,再次向它派發(fā)同步任務(wù)。
當(dāng)我們同步的提交一個任務(wù)時朦蕴,首先會阻塞當(dāng)前隊列指厌,然后等到下一次 runloop 時再在合適的線程中執(zhí)行 block刊愚。
下面的代碼:
dispatch_sync(dispatch_get_main_queue(), ^{});
主隊列環(huán)境下等同于:
//這里是sync或async都不影響結(jié)果
dispatch_sync(dispatch_get_main_queue(), ^{//任務(wù)1
//任務(wù)1的前半部分
dispatch_sync(dispatch_get_main_queue(), ^{//任務(wù)2
});
//任務(wù)1的下半部分
});
即串行隊列的任務(wù)1執(zhí)行過程中,向它派發(fā)了一個同步任務(wù)2踩验,導(dǎo)致兩個任務(wù)產(chǎn)生競爭鸥诽。
隊列 和 線程 之間并沒有 擁有關(guān)系(ownership);
主隊列環(huán)境=>主線程,主線程環(huán)境≠>主隊列
一箕憾、基礎(chǔ)概念
GCD
GCD 是 libdispatch 的市場名稱牡借,而 libdispatch 作為 Apple 的一個庫,為并發(fā)代碼在多核硬件(跑 iOS 或 OS X )上執(zhí)行提供有力支持袭异。它具有以下優(yōu)點:
1.GCD 能通過推遲昂貴計算任務(wù)并在后臺運行它們來改善你的應(yīng)用的響應(yīng)性能钠龙。
2.GCD 提供一個易于使用的并發(fā)模型而不僅僅只是鎖和線程,以幫助我們避開并發(fā)陷阱御铃。
3.GCD 具有在常見模式(例如單例)上用更高性能的原語優(yōu)化你的代碼的潛在能力碴里。
Serial vs. Concurrent 串行 vs. 并發(fā)
//串行
DISPATCH_QUEUE_SERIAL
//并發(fā)
DISPATCH_QUEUE_CONCURRENT
Synchronous vs. Asynchronous 同步 vs. 異步
//同步執(zhí)行
dispatch_sync(..., ^(block))
//異步執(zhí)行
dispatch_async(..., ^(block))
Critical Section 臨界區(qū)
就是一段代碼不能被并發(fā)執(zhí)行,也就是上真,兩個線程不能同時執(zhí)行這段代碼咬腋。這很常見,因為代碼去操作一個共享資源谷羞,例如一個變量若能被并發(fā)進(jìn)程訪問帝火,那么它很可能會變質(zhì)(譯者注:它的值不再可信)。
Race Condition 競態(tài)條件
這種狀況是指基于特定序列或時機(jī)的事件的軟件系統(tǒng)以不受控制的方式運行的行為湃缎,例如程序的并發(fā)任務(wù)執(zhí)行的確切順序犀填。競態(tài)條件可導(dǎo)致無法預(yù)測的行為,而不能通過代碼檢查立即發(fā)現(xiàn)嗓违。
Deadlock 死鎖
兩個(有時更多)東西——在大多數(shù)情況下九巡,是線程——所謂的死鎖是指它們都卡住了,并等待對方完成或執(zhí)行其它操作蹂季。第一個不能完成是因為它在等待第二個的完成冕广。但第二個也不能完成,因為它在等待第一個的完成偿洁。
Thread Safe 線程安全
線程安全的代碼能在多線程或并發(fā)任務(wù)中被安全的調(diào)用撒汉,而不會導(dǎo)致任何問題(數(shù)據(jù)損壞,崩潰涕滋,等)睬辐。線程不安全的代碼在某個時刻只能在一個上下文中運行。一個線程安全代碼的例子是 NSDictionary 。你可以在同一時間在多個線程中使用它而不會有問題溯饵。另一方面侵俗,NSMutableDictionary 就不是線程安全的,應(yīng)該保證一次只能有一個線程訪問它丰刊。
Context Switch 上下文切換
一個上下文切換指當(dāng)你在單個進(jìn)程里切換執(zhí)行不同的線程時存儲與恢復(fù)執(zhí)行狀態(tài)的過程隘谣。這個過程在編寫多任務(wù)應(yīng)用時很普遍,但會帶來一些額外的開銷啄巧。
Concurrency vs Parallelism 并發(fā)與并行
并發(fā)代碼的不同部分可以“同步”執(zhí)行寻歧。然而,該怎樣發(fā)生或是否發(fā)生都取決于系統(tǒng)棵帽。多核設(shè)備通過并行來同時執(zhí)行多個線程熄求;然而渣玲,為了使單核設(shè)備也能實現(xiàn)這一點逗概,它們必須先運行一個線程,執(zhí)行一個上下文切換忘衍,然后運行另一個線程或進(jìn)程逾苫。這通常發(fā)生地足夠快以致給我們并發(fā)執(zhí)行地錯覺。雖然你可以編寫代碼在 GCD 下并發(fā)執(zhí)行枚钓,但 GCD 會決定有多少并行的需求铅搓。并行要求并發(fā),但并發(fā)并不能保證并行搀捷。更深入的觀點是并發(fā)實際上是關(guān)于構(gòu)造星掰。當(dāng)你在腦海中用 GCD 編寫代碼,你組織你的代碼來暴露能同時運行的多個工作片段嫩舟,以及不能同時運行的那些氢烘。如果你想深入此主題,看看 this excellent talk by Rob Pike 家厌。
二播玖、dispatch各知識點與應(yīng)用場景
先上dispatch頭文件分布圖:
① dispatch block
dispatch_block_t是GCD隊列專用block類型,沒有參數(shù)饭于,沒有返回值蜀踏。
簡單定義無參數(shù)回調(diào)函數(shù):
@property (nonatomic,copy) dispatch_block_t blockAction;
使用方式和一般的無參數(shù)回調(diào)函數(shù)相同
② dispatch IO與dispatch data
文件處理接觸較少,mark:
星光社的戴銘--細(xì)說GCD(Grand Central Dispatch)如何用
③ dispatch queue
GCD 提供有 dispatch queues 來處理代碼塊掰吕,這些隊列管理你提供給 GCD 的任務(wù)并用 FIFO 順序執(zhí)行這些任務(wù)果覆。這就保證了第一個被添加到隊列里的任務(wù)會是隊列中第一個開始的任務(wù),而第二個被添加的任務(wù)將第二個開始殖熟,如此直到隊列的終點局待。
所有的調(diào)度隊列(dispatch queues)自身都是線程安全的,你能從多個線程并行的訪問它們。 GCD 的優(yōu)點是顯而易見的燎猛,即當(dāng)你了解了調(diào)度隊列如何為你自己代碼的不同部分提供線程安全恋捆。關(guān)于這一點的關(guān)鍵是選擇正確類型的調(diào)度隊列和正確的調(diào)度函數(shù)來提交你的工作。
Serial Queues 串行隊列 與 Concurrent Queues 并發(fā)隊列
//串行隊列
dispatch_queue_create("com.starming.serialqueue", DISPATCH_QUEUE_SERIAL)
//并行隊列
dispatch_queue_create("com.starming.concurrentqueue", DISPATCH_QUEUE_CONCURRENT)
Queue Types 隊列類型
- dispatch_get_main_queue
首先重绷,系統(tǒng)提供給你一個叫做主隊列的特殊隊列沸停。和其它串行隊列一樣,這個隊列中的任務(wù)一次只能執(zhí)行一個昭卓。然而愤钾,它能保證所有的任務(wù)都在主線程執(zhí)行,而主線程是唯一可用于更新 UI 的線程候醒。這個隊列就是用于發(fā)生消息給 UIView 或發(fā)送通知的能颁。
dispatch_queue_t mainQueue = dispatch_get_main_queue();
- dispatch_get_global_queue
系統(tǒng)同時提供給你好幾個并發(fā)隊列。它們叫做全局調(diào)度隊列 倒淫。目前的四個全局隊列有著不同的優(yōu)先級:** DISPATCH_QUEUE_PRIORITY_BACKGROUND伙菊、 DISPATCH_QUEUE_PRIORITY_LOW、 DISPATCH_QUEUE_PRIORITY_DEFAULT** 以及 ** DISPATCH_QUEUE_PRIORITY_HIGH**敌土。要知道镜硕,Apple 的 API 也會使用這些隊列,所以你添加的任何任務(wù)都不會是這些隊列中唯一的任務(wù)返干。
// DISPATCH_QUEUE_PRIORITY_HIGH(QOS_CLASS_USER_INTERACTIVE)
// DISPATCH_QUEUE_PRIORITY_DEFAULT(QOS_CLASS_USER_INITIATED)
// DISPATCH_QUEUE_PRIORITY_LOW(QOS_CLASS_UTILITY)
// DISPATCH_QUEUE_PRIORITY_BACKGROUND(QOS_CLASS_BACKGROUND)
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
QOS_CLASS_USER_INTERACTIVE:user interactive等級表示任務(wù)需要被立即執(zhí)行提供好的體驗兴枯,用來更新UI,響應(yīng)事件等矩欠。這個等級最好保持小規(guī)模财剖。
QOS_CLASS_USER_INITIATED:user initiated等級表示任務(wù)由UI發(fā)起異步執(zhí)行。適用場景是需要及時結(jié)果同時又可以繼續(xù)交互的時候癌淮。
QOS_CLASS_UTILITY:utility等級表示需要長時間運行的任務(wù)躺坟,伴有用戶可見進(jìn)度指示器。經(jīng)常會用來做計算该默,I/O瞳氓,網(wǎng)絡(luò),持續(xù)的數(shù)據(jù)填充等任務(wù)栓袖。這個任務(wù)節(jié)能匣摘。
QOS_CLASS_BACKGROUND:background等級表示用戶不會察覺的任務(wù),使用它來處理預(yù)加載裹刮,或者不需要用戶交互和對時間不敏感的任務(wù)音榜。
- 自定義隊列
最后,你也可以創(chuàng)建自己的串行隊列或并發(fā)隊列捧弃。
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_SERIAL);
也可以設(shè)置自定義隊列的優(yōu)先級:
//dipatch_queue_attr_make_with_qos_class IOS8.0之后開放
dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", attr);
//更多時候使用dispatch_set_target_queue
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_SERIAL); //需要設(shè)置優(yōu)先級的queue
dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //參考優(yōu)先級
dispatch_set_target_queue(queue, referQueue); //設(shè)置queue的優(yōu)先級和referQueue的一樣
dispatch_set_target_queue也可以設(shè)置隊列層級體系赠叼,設(shè)置dispatch_set_target_queue(queue, referQueue); 也相當(dāng)于把queue的任務(wù)對象指派到referQueue中執(zhí)行擦囊。比如說將多個串行的queue指定到了同一目標(biāo),那么著多個串行queue在目標(biāo)queue上就是同步執(zhí)行的嘴办,不再是并行執(zhí)行瞬场。這部分知識以后用到再進(jìn)行擴(kuò)展
dispatch_after
//延遲3秒
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
// do you something
});
dispatch_after 工作起來就像一個延遲版的 dispatch_async 。你不能控制實際的執(zhí)行時間涧郊,且一旦 dispatch_after 返回也就不能再取消它贯被。
dispatch_after最佳使用環(huán)境:主隊列(串行)。
dispatch_barrier
通過下面這個圖了解一下dispatch_barrier障礙函數(shù)
注意到正常部分的操作就如同一個正常的并發(fā)隊列妆艘。但當(dāng)障礙函數(shù)執(zhí)行時彤灶,它是唯一在執(zhí)行的事件。在障礙完成后批旺,隊列回到一個正常并發(fā)隊列的樣子幌陕。
//這個例子中3和4會等待1和2執(zhí)行完成后再執(zhí)行
dispatch_queue_t queue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
NSLog(@"-------------------1");
});
dispatch_async(queue, ^{
NSLog(@"-------------------2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"I am a barrier");
});
dispatch_async(queue, ^{
NSLog(@"-------------------3");
});
dispatch_async(queue, ^{
NSLog(@"-------------------4");
});
dispatch_barrier有dispatch_barrier_async和dispatch_barrier_sync兩種,用法與dispatch_async和dispatch_sync一樣汽煮,顧名思義sync會等待block的內(nèi)容執(zhí)行完再繼續(xù)往下執(zhí)行搏熄,而async則不會等待。
dispatch_after最佳使用環(huán)境:自定義隊列(并發(fā))逗物。
dispatch_apply
一句話闡述:并發(fā)地發(fā)起for循環(huán)的迭代搬卒,提高循環(huán)運行效率。
不過這個函數(shù)本身是同步的翎卓,會等待結(jié)構(gòu)體里的內(nèi)容執(zhí)行完再返回。
dispatch_queue_t concurrentQueue = dispatch_queue_create("user.create.queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, concurrentQueue, ^(size_t i) {
NSLog(@"%zu",i);
});
NSLog(@"The end"); //這里有個需要注意的是摆寄,dispatch_apply這個是會阻塞主線程的失暴。這個log打印會在dispatch_apply都結(jié)束后才開始執(zhí)行
dispatch_after最佳使用環(huán)境:并發(fā)隊列。
④ dispatch groups
即調(diào)度組微饥《喊牵可以對任務(wù)進(jìn)行監(jiān)聽,這些任務(wù)可以是同步的欠橘,也可以是異步的矩肩,即便在不同的隊列也行。而且在整個組的任務(wù)都完成時肃续,Dispatch Group 可以用同步的或者異步的方式通知你黍檩。如果要監(jiān)控的任務(wù)在不同隊列,那就用一個 dispatch_group_t 的實例來記下這些不同的任務(wù)始锚。
以下代碼介紹dispatch_group_t的創(chuàng)建與執(zhí)行方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//創(chuàng)建dispatch_group_t方法
dispatch_group_t downloadGroup = dispatch_group_create();
//代表group一個任務(wù)執(zhí)行過程
dispatch_group_enter(downloadGroup);
NSLog(@"-------------------");
dispatch_group_leave(downloadGroup);
//等價于dispatch_group_enter+dispatch_group_leave
dispatch_group_async(group, queue, ^{
//do something
NSLog(@"-------------------");
});
當(dāng)組中所有的事件都完成時刽酱,GCD 的 API 以下提供了兩種通知方式颜凯。
- dispatch_group_wait
//異步調(diào)用全局調(diào)度隊列
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ // 1
//此時group在全局調(diào)度隊列中
dispatch_group_t downloadGroup = dispatch_group_create(); // 2
for (NSInteger i = 0; i < 3; i++) {
dispatch_group_enter(downloadGroup); // 3
self.block = ^(){
NSLog(@"-------------------");
dispatch_group_leave(downloadGroup); // 4
};
}
dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); // 5
dispatch_async(dispatch_get_main_queue(), ^{ // 6
NSLog(@"-------------------");
});
});
- dispatch_group_notify
//此時group在主隊列(串行)中
dispatch_group_t downloadGroup = dispatch_group_create(); // 1
for (NSInteger i = 0; i < 3; i++) {
dispatch_group_enter(downloadGroup); // 2
self.block = ^(){
NSLog(@"-------------------");
dispatch_group_leave(downloadGroup); // 3
};
}
dispatch_group_notify(downloadGroup, dispatch_get_main_queue(), ^{ // 4
NSLog(@"-------------------");
});
需要注意的是dispatch_group_wait會阻塞當(dāng)前線程夸赫,而dispatch_group_notify不會。
關(guān)于何時以及怎樣使用有著不同的隊列類型的 Dispatch Group :
- 自定義串行隊列:它很適合當(dāng)一組任務(wù)完成時發(fā)出通知禾进。
- 主隊列(串行):它也很適合這樣的情況。但如果你要同步地等待所有工作地完成殿怜,那你就不應(yīng)該使用它典蝌,因為你不能阻塞主線程。然而头谜,如果需要在幾個較長任務(wù)(例如網(wǎng)絡(luò)調(diào)用)完成后更新 UI 的話赠法,dispatch_group_notify是非常適合的方式。
- 并發(fā)隊列:它也很適合 Dispatch Group 和完成時通知乔夯。
⑤dispatch once
dispatch_once_t要是全局或static變量砖织,保證dispatch_once_t只有一份實例
+ (UIColor *)boringColor;
{
static UIColor *color;
//只運行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
color = [UIColor colorWithRed:0.380f green:0.376f blue:0.376f alpha:1.000f];
});
return color;
}
⑥dispatch semaphore
信號量是用來保證關(guān)鍵代碼段不被所設(shè)定次數(shù)以上并發(fā)調(diào)用的手段。在進(jìn)入一個關(guān)鍵代碼段之前末荐,線程必須獲取一個信號量侧纯,同時最多只能有設(shè)定次數(shù)個線程可以訪問臨界區(qū)。其他想使用資源的線程必須在一個FIFO隊列里等待甲脏。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *array = [NSMutableArrayarray];
for (int index = 0; index < 100000; index++) {
dispatch_async(queue, ^(){
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//
NSLog(@"addd :%d", index);
[array addObject:[NSNumber numberWithInt:index]];
dispatch_semaphore_signal(semaphore);
});
}
在執(zhí)行到wait方法的時候如果semaphore計數(shù)大于等于1.計數(shù)-1眶熬,返回,程序繼續(xù)運行块请。如果計數(shù)為0娜氏,則等待。這里設(shè)置的等待時間是一直等待墩新。dispatch_semaphore_signal(semaphore);計數(shù)+1.在這兩句代碼中間的執(zhí)行代碼贸弥,每次只會允許一個線程進(jìn)入,這樣就有效的保證了在多線程環(huán)境下海渊,只能有一個線程進(jìn)入绵疲。
參考鏈接
星光社的戴銘--細(xì)說GCD(Grand Central Dispatch)如何用
GCD 深入理解(二)
夏天然后--帶你系統(tǒng)學(xué)習(xí)GCD