iOS多線程之GCD詳解

什麼是GCD

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

Grand Central Dispatch (GCD) 是異步執行任務的技術之一。通常將應用程序中記述的線程管理用的代碼在系統級中實現。開發者只須要定義想執行的任務並追加到適當的Dispatch Queue中,GCD就能生成必要的線程並計劃執行任務。也就是說GCD用咱們難以置信的很是簡潔的計述方法,實現了極爲複雜繁瑣的多線程編程。github

多線程編程

一個CPU一次只能執行一個命令,不能執行某處分開的並列的兩個命令,所以經過CPU執行的CPU命令列就比如一條無分叉的大道,其執行不會出現分歧。 這裏所說的 " 一個CPU執行的CPU命令列爲一條無分叉的路徑" 即爲 "線程"。數據庫

如今一個物理的CPU芯片實際上有64個(64核)CPU,若是一個CPU核虛擬爲兩個CPU核工做,那麼一臺計算機上使用多個CPU核就是理所固然的事了,儘管如此 " 一個CPU執行的CPU命令列爲一條無分叉的路徑" 仍然 不變。這種無分叉的路徑不止有一條,存在有多條時即爲 "多線程"編程

因爲使用多線程的程序能夠在摸個線程和其餘線程之間反覆屢次進行上下文切換,所以看上去好像1個CPU核可以並列的執行多個線程同樣,並且在具備多個CPU核的狀況下,就不是 " 看上去像" 了,而是真的提供了多個CPU核並行執行多個線程的技術。安全

這種利用多線程編程的技術就被稱爲"多線程編程"。bash

GCD相關API

1. Dispatch Queue

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

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 Queueapp

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下載

相關文章
相關標籤/搜索