你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> GCD API 理解 (一)

GCD API 理解 (一)

編輯:IOS開發綜合

引子

iOS 開發中有三大進階性的技術點,分別是GCD、runtime 和runloop。其中GCD用的最多,runtime也有不少使用場景,runloop在系統的API裡體現的比較多,項目裡實際使用比較少。
一直都想就這三個技術點做一些總結,沒事的時候可以回來復習鞏固一下,可是記錄了很多要寫的點,但是文章卻是一拖再拖。本文就記錄GCD的一些API自己的理解和用法等,遇到新的API也會補充進來。

擴展

pthread 也是C 語言API(pthread現在已經基本看不到有使用的了),而NSThread 是Objective-C對pthread的封裝;雖然GCD也是C語言API,但是非常容易使用,而NSOpretion是Objective-C對GCD的進一步封裝。這是iOS 中幾種多線程技術的關系。

介紹

GCD(Grand Central Dispatch)是OS X與iOS 開發中一種多核開發的解決方案。GCD是在libdispatch框架內,而該框架是iOS 應用默認的已經包含進去了。
蘋果是在 OS X 10.6 和 iOS 4 中引入了 GCD,它是低層級的C語言 API。使用GCD,它能夠讓開發者更加方便、更加容易得使用多核CPU。而且我們開發者不需要再直接跟線程打交道了,只需要向隊列中添加代碼塊即可,而GCD 在後端其實是管理著一個線程池。GCD 不僅決定著我們的代碼塊將在哪個線程被執行,它還根據可用的系統資源對這些線程進行管理。通過集中的管理線程,來緩解大量線程被創建的問題。
媽媽再也不用擔心我們自己來處理線程的創建和銷毀了

基本知識

1.串行隊列和並發隊列

串行隊列就是每次只有一個任務執行,當一個任務執行完畢後,才會執行下一個任務。
而並發隊列就是同時有多個任務在執行,同時執行的任務哪個先執行完,我們根本不知道。
iOS 中的串行隊列有兩種:主隊列(dispatch_get_main_queue())、通過dispatch_queue_create創建的串行隊列

1.1創建串行隊列

主隊列是系統為我們的應用創建的,我們是沒辦法創建一個新的主隊列的。
以下是一個創建串行隊列的例子。

dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);

其中第一個參數隊列的label標記,第二個可以控制創建出來的隊列是串行隊列還是並行對列。
DISPATCH_QUEUE_SERIAL其實就是一個宏,實際上是NULL。
這也就是為什麼,我們經常會看到別人是這樣創建串行隊列的:

dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", NULL);
1.2創建並發隊列

第一種創建並發隊列的方式就是將上面方法的第二個參數修改為並發參數:

dispatch_queue_t concurrent_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_CONCURRENT);

第二種方式創建全局並發隊列:

dispatch_queue_t concurrent_queue =  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

該方法第一個參數是創建的queue的級別。有四個宏,分別對應不同的級別:

#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN

第二個參數,是蘋果留作以後備用的,一般默認就填0就OK了。
因為DISPATCH_QUEUE_PRIORITY_DEFAULT其實就是等於0,所以我們常常會看到:

dispatch_queue_t concurrent_queue =  dispatch_get_global_queue(0, 0);
2 同步和異步

同步函數就是等待任務的執行,等任務執行完成後再返回。所以同步就意味著在當前線程中執行任務,並不會開啟新的線程,不管是串行隊列還是並發隊列。

dispatch_sync(concurrent_queue, ^{ 
      NSLog(@"打印%d----線程:%@", i,[NSThread currentThread]); //在當前線程中執行
});

而異步函數就不會等待任務的執行完成,它會立即返回。所以異步也就意味著會開啟一個新的線程,所以並不會阻塞當前的線程。

dispatch_async(concurrent_queue, ^{ 
  NSLog(@"打印%d----線程:%@", i,[NSThread currentThread]);  //在新的線程中執行
});
3 總結

GCD的多線程使用都是同步/異步 與串行隊列/並發隊列組合使用的。

同步 異步 串行隊列 不創建新線程,順序執行 並發隊列 不創建新線程,順序執行

串行隊列無論是同步的執行任務,還是異步的執行任務,任務都是順序執行的。

串行隊列同步任務:

    dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
    for (int i = 0 ; i < 5; i++) {
        dispatch_sync(serial_queue, ^{
            NSLog(@"串行%d----線程:%@", i,[NSThread currentThread]);
        });
    }
    dispatch_sync(serial_queue, ^{
        NSLog(@"串行最後----線程:%@",[NSThread currentThread]);
    });
// 打印結果:
2016-07-08 16:15:13.421 PractiseProject[9528:187413] 串行0----線程:{number = 1, name = main}
2016-07-08 16:15:13.421 PractiseProject[9528:187413] 串行1----線程:{number = 1, name = main}
2016-07-08 16:15:13.422 PractiseProject[9528:187413] 串行2----線程:{number = 1, name = main}
2016-07-08 16:15:13.422 PractiseProject[9528:187413] 串行3----線程:{number = 1, name = main}
2016-07-08 16:15:13.422 PractiseProject[9528:187413] 串行4----線程:{number = 1, name = main}
2016-07-08 16:15:13.422 PractiseProject[9528:187413] 串行最後----線程:{number = 1, name = main}

* 串行隊列異步任務:*

    dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
    for (int i = 0 ; i < 5; i++) {
        dispatch_async(serial_queue, ^{
            NSLog(@"串行%d----線程:%@", i,[NSThread currentThread]);
        });
    }
    dispatch_async(serial_queue, ^{
        NSLog(@"串行最後----線程:%@",[NSThread currentThread]);
    });
// 打印結果:
2016-07-08 16:16:34.852 PractiseProject[9545:188432] 串行0----線程:{number = 2, name = (null)}
2016-07-08 16:16:34.852 PractiseProject[9545:188432] 串行1----線程:{number = 2, name = (null)}
2016-07-08 16:16:34.853 PractiseProject[9545:188432] 串行2----線程:{number = 2, name = (null)}
2016-07-08 16:16:34.853 PractiseProject[9545:188432] 串行3----線程:{number = 2, name = (null)}
2016-07-08 16:16:34.853 PractiseProject[9545:188432] 串行4----線程:{number = 2, name = (null)}
2016-07-08 16:16:34.853 PractiseProject[9545:188432] 串行最後----線程:{number = 2, name = (null)}

* 串行隊列混合任務: *
即使串行隊列中有同步任務和異步任務,也還是按照添加任務的順序執行,只不過同步任務在當前線程執行,異步任務在新線程中執行:

    dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
    for (int i = 0 ; i < 5; i++) {
        dispatch_sync(serial_queue, ^{
            NSLog(@"串行%d----線程:%@", i,[NSThread currentThread]);
        });
    }
    dispatch_async(serial_queue, ^{
        NSLog(@"串行最後----線程:%@",[NSThread currentThread]);
    });
// 打印結果:
2016-07-08 16:21:34.545 PractiseProject[9588:190084] 串行0----線程:{number = 1, name = main}
2016-07-08 16:21:34.545 PractiseProject[9588:190084] 串行1----線程:{number = 1, name = main}
2016-07-08 16:21:34.545 PractiseProject[9588:190084] 串行2----線程:{number = 1, name = main}
2016-07-08 16:21:34.545 PractiseProject[9588:190084] 串行3----線程:{number = 1, name = main}
2016-07-08 16:21:34.546 PractiseProject[9588:190084] 串行4----線程:{number = 1, name = main}
2016-07-08 16:21:34.546 PractiseProject[9588:190120] 串行最後----線程:{number = 2, name = (null)}

* 並發隊列同步任務:*
因為不會開啟新的線程,所以就在當前線程中執行,只有一條線程,因此只能順序執行了。

    dispatch_queue_t concurrent_queue =  dispatch_get_global_queue(0, 0);
    for (int i = 0 ; i < 5; i++) {
        dispatch_sync(concurrent_queue, ^{
            NSLog(@"並行%d----線程:%@", i,[NSThread currentThread]);
        });
    }
    dispatch_sync(concurrent_queue, ^{
        NSLog(@"並行最後----線程:%@",[NSThread currentThread]);
    });
//打印結果:
2016-07-08 16:26:44.264 PractiseProject[9671:193541] 並發0----線程:{number = 1, name = main}
2016-07-08 16:26:44.264 PractiseProject[9671:193541] 並發1----線程:{number = 1, name = main}
2016-07-08 16:26:44.265 PractiseProject[9671:193541] 並發2----線程:{number = 1, name = main}
2016-07-08 16:26:44.265 PractiseProject[9671:193541] 並發3----線程:{number = 1, name = main}
2016-07-08 16:26:44.265 PractiseProject[9671:193541] 並發4----線程:{number = 1, name = main}
2016-07-08 16:26:44.265 PractiseProject[9671:193541] 並發最後----線程:{number = 1, name = main}

* 並發隊列異步任務:*
因為異步任務會開啟新的線程,所以哪個任務先執行完畢,是不知道的。相同的示例代碼多次運行,任務執行完成的順序是不一樣的。這裡也可以看出一個GCD的優點,它會復用之前使用過的閒置的線程。

    dispatch_queue_t concurrent_queue =  dispatch_get_global_queue(0, 0);
    for (int i = 0 ; i < 5; i++) {
        dispatch_async(concurrent_queue, ^{
            NSLog(@"並行%d----線程:%@", i,[NSThread currentThread]);
        });
    }
    dispatch_async(concurrent_queue, ^{
        NSLog(@"並行最後----線程:%@",[NSThread currentThread]);
    });
// 打印結果:
2016-07-08 16:29:03.522 PractiseProject[9696:194688] 並發1----線程:{number = 4, name = (null)}
2016-07-08 16:29:03.522 PractiseProject[9696:194694] 並發0----線程:{number = 3, name = (null)}
2016-07-08 16:29:03.522 PractiseProject[9696:194703] 並發3----線程:{number = 6, name = (null)}
2016-07-08 16:29:03.522 PractiseProject[9696:194693] 並發2----線程:{number = 5, name = (null)}
2016-07-08 16:29:03.523 PractiseProject[9696:194688] 並發4----線程:{number = 4, name = (null)}
2016-07-08 16:29:03.523 PractiseProject[9696:194694] 並發最後----線程:{number = 3, name = (null)}

* 並發隊列混合任務:*
可以確定的是如果異步任務添加在同步任務後面,則一定會等同步任務執行完畢後,才會執行異步任務;如果異步任務在同步任務前添加,則異步任務什麼時候執行完畢也是未知的,但是多個同步任務一定是順序執行的。

    dispatch_queue_t concurrent_queue =  dispatch_get_global_queue(0, 0);
    for (int i = 0 ; i < 5; i++) {
        dispatch_sync(concurrent_queue, ^{
            NSLog(@"並行%d----線程:%@", i,[NSThread currentThread]);
        });
    }
    dispatch_async(concurrent_queue, ^{
        NSLog(@"並行最後----線程:%@",[NSThread currentThread]);
    });
    for (int i = 0 ; i < 5; i++) {
        dispatch_sync(concurrent_queue, ^{
            NSLog(@"並行%d----線程:%@", i,[NSThread currentThread]);
        });
    }
// 打印結果(多次運行打印結果並不相同)
2016-07-08 16:38:56.273 PractiseProject[9805:199354] 並發0----線程:{number = 1, name = main}
2016-07-08 16:38:56.273 PractiseProject[9805:199354] 並發1----線程:{number = 1, name = main}
2016-07-08 16:38:56.273 PractiseProject[9805:199354] 並發2----線程:{number = 1, name = main}
2016-07-08 16:38:56.273 PractiseProject[9805:199354] 並發3----線程:{number = 1, name = main}
2016-07-08 16:38:56.273 PractiseProject[9805:199354] 並發4----線程:{number = 1, name = main}
2016-07-08 16:38:56.274 PractiseProject[9805:199354] 並發0----線程:{number = 1, name = main}
2016-07-08 16:38:56.274 PractiseProject[9805:199354] 並發1----線程:{number = 1, name = main}
2016-07-08 16:38:56.274 PractiseProject[9805:199354] 並發2----線程:{number = 1, name = main}
2016-07-08 16:38:56.274 PractiseProject[9805:199470] 並發最後----線程:{number = 3, name = (null)}
2016-07-08 16:38:56.274 PractiseProject[9805:199354] 並發3----線程:{number = 1, name = main}
2016-07-08 16:38:56.274 PractiseProject[9805:199354] 並發4----線程:{number = 1, name = main}

注意事項

利用同步/異步任務 和串行隊列/並發隊列時,需要注意一些情況防止發生死鎖。
* 情形一*
在主線程中調度主隊列完成一個同步任務,會發生死鎖。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    self.view.backgroundColor = [UIColor orangeColor];
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"串行----線程:%@",[NSThread currentThread]);
    });
}

如上代碼,界面永遠不會加載出來,裡面的NSLog永遠也不會執行。原因是ViewDidLoad是在主隊列的主線程中執行,執行到dispatch_sync 時會阻塞住,等待dispatch_sync中的打印任務執行完畢。而dispatch_sync又會等viewDidLoad執行完畢,再開始執行,因此就互相等待發生死鎖。
* 情形二 *
在串行隊列的同步任務中再執行同步任務,會發生死鎖。

    dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(serial_queue, ^{
        NSLog(@"串行1----線程:%@",[NSThread currentThread]);
        dispatch_sync(serial_queue, ^{
            NSLog(@"串行2----線程:%@",[NSThread currentThread]);
        });
    });

上面示例中的NSLog(@"串行1----線程:%@",[NSThread currentThread]);會打印,
但是NSLog(@"串行2----線程:%@",[NSThread currentThread]);永遠也不會執行。
因為串行隊列一次只能執行一個任務,執行完畢返回後,才會執行下一個任務,而外層任務的完成需要等待內層任務的結束,而內層任務的開始需要等外層任務結束。
其實情形一是情形二的一種特殊情況。

* 情形三 *
在串行隊列的異步任務中再嵌套執行同步任務,也會發生死鎖。

    dispatch_queue_t serial_queue = dispatch_queue_create("com.haley.com", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serial_queue, ^{
        NSLog(@"串行異步----線程:%@",[NSThread currentThread]);
        dispatch_sync(serial_queue, ^{
            NSLog(@"串行2----線程:%@",[NSThread currentThread]);
        });
        [NSThread sleepForTimeInterval:2.0];
    });

同樣的,由於串行隊列一次只能執行一個任務,任務結束後,才能執行下一個任務。
所以異步任務的結束需要等裡面同步任務結束,而裡面同步任務的開始需要等外面異步任務結束,所以就相互等待,發生死鎖了。
第一篇就到這裡了,下一篇記錄GCD的其他API。Have Fun!

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved