GCD日記

更新文章:
死鎖 一般都是由于產(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ā)進程訪問,那么它很可能會變質(zhì)(譯者注:它的值不再可信)。

Race Condition 競態(tài)條件

這種狀況是指基于特定序列或時機的事件的軟件系統(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)你在單個進程里切換執(zhí)行不同的線程時存儲與恢復(fù)執(zhí)行狀態(tài)的過程。這個過程在編寫多任務(wù)應(yīng)用時很普遍,但會帶來一些額外的開銷。

Concurrency vs Parallelism 并發(fā)與并行

并發(fā)代碼的不同部分可以“同步”執(zhí)行。然而,該怎樣發(fā)生或是否發(fā)生都取決于系統(tǒng)。多核設(shè)備通過并行來同時執(zhí)行多個線程;然而,為了使單核設(shè)備也能實現(xiàn)這一點,它們必須先運行一個線程,執(zhí)行一個上下文切換,然后運行另一個線程或進程。這通常發(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頭文件
① 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ī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í)行。這部分知識以后用到再進行擴展

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ù)進行監(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 :

  1. 自定義串行隊列:它很適合當(dāng)一組任務(wù)完成時發(fā)出通知。
  2. 主隊列(串行):它也很適合這樣的情況。但如果你要同步地等待所有工作地完成,那你就不應(yīng)該使用它,因為你不能阻塞主線程。然而,如果需要在幾個較長任務(wù)(例如網(wǎng)絡(luò)調(diào)用)完成后更新 UI 的話,dispatch_group_notify是非常適合的方式。
  3. 并發(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)用的手段。在進入一個關(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í)行代碼,每次只會允許一個線程進入,這樣就有效的保證了在多線程環(huán)境下,只能有一個線程進入。

參考鏈接

星光社的戴銘--細(xì)說GCD(Grand Central Dispatch)如何用
GCD 深入理解(二)
夏天然后--帶你系統(tǒng)學(xué)習(xí)GCD

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容