iOS多執行緒之GCD詳解

_執念發表於2017-12-26

什麼是GCD

什麼是GCD?下面是蘋果的官方說明。

Grand Central Dispatch (GCD) 是非同步執行任務的技術之一。一般將應用程式中記述的執行緒管理用的程式碼在系統級中實現。開發者只需要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的執行緒並計劃執行任務。也就是說GCD用我們難以置信的非常簡潔的計述方法,實現了極為複雜繁瑣的多執行緒程式設計。

多執行緒程式設計

一個CPU一次只能執行一個命令,不能執行某處分開的並列的兩個命令,因此通過CPU執行的CPU命令列就好比一條無分叉的大道,其執行不會出現分歧。 這裡所說的 " 一個CPU執行的CPU命令列為一條無分叉的路徑" 即為 "執行緒"。

現在一個物理的CPU晶片實際上有64個(64核)CPU,如果一個CPU核虛擬為兩個CPU核工作,那麼一臺計算機上使用多個CPU核就是理所當然的事了,儘管如此 " 一個CPU執行的CPU命令列為一條無分叉的路徑" 仍然 不變。這種無分叉的路徑不止有一條,存在有多條時即為 "多執行緒"

由於使用多執行緒的程式可以在摸個執行緒和其他執行緒之間反覆多次進行上下文切換,因此看上去好像1個CPU核能夠並列的執行多個執行緒一樣,而且在具有多個CPU核的情況下,就不是 " 看上去像" 了,而是真的提供了多個CPU核並行執行多個執行緒的技術。

這種利用多執行緒程式設計的技術就被稱為"多執行緒程式設計"。

GCD相關API

1. Dispatch Queue

開發者要做的只是定義想執行的任務並追加到適當的Dispatch Queue中。

   dispatch_async(queue, ^{

       //想要執行的任務
   });
複製程式碼

該程式碼使用Block語法 “定義想執行的任務” ,通過dispatch_async函式 “追加” 賦值在變數queue的Dispatch Queue中,僅這樣就可使指定的Block在另一個執行緒執行。 Dispatch Queue是什麼?如其名稱所示,是執行處理的等待佇列。應用程式程式設計人員通過dispatch_async函式等API,在Block語法中計述想執行的處理並追加到Dispatch Queue中。Dispatch Queue按追加順序(先進先出FIFO,First-In-First-Out)執行處理。

通過Dispatch Queue執行處理.png

執行處理時存在兩種Dispatch Queue,一種是等待現在執行中處理的Serial Dispatch Queue,另一種是不等待現在執行中處理的Concurrent Dispatch Queue

Dispatch Queue種類 說明
Serial Dispatch Queue (序列) 等待現在執行中處理結束
Concurrent Dispatch Queue (並行) 不等待現在執行中處理結束

?下面看這個例子:

    dispatch_async(queue, ^{NSLog(@"block0");});
    dispatch_async(queue, ^{NSLog(@"block1");});
    dispatch_async(queue, ^{NSLog(@"block2");});
    dispatch_async(queue, ^{NSLog(@"block3");});
    dispatch_async(queue, ^{NSLog(@"block4");});
    dispatch_async(queue, ^{NSLog(@"block5");});
    dispatch_async(queue, ^{NSLog(@"block6");});
    dispatch_async(queue, ^{NSLog(@"block7");});
複製程式碼

1.當變數queueSerial Dispatch Queue時,因為要等待現在執行中的處理結束。首先執行block0,block0執行結束後,接著執行block1,block1執行結束後在執行block2,如此重複,同時執行的處理數只有1個。

2.當變數queueConcurrent Dispatch Queue時,因為不用等待現在執行中的處理結束。所以首先執行block0,不管block0的執行是否結束,都開始執行後面的block1,不管block1的執行是否結束,都開始執行後面的block2,如此重複迴圈。

如何建立Dispatch Queue,方法有兩種。

2. dispatch_queue_create

第一種方法是通過 GCD 的API生成Dispatch Queue,通過dispatch_queue_create函式可以生成Dispatch Queue

    /**
     建立 dispatch_queue
     第一個引數: 執行緒名稱,推薦使用應用程式ID這種逆序全程域名,也可以設定為`NULL`
     第二個引數: `SerialDispatchQueue`時設定為`DISPATCH_QUEUE_SERIAL` or `NULL`
                `ConcurrentDispatchQueue`時設定為`DISPATCH_QUEUE_CONCURRENT`
     */
    dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("caoxueliang.MultiThreadStudy.mySerialDispatchQueue", NULL);
    dispatch_queue_t myConcurrentDispatchQueue = dispatch_queue_create("caoxueliang.MultiThreadStudy.myConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(mySerialDispatchQueue, ^{
        NSLog(@"block on mySerialDispatchQueue");
    });
    
    dispatch_async(myConcurrentDispatchQueue, ^{
        NSLog(@"block on myConcurrentDispatchQueue");
    });
複製程式碼
3. Main Dispatch Queue / Global Dispatch Queue

第二種方法是獲取系統標準的Dispatch QueueMain Dispatch Queue正如其名稱中含有的Main一樣,是在主執行緒中執行的Dispatch Queue,因為主執行緒只有一個,所以Main Dispatch Queue自然就是Serial Dispatch Queue。 追加到Main Dispatch Queue的處理在主執行緒的RunLoop中執行,因此要將使用者介面更新等一些必須在主執行緒中執行的處理追加到Main Dispatch Queue使用。 另一個Global Dispatch Queue是所有應用程式都能夠使用的Concurrent Dispatch Queue,沒有必要通過dispatch_queue_create函式逐個生成Concurrent Dispatch Queue,只要獲取Global Dispatch Queue使用即可。

    //獲取系統標準提供的 Dispatch Queue
    dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
    dispatch_async(mainDispatchQueue, ^{
        NSLog(@"主執行緒");
    });
    
    dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(globalDispatchQueue, ^{
        NSLog(@"globalDispatchQueue");
    });
複製程式碼

Global Dispatch Queue有4個執行優先順序,通過XUN核心管理的用於Global Dispatch Queue的執行緒,將各自使用的Global Dispatch Queue的執行優先順序作為執行緒的執行優先順序使用。但是通過XUN核心用於Global Dispatch Queue的執行緒,並不能保證實時性,因此執行優先順序只是大致的判斷。

Dispatch Queue的種類:

名稱 Dispatch Queue種類 說明
Main Dispatch Queue Serial Dispatch Queue 主執行緒執行
Global Dispatch Queue (High Priority) Global Dispatch Queue 執行優先順序: 高
Global Dispatch Queue (Default Priority) Global Dispatch Queue 執行優先順序: 預設
Global Dispatch Queue (Low Priority) Global Dispatch Queue 執行優先順序: 低
Global Dispatch Queue (Background Priority) Global Dispatch Queue 執行優先順序: 後臺

Main Dispatch QueueGlobal Dispatch Queue結合使用的例子:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        /*
         * 可並行執行的處理
         *
        dispatch_async(dispatch_get_main_queue(), ^{
            //只能在主執行緒中執行的處理,更新UI
        });
    });
複製程式碼
4. dispatch_set_target_queue

dispatch_queue_create函式生成的Dispatch Queue,不管是Serial Dispatch Queue還是Concurrent Dispatch Queue,都使用與預設優先順序Global Dispatch Queue相同執行優先順序的執行緒,而要變更生成的Dispatch Queue的執行優先順序,要使用dispatch_set_target_queue函式。

   dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("caoxueliang.MultiThreadStudy.mySerialDispatchQueue", NULL);
   dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    /*
     變更生成的Dispatch Queue 的執行優先順序
     第一個引數: 要變更執行優先順序的Dispatch Queue
     第二個引數: 指定與要使用的執行優先順序相同優先順序的`globalDispatchQueue`
     */
   dispatch_set_target_queue(mySerialDispatchQueue, globalDispatchQueue);
複製程式碼

需要注意的是:第一個引數不能指定為系統提供的Main Dispatch QueueGlobal Dispatch Queue

5. dispatch_after

在指定的時間後執行處理,比如3秒後執行處理,可使用dispatch_after函式來實現。在3秒後將指定的Block,追加到Main Dispatch Queue中:

 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC));
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"waited at least three seconds");
  });
複製程式碼

需要注意的是,dispatch_after函式並不是在指定時間後執行處理,而只是在指定時間追加處理到Dispatch Queue,此程式碼與在三秒後用dispatch_async函式,追加Block到Main Dispatch Queue相同。

因為Main Dispatch Queue在主執行緒的RunLoop中執行,所以在比如每隔1/60秒執行的RunLoop中,Block最快在3秒後執行,最慢在3秒+1/60秒後執行,並且在Main Dispatch Queue有大量處理追加或主執行緒的處理本身有延遲時,這個時間會更長。 雖然在有嚴格時間的要求下使用時會出現問題,但在想大致延遲執行處理時,該函式是非常有效的。 dispatch_time函式通常用於計算相對時間,而dispatch_walltime函式用於計算絕對時間,例如在dispatch_after函式中指定2011年11月11日11分11秒這一絕對時間的情況。

由NSDate類物件獲取傳遞給dispatch_after函式的dispatch_time_t型別的值:

static inline dispatch_time_t dispatch_walltime_date(NSDate *date) {
    NSTimeInterval interval;
    double second, subsecond;
    struct timespec time;
    dispatch_time_t milestone;
    
    interval = [date timeIntervalSince1970];
    subsecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = subsecond * NSEC_PER_SEC;
    milestone = dispatch_walltime(&time, 0);
    return milestone;
}
複製程式碼
6. Dispatch Group

在追加到Dispatch Queue中的多個處理全部結束後想執行結束處理,這種情況會經常出現,只是用一個Serial Dispatch Queue時,只要將想執行的處理全部追加到該Serial Dispatch Queue中並在最後追加結束處理,即可實現,但是使用Concurrent Dispatch Queue時或同時使用多個Dispatch Queue時,原始碼就會變得非常複雜。

這種情況下應該使用Dispatch Group,例如下載3張圖片,只有當這3張圖片都下載完成時,才會走結束處理的Block。

    /*
     在追加到 Dispatch Queue 中的多個處理全部結束後,執行結束處理
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
        NSURL *imageUrl = [NSURL URLWithString:@"https://wx1.sinaimg.cn/mw690/9bbc284bgy1flt5w1kf5gj20dw0ku13h.jpg"];
        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
        if (imageData) {
            NSLog(@"block1");
        }
    });
    dispatch_group_async(group, queue, ^{
        NSURL *imageUrl = [NSURL URLWithString:@"https://wx3.sinaimg.cn/mw690/9bbc284bgy1fly7dmgh87j20gq0r6akh.jpg"];
        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
        if (imageData) {
            NSLog(@"block2");
        }
    });
    dispatch_group_async(group, queue, ^{
        NSURL *imageUrl = [NSURL URLWithString:@"https://wx3.sinaimg.cn/mw690/9bbc284bgy1fly7dmgh87j20gq0r6akh.jpg"];
        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
        if (imageData) {
            NSLog(@"block3");
        }
    });
    dispatch_group_notify(group, queue, ^{
        NSLog(@"執行完畢");
    });
複製程式碼

上面程式碼的執行結果為:

2017-12-06 23:28:01.661591+0800 MultiThreadStudy[1329:81841] block1
2017-12-06 23:28:01.802706+0800 MultiThreadStudy[1329:81846] block3
2017-12-06 23:28:01.886015+0800 MultiThreadStudy[1329:81843] block2
2017-12-06 23:28:01.886432+0800 MultiThreadStudy[1329:81843] 執行完畢
複製程式碼

因為向Global Dispatch QueueConcurrent Dispatch Queue追加處理,多個執行緒並行執行,所以追加處理的執行順序不定,執行時會發生變化,但是最後執行完畢一定是最後輸出的。

7. Dispatch_barrier_async

在訪問資料庫或檔案時,如上所述,使用Serial Dispatch Queue可避免資料競爭的問題,寫入處理確實不可與其他的寫入處理以及包含讀取處理的其他某些處理並行執行,但是如果讀取處理只是與讀取處理並行執行,那麼多個並行執行就不會發生問題。 也就是說,為了高效率的進行訪問,讀取處理追加到Concurrent Dispatch Queue中,寫入處理在任一個讀取處理沒有執行的狀態下,追加到Serial Dispatch Queue中即可(在寫入處理結束之前,讀取處理不可執行)。 Dispatch_barrier_async函式同dispatch_queue_create函式生成的Concurrent Dispatch Queue一起使用。

在block3_for_reading處理和block4_for_reading處理之間執行寫入處理,並將寫入的內容讀取block4_for_reading處理以及之後的處理中。

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.ForBarrier", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{sleep(4); NSLog(@"block0_for_reading");});
    dispatch_async(queue, ^{sleep(1); NSLog(@"block1_for_reading");});
    dispatch_async(queue, ^{sleep(2); NSLog(@"block2_for_reading");});
    dispatch_async(queue, ^{sleep(2); NSLog(@"block3_for_reading");});

    dispatch_async(queue, ^{sleep(3);NSLog(@"寫入處理");});

    dispatch_async(queue, ^{sleep(1); NSLog(@"block4_for_reading");});
    dispatch_async(queue, ^{sleep(2); NSLog(@"block5_for_reading");});
    dispatch_async(queue, ^{sleep(4); NSLog(@"block6_for_reading");});
    dispatch_async(queue, ^{NSLog(@"block7_for_reading");});
複製程式碼

執行結果如下所示:

2017-12-11 22:36:10.379768+0800 MultiThreadStudy[758:22271] block7_for_reading
2017-12-11 22:36:11.381917+0800 MultiThreadStudy[758:22260] block1_for_reading
2017-12-11 22:36:11.381963+0800 MultiThreadStudy[758:22268] block4_for_reading
2017-12-11 22:36:12.382093+0800 MultiThreadStudy[758:22261] block2_for_reading
2017-12-11 22:36:12.382097+0800 MultiThreadStudy[758:22257] block3_for_reading
2017-12-11 22:36:12.382124+0800 MultiThreadStudy[758:22269] block5_for_reading
2017-12-11 22:36:13.382211+0800 MultiThreadStudy[758:22267] 寫入處理
2017-12-11 22:36:14.382834+0800 MultiThreadStudy[758:22258] block0_for_reading
2017-12-11 22:36:14.382854+0800 MultiThreadStudy[758:22270] block6_for_reading
複製程式碼

如果像上面?這樣簡單的在dispatch_async函式中加入寫入處理,那麼根據Concurrent Dispatch Queue的性質,就有可能在追加到寫入處理前面的處理中讀取到與期待不符的資料,還可能因非法訪問導致應用程式異常結束。如果追加多個寫入處理,則可能發生更多問題,比如資料競爭等。

所以,應該使用dispatch_barrier_async函式代替dispatch_async函式進行寫入處理,如下所示:

    dispatch_async(queue, ^{sleep(4); NSLog(@"block0_for_reading");});
    dispatch_async(queue, ^{sleep(1); NSLog(@"block1_for_reading");});
    dispatch_async(queue, ^{sleep(2); NSLog(@"block2_for_reading");});
    dispatch_async(queue, ^{sleep(2); NSLog(@"block3_for_reading");});

    dispatch_barrier_async(queue, ^{
        NSLog(@"寫入處理");
    });

    dispatch_async(queue, ^{sleep(1); NSLog(@"block4_for_reading");});
    dispatch_async(queue, ^{sleep(2); NSLog(@"block5_for_reading");});
    dispatch_async(queue, ^{sleep(4); NSLog(@"block6_for_reading");});
    dispatch_async(queue, ^{NSLog(@"block7_for_reading");});
複製程式碼

執行結果如下:

2017-12-11 22:52:40.062396+0800 MultiThreadStudy[834:30834] block1_for_reading
2017-12-11 22:52:41.062253+0800 MultiThreadStudy[834:30832] block2_for_reading
2017-12-11 22:52:41.062253+0800 MultiThreadStudy[834:30835] block3_for_reading
2017-12-11 22:52:43.062270+0800 MultiThreadStudy[834:30831] block0_for_reading
2017-12-11 22:52:43.062679+0800 MultiThreadStudy[834:30831] 寫入處理
2017-12-11 22:52:43.063032+0800 MultiThreadStudy[834:30834] block7_for_reading
2017-12-11 22:52:44.063647+0800 MultiThreadStudy[834:30831] block4_for_reading
2017-12-11 22:52:45.065397+0800 MultiThreadStudy[834:30835] block5_for_reading
2017-12-11 22:52:47.065416+0800 MultiThreadStudy[834:30832] block6_for_reading
複製程式碼

因此我們要使用dispatch_barrier_async函式,該函式會等待追加到Concurrent Dispatch Queue上的並行執行的處理全部結束之後,再將指定的處理追加到該Dispatch Dispatch Queue中,然後在由dispatch_barrier_async函式追加的處理執行完畢後,Concurrent Dispatch Queue才恢復為一般的動作,追加到該Concurrent Dispatch Queue的處理又開始並行執行。

Dispatch_barrier_async函式處理流程.png

8. dispatch_sync 與 dispatch_async

dispatch_async函式的async意味著非同步,就是將指定的Block非同步地追加到指定的Dispatch_Queue中,dispatch_async函式不做任何等待,不等待處理執行結束。 既然有async,當然也就有sync,即dispatch_sync函式,它意味著同步,也就是將指定的Block同步追加到指定的Dispatch Queue中,在追加Block之前,dispatch_sync函式會一直等待。一旦呼叫dispatch_sync函式使用簡單,所以也容易引起問題,即死鎖。

下面?這段程式碼表示在Main Dispatch Queue即主執行緒中執行指定的Block,並等待其執行結束,而其實在主執行緒中正在執行這些原始碼,所以無法執行追加到Main Dispatch Queue的Block。

dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
             NSLog(@"hello");
        });
    });
複製程式碼
9. dispatch_apply

dispatch_apply函式是dispatch_sync函式和Dispatch Group的關聯API。該函式按指定的次數將指定的Block追加到指定的Dispatch Queue中,並等待全部處理執行結束。

   //推薦在`dispatch_async`函式中非同步的執行`dispatch_apply`函式
    NSArray *tmpArray = [NSArray arrayWithObjects:@1,@2,@3,@4, nil];
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(queue, ^{
        /*
         *Global Dispatch Queue
         *等待`dispatch_apply`函式中全部處理執行結束
         */
        dispatch_apply([tmpArray count], queue, ^(size_t index) {
            
            //並列處理包含在`Nsarray`中的全部物件
            NSLog(@"%@",[tmpArray objectAtIndex:index]);
        });
        
        //`dispatch_apply`函式中處理全部執行結束
        
        //在`main dispatch queue`中非同步執行
        dispatch_async(dispatch_get_main_queue(), ^{         
            //更新使用者介面
            NSLog(@"done");
        });
    });
複製程式碼

輸出結果為:

2017-12-13 21:06:55.070579+0800 MultiThreadStudy[935:29821] 2
2017-12-13 21:06:55.070579+0800 MultiThreadStudy[935:29813] 1
2017-12-13 21:06:55.070580+0800 MultiThreadStudy[935:29820] 3
2017-12-13 21:06:55.070604+0800 MultiThreadStudy[935:29817] 4
2017-12-13 21:06:55.075021+0800 MultiThreadStudy[935:29629] done
複製程式碼

因為在Global Dispatch Queue中執行處理,所以各個處理的執行時間不定,但輸出結果中最後的done必定在最後的位置,這是因為diapatch_apply函式會等待全部處理執行結束。 第一個引數為重複次數,第二個引數為追加物件的Dispatch Queue,第三個引數的Block為帶引數的Block。 另外,由於dispatch_apply函式也與dispatch_sync函式相同,會等待處理執行結束,因此推薦在dispatch_async函式中非同步地執行dispatch_apply函式。

10. dispatch_suspend / dispatch_resume

當追加大量處理到Dispatch Queue時,在追加處理的過程中,有時希望不執行已追加的處理,在這種情況下,只要掛起Dispatch Queue即可,當可以執行時在恢復。

//掛起指定的queue
dispatch_suspend(queue);

//恢復指定的queue
dispatch_resume(queue);
複製程式碼

這些函式對已經執行的處理沒有影響,掛起後,追加到Disaptch Queue中但尚未執行的處理在此之後停止執行,而恢復則使得這些處理能夠繼續執行。

11. dispatch_semaphore

訊號量就是一種可用來控制訪問資源的數量標識,設定一個訊號量,線上程訪問之前,加上訊號量的處理,則告知系統按照我們指定的訊號量數量來執行多個執行緒。

  • dispatch_semaphore_create(n) :生成訊號,n表示訊號量為n。
  • dispatch_semaphore_wait:訊號等待,它像一個安保,比如小區規定最多隻能進入3輛車,而進入一輛車後名額就會減少一個,當剩下的名額為0的時候,再有汽車說要進去時,就只能在外面等待了,直到有名額閒置出來了,才能開進小區。
  • dispatch_semaphore_signal:訊號釋放,當有一輛車從小區出來時,就騰出來了一個名額。
/*
     * 生成 Dispatch Semaphone
     * Dispatch Semaphone 的計數初始值設定為2
     */
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    dispatch_queue_t quene = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //任務1
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore);
    });
    //任務2
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });
    //任務3
    dispatch_async(quene, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);
    });
複製程式碼

列印結果如下:

2017-12-13 23:03:00.196126+0800 MultiThreadStudy[1284:98022] run task 2
2017-12-13 23:03:00.196126+0800 MultiThreadStudy[1284:98014] run task 1
2017-12-13 23:03:01.201554+0800 MultiThreadStudy[1284:98022] complete task 2
2017-12-13 23:03:01.201562+0800 MultiThreadStudy[1284:98014] complete task 1
2017-12-13 23:03:01.201911+0800 MultiThreadStudy[1284:98015] run task 3
2017-12-13 23:03:02.205812+0800 MultiThreadStudy[1284:98015] complete task 3
複製程式碼

當將訊號量個數設定為1時,列印結果如下:

2017-12-13 23:04:02.907501+0800 MultiThreadStudy[1316:99128] run task 1
2017-12-13 23:04:03.912940+0800 MultiThreadStudy[1316:99128] complete task 1
2017-12-13 23:04:03.913360+0800 MultiThreadStudy[1316:99131] run task 2
2017-12-13 23:04:04.913853+0800 MultiThreadStudy[1316:99131] complete task 2
2017-12-13 23:04:04.914204+0800 MultiThreadStudy[1316:99129] run task 3
2017-12-13 23:04:05.919195+0800 MultiThreadStudy[1316:99129] complete task 3
複製程式碼

當將訊號量個數設定為3時,則列印結果如下:

2017-12-13 23:05:22.677144+0800 MultiThreadStudy[1354:100642] run task 1
2017-12-13 23:05:22.677145+0800 MultiThreadStudy[1354:100638] run task 2
2017-12-13 23:05:22.677175+0800 MultiThreadStudy[1354:100646] run task 3
2017-12-13 23:05:23.681331+0800 MultiThreadStudy[1354:100642] complete task 1
2017-12-13 23:05:23.681333+0800 MultiThreadStudy[1354:100646] complete task 3
2017-12-13 23:05:23.681333+0800 MultiThreadStudy[1354:100638] complete task 2
複製程式碼

通過上面的例子,對dispatch_semaphore已經有了基本的瞭解。

12. dispatch_once

dispatch_once函式是保證在應用程式執行中只執行一次指定處理的API。請看下面?這個例子:

+ (NSBundle *)bundle {
    static NSBundle *bundle;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSString *path = [[NSBundle mainBundle] pathForResource:@"ResourceWeibo" ofType:@"bundle"];
        bundle = [NSBundle bundleWithPath:path];
    });
    return bundle;
}
複製程式碼

通過dispatch_once函式建立的,該程式碼即使在多執行緒環境下執行,也可以保證百分百安全。該函式經常在生成單例物件時使用。

13. dispatch_source

dispatch_source函式可以實現定時器的功能:

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_source_t _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //每秒執行
    dispatch_source_set_timer(_timer,dispatch_walltime(NULL, 0),1.0*NSEC_PER_SEC, 0);

    //指定定時器指定時間內執行的處理
    dispatch_source_set_event_handler(_timer, ^{
        NSLog(@"text");
        if(time <= 0){
            //倒數計時結束,關閉
            dispatch_source_cancel(_timer);
            dispatch_async(dispatch_get_main_queue(), ^{

            });
        }
    });
    //啟動 Dispatch Source
    dispatch_resume(_timer);
複製程式碼

結尾

參考書籍Objective-C高階程式設計 文中DemoGitHub下載

相關文章