單核&多核,進程&線程,串行&并行,同步&異步

先了解一些基本概念:

單核&多核:一個處理器(CPU)有幾個運算核心,來區別是單核還是多核。
單核和多核的本質區別就是同一時刻可以運行幾個線程,單核只能運行一個,N核可以運行N個。那么問題來了,單核CPU是如何多線程“同時”運行的?為什么同時會用引號,因為本質上單核CPU運行多線程不是同時的,同時只是一種假象,它的運行原理是時間片進行的,即操作系統分配時間片給ABCDE...線程,在不同時間片內運行不同線程,由于每個時間片很短所以看起來是多個線程同時進行的,這就是所謂的并發。

進程&線程:進程是表示資源分配的的基本概念,又是調度運行的基本單位,是系統中的并發執行的單位。線程是CPU調度的基本單元。這樣表述簡直太抽象,簡單說,運行一個程序就會開啟一個進程,一個進程可以有多個線程,一個線程只能屬于一個進程。

并發:擁有處理多個任務的能力,不一定要同時,不同代碼塊交替執行的性能,可以串行處理也可以并行處理
并行:同時處理多個任務的能力,不同代碼塊同時執行的性能
串行:指多個任務時,各個任務按順序執行,完成一個之后才能執行下一個

同步:就是指一個進程在執行某個請求的時候,若該請求需要一段時間才能返回信息,那么這個進程將會一直等待下去,直到收到返回信息才繼續執行下去(注意死鎖原因分析);
異步:是指進程不需要一直等下去,而是繼續執行下面的操作,不管其他進程的狀態。當有消息返回時系統會通知進程進行處理,這樣可以提高執行的效率。
比如:你叫我去吃飯,我聽到了就立刻和你去吃飯,如果我沒有聽到,你就會一直叫我,直到我聽見和你一起去吃飯,這個過程叫同步;異步過程指你叫我去吃飯,然后你就去吃飯了,而不管我是否和你一起去吃飯。而我得到消息后可能立即就走,也可能過段時間再走。

異步、同步 & 并行、串行的特點

2.png

線程、任務和隊列的概念

1.png

同步、異步和串行、并行針對的對象是什么?
同步、異步是對于線程而言的。同步,不開啟新線程;異步會開啟新線程。
串行、并行是針對隊列里面的任務來說的。串行就是任務一個一個按順序做,并行就是多任務同時進行。

同步、異步和串行、并行組合的結果

3.png

簡要解釋:
異步并行隊列: 就是這個隊列的任務是并行執行的,也就是每一個任務都會開辟一個新線程,這些任務同時執行。
異步串行隊列:重新開辟一個新線程,這個串行隊列里面的任務會在這個線程按順序一個一個執行。
同步并行隊列(串行隊列):首先不會開辟新線程,所以不管是串行隊列還是并行隊列,隊列里面的任務都在當前線程執行,所以所有任務按順序一個一個執行。

關于死鎖的形成:

- (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 : 全局并發隊列。
3、用戶隊列:是用函數 dispatch_queue_create 創建的自定義隊列

dispatch_sync 和 dispatch_async 區別:
dispatch_async(queue,block) async 異步隊列,dispatch_async 函數會立即返回, block會在后臺異步執行。
dispatch_sync(queue,block) sync 同步隊列,dispatch_sync 函數不會立即返回,及阻塞當前線程,等待 block同步執行完成。

分析上面代碼:

viewDidLoad 在主線程中, 及在dispatch_get_main_queue() 中,執行到sync 時向dispatch_get_main_queue()插入 同步 thread1.

sync 會等到 后面block 執行完成才返回, sync又在dispatch_get_main_queue() 隊列中,它是串行隊列,sync 是后加入的,前一個是主線程,所以 sync 想執行 block 必須等待主線程執行完成,主線程等待 sync 返回,去執行后續內容。
造成死鎖,sync 等待mainThread 執行完成, mianThread 等待sync 函數返回。

串行隊列中添加了block,block一直等到前面的任務處理完才會執行,從而導致了死鎖。我這樣理解,mianThread這時候有兩個任務,一個是dispatch_sync,另一個是block。dispatch_sync收到返回信息的時候這個任務才會結束,但是dispatch_sync收到返回信息是在block執行結束,所以簡單說,就是sync這是一個在主線程中進行的任務,想要執行block必須等主線程完成這個任務,但是這個任務的完成必須要block執行結束,然后就死鎖了。

iOS GCD詳細介紹——點擊進入

了解了這些基本概念后,下面重點深入探討同步&異步、串行&并行的使用及場景。

Critical Section 臨界區
就是一段代碼不能被并發執行,也就是,兩個線程不能同時執行這段代碼。這很常見,因為代碼去操作一個共享資源,例如一個變量若能被并發進程訪問,那么它很可能會變質(譯者注:它的值不再可信)。

Race Condition 競態條件
這種狀況是指基于特定序列或時機的事件的軟件系統以不受控制的方式運行的行為,例如程序的并發任務執行的確切順序。競態條件可導致無法預測的行為,而不能通過代碼檢查立即發現。

Deadlock 死鎖
兩個(有時更多)東西——在大多數情況下,是線程——所謂的死鎖是指它們都卡住了,并等待對方完成或執行其它操作。第一個不能完成是因為它在等待第二個的完成。但第二個也不能完成,因為它在等待第一個的完成。

Thread Safe 線程安全
線程安全的代碼能在多線程或并發任務中被安全的調用,而不會導致任何問題(數據損壞,崩潰,等)。線程不安全的代碼在某個時刻只能在一個上下文中運行。一個線程安全代碼的例子是 NSDictionary 。你可以在同一時間在多個線程中使用它而不會有問題。另一方面,NSMutableDictionary 就不是線程安全的,應該保證一次只能有一個線程訪問它。

Context Switch 上下文切換
一個上下文切換指當你在單個進程里切換執行不同的線程時存儲與恢復執行狀態的過程。這個過程在編寫多任務應用時很普遍,但會帶來一些額外的開銷。

  • 在加載控制器的時候,一些比較費時的操作都異步執行,這樣不會阻塞主線程push控制器。

下面是一個關于在 dispatch_async 上如何以及何時使用不同的隊列類型的快速指導:

  1. 自定義串行隊列:當你想串行執行后臺任務并追蹤它時就是一個好選擇。這消除了資源爭用,因為你知道一次只有一個任務在執行。注意若你需要來自某個方法的數據,你必須內聯另一個 Block 來找回它或考慮使用 dispatch_sync。
  2. 主隊列(串行):這是在一個并發隊列上完成任務后更新 UI 的共同選擇。要這樣做,你將在一個 Block 內部編寫另一個 Block 。以及,如果你在主隊列調用 dispatch_async 到主隊列,你能確保這個新任務將在當前方法完成后的某個時間執行。
  3. 并發隊列:這是在后臺執行非 UI 工作的共同選擇。

不知道何時適合使用 dispatch_after ?

  1. 自定義串行隊列:在一個自定義串行隊列上使用 dispatch_after 要小心。你最好堅持使用主隊列。
  2. 主隊列(串行):是使用 dispatch_after 的好選擇;Xcode 提供了一個不錯的自動完成模版。
  3. 并發隊列:在并發隊列上使用 dispatch_after 也要小心;你會這樣做就比較罕見。還是在主隊列做這些操作吧。

dispatch_once()
以線程安全的方式執行且僅執行其代碼塊一次。試圖訪問臨界區(即傳遞給 dispatch_once 的代碼)的不同的線程會在臨界區已有一個線程的情況下被阻塞,直到臨界區完成為止。常用于單例。

Dispatch barriers
Dispatch barriers 是一組函數,在并發隊列上工作時扮演一個串行式的瓶頸。使用 GCD 的障礙(barrier)API 確保提交的 Block 在那個特定時間上是指定隊列上唯一被執行的條目。這就意味著所有的先于調度障礙提交到隊列的條目必能在這個 Block 執行前完成。

當這個 Block 的時機到達,調度障礙執行這個 Block 并確保在那個時間里隊列不會執行任何其它 Block 。一旦完成,隊列就返回到它默認的實現狀態。 GCD 提供了同步和異步兩種障礙函數。

注意到正常部分的操作就如同一個正常的并發隊列。但當障礙執行時,它本質上就如同一個串行隊列。也就是,障礙是唯一在執行的事物。在障礙完成后,隊列回到一個正常并發隊列的樣子。

下面是你何時會——和不會——使用障礙函數的情況:

  1. 自定義串行隊列:一個很壞的選擇;障礙不會有任何幫助,因為不管怎樣,一個串行隊列一次都只執行一個操作。

  2. 全局并發隊列:要小心;這可能不是最好的主意,因為其它系統可能在使用隊列而且你不能壟斷它們只為你自己的目的。

  3. 自定義并發隊列:這對于原子或臨界區代碼來說是極佳的選擇。任何你在設置或實例化的需要線程安全的事物都是使用障礙的最佳候選。

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_sync

dispatch_barrier_async 打印結果:


dispatch_barrier_async

hello、world位置的區別就是兩種方式的區別。

可以看出,不管那種方式queue隊列中任務執行順序都是barrier前->barrier里->barrier后。區別在于,dispatch_barrier_sync阻礙了主線程任務的執行,而dispatch_barrier_async則沒有阻礙。

下面是一個快速總覽,關于在何時以及何處使用 dispatch_sync :

  1. 自定義串行隊列:在這個狀況下要非常小心!如果你正運行在一個隊列并調用 dispatch_sync 放在同一個隊列,那你就百分百地創建了一個死鎖。
  2. 主隊列(串行):同上面的理由一樣,必須非常小心!這個狀況同樣有潛在的導致死鎖的情況。
  3. 并發隊列:這才是做同步工作的好選擇,不論是通過調度障礙,或者需要等待一個任務完成才能執行進一步處理的情況。

Dispatch Groups(調度組)
Dispatch Group 會在整個組的任務都完成時通知你。這些任務可以是同步的,也可以是異步的,即便在不同的隊列也行。而且在整個組的任務都完成時,Dispatch Group 可以用同步的或者異步的方式通知你。因為要監控的任務在不同隊列,那就用一個 dispatch_group_t 的實例來記下這些不同的任務。

  • 第一種是 dispatch_group_wait ,它會阻塞當前線程,直到組里面所有的任務都完成或者等到某個超時發生。
  • 另一種是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
表現得就像一個 for 循環,但它能并發地執行不同的迭代。這個函數是同步的,所以和普通的 for 循環一樣,它只會在所有工作都完成后才會返回。

for循環是走完一次迭代后再走下一次迭代,按順序一次一次走完,dispatch_apply是像瀑布一樣一起執行所有迭代。

那么問題來了:為什么我們經常用for循環,卻不用dispatch_apply?
當在 Block 內計算任何給定數量的工作的最佳迭代數量時,必須要小心,因為過多的迭代和每個迭代只有少量的工作會導致大量開銷以致它能抵消任何因并發帶來的收益。而被稱為跨越式(striding)的技術可以在此幫到你,即通過在每個迭代里多做幾個不同的工作。

注意:你創建并行運行線程而付出的開銷,很可能比直接使用 for 循環要多。若每個迭代是非常大的集合(即每個迭代要操作很多東西),那才應該考慮使用 dispatch_apply,否則可能得不償失。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容