你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> 深化了解GCD

深化了解GCD

編輯:IOS開發綜合

談到IOS多線程,普通都談判到四種方式:pthread、NSThread、GCD和NSOperation。其中,蘋果引薦也是我們最常常運用的無疑是GCD。關於身為開發者的我們來說,並發不斷都很順手,假如對GCD的了解不夠透徹,那麼IOS開發的歷程相對不會順利。這裡,我會從幾個角度淺談我對GCD的了解。

一、多線程背景

Although threads have been around for many years and continue to have their uses, they do not solve the general problem of executing multiple tasks in a scalable way. With threads, the burden of creating a scalable solution rests squarely on the shoulders of you, the developer. You have to decide how many threads to create and adjust that number dynamically as system conditions change. Another problem is that your application assumes most of the costs associated with creating and maintaining any threads it uses.

上述大致說出了直接操縱線程完成多線程的弊端:

開發人員必需依據零碎的變化靜態調整線程的數量和形態,即對開發者的擔負重。使用順序會在創立和維護線程上耗費很多本錢,即效率低。

絕對的,GCD是一套低層級的C API,經過 GCD,開發者只需求向隊列中添加一段代碼塊(block或C函數指針),而不需求直接和線程打交道。GCD在後端管理著一個線程池,它不只決議著你的代碼塊將在哪個線程被執行,還依據可用的零碎資源對這些線程停止管理。GCD的任務方式,使其擁有很多優點(快、穩、准):

快,更快的內存效率,由於線程棧不暫存於使用內存。穩,提供了自動的和片面的線程池管理機制,波動而便捷。准,提供了直接並且復雜的調用接口,運用方便,精確。 二、隊列和義務

初學GCD的時分,一定會糾結一些看似很關鍵但卻毫有意義的問題。比方:GCD和線程究竟什麼關系;異步義務究竟在哪個線程任務;隊列究竟是個什麼東西;mian queue和main thread究竟搞什麼名堂等等。如今,這些我們直接略過(最後拾遺中談判一下),蘋果既然引薦運用GCD,那麼為什麼還要糾結於線程呢?需求關注的只要兩個概念:隊列、義務。

1. 隊列

調度隊列是一個對象,它會以first-in、first-out的方式管理您提交的義務。GCD有三種隊列類型:

串行隊列,串行隊列將義務以先進先出(FIFO)的順序來執行,所以串行隊列常常用來做訪問某些特定資源的同步處置。你可以也依據需求創立多個隊列,而這些隊列絕對其他隊列都是並發執行的。換句話說,假如你創立了4個串行隊列,每一個隊列在同一時間都只執行一個義務,對這四個義務來說,他們是互相獨立且並發執行的。假如需求創立串行隊列,普通用dispatch_queue_create這個辦法來完成,並指定隊列類型DISPATCH_QUEUE_SERIAL。並行隊列,並發隊列雖然是能同時執行多個義務,但這些義務依然是依照先到先執行(FIFO)的順序來執行的。並發隊列會基於零碎負載來適宜地選擇並發執行這些義務。並發隊列普通指的就是全局隊列(Global queue),進程中存在四個全局隊列:高、中(默許)、低、後台四個優先級隊列,可以調用dispatch_get_global_queue函數傳入優先級來訪問隊列。當然我們也可以用dispatch_queue_create,並指定隊列類型DISPATCH_QUEUE_CONCURRENT,來自己創立一個並發隊列。客隊列,與主線程功用相反。實踐上,提交至main queue的義務會在主線程中執行。main queue可以調用dispatch_get_main_queue()來取得。由於main queue是與主線程相關的,所以這是一個串行隊列。和其它串行隊列一樣,這個隊列中的義務一次只能執行一個。它能保證一切的義務都在主線程執行,而主線程是獨一可用於更新 UI 的線程。

額定說一句,下面也說過,隊列間的執行是並行的,但是也存在一些限制。比方,並行執行的隊列數量遭到內核數的限制,無法真正做到少量隊列並行執行;比方,關於並行隊列中的全局隊列而言,其存在優先級關系,執行的時分也會遵照其優先順序,而不是並行。

2. 義務

linux內核中的義務的定義是描繪進程的一種構造體,而GCD中的義務只是一個代碼塊,它可以指一個block或許函數指針。依據這個代碼塊添加進入隊列的方式,將義務分為同步義務和異步義務:

同步義務,運用dispatch_sync將義務參加隊列。將同步義務參加串行隊列,會順序執行,普通不這樣做並且在一個義務未完畢時調起其它同步義務會死鎖。將同步義務參加並行隊列,會順序執行,但是也沒什麼意義。異步義務,運用dispatch_async將義務參加隊列。將異步義務參加串行隊列,會順序執行,並且不會呈現死鎖問題。將異步義務參加並行隊列,會並行執行多個義務,這也是我們最常用的一種方式。 3. 復雜使用
// 隊列的創立,queue1:中(默許)優先級的全局並行隊列、queue2:客隊列、queue3:未指定type則為串行隊列、queue4:指定串行隊列、queue5:指定並行隊列
dispatch_queue_t queue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_queue_t queue2 = dispatch_get_main_queue();
dispatch_queue_t queue3 = dispatch_queue_create("queue3", NULL);
dispatch_queue_t queue4 = dispatch_queue_create("queue4", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue5 = dispatch_queue_create("queue5", DISPATCH_QUEUE_CONCURRENT);

// 隊列中添加異步義務
dispatch_async(queue1, ^{
// 義務
...
});

// 隊列中添加同步義務
dispatch_sync(queue1, ^{
// 義務
...
});
三、GCD罕見用法和使用場景

十分喜歡一句話:Talk is cheap, show me the code.接上去對GCD的運用,我會經過代碼展現。

1. dispatch_async

普通用法

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(globalQueue, ^{
    // 一個異步的義務,例如網絡懇求,耗時的文件操作等等
    ...
    dispatch_async(dispatch_get_main_queue(), ^{
        // UI刷新
        ...
    });
});

使用場景
這種用法十分罕見,比方開啟一個異步的網絡懇求,待數據前往後前往客隊列刷新UI;又比方懇求圖片,待圖片前往刷新UI等等。

2. dispatch_after

普通用法

dispatch_queue_t queue= dispatch_get_main_queue();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), queue, ^{
    // 在queue外面延遲執行的一段代碼
    ...
});

使用場景
這為我們提供了一個復雜的延遲執行的方式,比方在view加載完畢延遲執行一個動畫等等。

3. dispatch_once

普通用法

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只執行一次的義務
    ...
});

使用場景
可以運用其創立一個單例,也可以做一些其他只執行一次的代碼,比方做一個只能點一次的button(仿佛沒啥用)。

4. dispatch_group

普通用法

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, ^{
    // 異步義務1
});

dispatch_group_async(group, queue, ^{
    // 異步義務2
});

// 等候group中多個異步義務執行終了,做一些事情,引見兩種方式

// 方式1(不好,會卡住以後線程)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
...

// 方式2(比擬好)
dispatch_group_notify(group, mainQueue, ^{
    // 義務完成後,在客隊列中做一些操作
    ...
});

使用場景
上述的一種方式,可以適用於自己維護的一些異步義務的同步問題;但是關於曾經封裝好的一些庫,比方A.networking等,我們不獲取其異步義務的隊列,這裡可以經過一種計數的方式控制義務間同步,上面為處理單界面多接口的一種方式。

// 兩個懇求和參數為我項目外面的不必在意。

// 計數+1
dispatch_group_enter(group);
[JDApiService getActivityDetailWithActivityId:self.activityId Location:stockAddressId SuccessBlock:^(NSDictionary *userInfo) {
    // 數據前往後一些處置
    ...

    // 計數-1
    dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
    // 數據前往後一些處置
    ...

    // 計數-1
    dispatch_group_leave(group);
}];

// 計數+1
dispatch_group_enter(group);
[JDApiService getAllCommentWithActivityId:self.activityId PageSize:3 PageNum:self.commentCurrentPage SuccessBlock:^(NSDictionary *userInfo) {
    // 數據前往後一些處置
    ...

    // 計數-1
    dispatch_group_leave(group);
} FailureBlock:^(NSError *error) {
    // 數據前往後一些處置
    ...

    // 計數-1
    dispatch_group_leave(group);
}];

// 其適用計數的說法能夠不太對,但是就這麼了解吧。會在計數為0的時分執行dispatch_group_notify的義務。
dispatch_group_notify(group, mainQueue, ^{
    // 普通為回客隊列刷新UI
    ...
});
5. dispatch_barrier_async

普通用法

// dispatch_barrier_async的作用可以用一個詞概括--承上啟下,它保證此前的義務都先於自己執行,爾後的義務也遲於自己執行。本例中,義務4會在義務1、2、3都執行完之後執行,而義務5、6會等候義務4執行完後執行。

dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
    // 義務1
    ...
});
dispatch_async(queue, ^{
    // 義務2
    ...
});
dispatch_async(queue, ^{
    // 義務3
    ...
});
dispatch_barrier_async(queue, ^{
    // 義務4
    ...
});
dispatch_async(queue, ^{
    // 義務5
    ...
});
dispatch_async(queue, ^{
    // 義務6
    ...
});

使用場景
和dispatch_group類似,dispatch_barrier也是異步義務間的一種同步方式,可以在比方文件的讀寫操作時運用,保證讀操作的精確性。另外,有一點需求留意,dispatch_barrier_sync和dispatch_barrier_async只在自己創立的並發隊列上無效,在全局(Global)並發隊列、串行隊列上,效果跟dispatch_(a)sync效果一樣。

6. dispatch_apply

普通用法

// for循環做一些事情,輸入0123456789
for (int i = 0; i < 10; i ++) {
    NSLog(@"%d", i);
}

// dispatch_apply交換(當且僅當處置順序對處置後果無影響環境),輸入順序不定,比方1098673452
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/*! dispatch_apply函數闡明
*
*  @brief  dispatch_apply函數是dispatch_sync函數和Dispatch Group的關聯API
*         該函數按指定的次數將指定的Block追加到指定的Dispatch Queue中,並等到全部的處置執行完畢
*
*  @param 10    指定反復次數  指定10次
*  @param queue 追加對象的Dispatch Queue
*  @param index 帶有參數的Block, index的作用是為了按執行的順序區分各個Block
*
*/
dispatch_apply(10, queue, ^(size_t index) {
    NSLog(@"%zu", index);
});

使用場景
那麼,dispatch_apply有什麼用呢,由於dispatch_apply並行的運轉機制,效率普通快於for循環的類串行機制(在for一次循環中的處置義務很多時差距比擬大)。比方這可以用來拉取網絡數據後提早算出各個控件的大小,避免繪制時計算,進步表單滑動流利性,假如用for循環,耗時較多,並且每個表單的數據沒有依賴關系,所以用dispatch_apply比擬好。

7. dispatch_suspend和dispatch_resume

普通用法

dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_suspend(queue); //暫停隊列queue
dispatch_resume(queue);  //恢復隊列queue

使用場景
這種用法我還沒有嘗試過,不過其中有個需求留意的點。這兩個函數不會影響到隊列中曾經執行的義務,隊列暫停後,曾經添加到隊列中但還沒有執行的義務不會執行,直到隊列被恢復。

8. dispatch_semaphore_signal

普通用法

// dispatch_semaphore_signal有兩類用法:a、處理同步問題;b、處理無限資源訪問(資源為1,即互斥)問題。
// dispatch_semaphore_wait,若semaphore計數為0則等候,大於0則使其減1。
// dispatch_semaphore_signal使semaphore計數加1。

// a、同步問題:輸入一定為1、2、3。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore1 = dispatch_semaphore_create(1);
dispatch_semaphore_t semaphore2 = dispatch_semaphore_create(0);
dispatch_semaphore_t semaphore3 = dispatch_semaphore_create(0);

dispatch_async(queue, ^{
    // 義務1
    dispatch_semaphore_wait(semaphore1, DISPATCH_TIME_FOREVER);
    NSLog(@"1\n");
    dispatch_semaphore_signal(semaphore2);
    dispatch_semaphore_signal(semaphore1);
});

dispatch_async(queue, ^{
    // 義務2
    dispatch_semaphore_wait(semaphore2, DISPATCH_TIME_FOREVER);
    NSLog(@"2\n");
    dispatch_semaphore_signal(semaphore3);
    dispatch_semaphore_signal(semaphore2);
});

dispatch_async(queue, ^{
    // 義務3
    dispatch_semaphore_wait(semaphore3, DISPATCH_TIME_FOREVER);
    NSLog(@"3\n");
    dispatch_semaphore_signal(semaphore3);
});

// b、無限資源訪問問題:for循環看似能創立100個異步義務,本質由於信號限制,最多創立10個異步義務。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(10);
for (int i = 0; i < 100; i ++) {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    dispatch_async(queue, ^{
    // 義務
    ...
    dispatch_semaphore_signal(semaphore);
    });
}

使用場景
其實關於dispatch_semaphore_t,並沒有看到太多使用和材料解釋,我只能參照自己對linux信號量的了解寫了兩個用法,經測試的確相似。這裡,就不對一些死鎖問題停止討論了。

9. dispatch_set_context、dispatch_get_context和dispatch_set_finalizer_f

普通用法

// dispatch_set_context、dispatch_get_context是為了向隊列中傳遞上下文context服務的。
// dispatch_set_finalizer_f相當於dispatch_object_t的析構函數。
// 由於context的數據不是foundation對象,所以arc不會自動回收,普通在dispatch_set_finalizer_f中手動回收,所以普通講上述三個辦法綁定運用。

- (void)test
{
    // 幾種創立context的方式
    // a、用C言語的malloc創立context數據。
    // b、用C++的new創立類對象。
    // c、用Objective-C的對象,但是要用__bridge等關鍵字轉為Core Foundation對象。

    dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
    if (queue) {
        // "123"即為傳入的context
        dispatch_set_context(queue, "123");
        dispatch_set_finalizer_f(queue, &xigou);
    }
    dispatch_async(queue, ^{
        char *string = dispatch_get_context(queue);
        NSLog(@"%s", string);
    });
}

// 該函數會在dispatch_object_t銷毀時調用。
void xigou(void *context)
{
    // 釋放context的內存(對應上述abc)

    // a、CFRelease(context);
    // b、free(context);
    // c、delete context;
}

使用場景
dispatch_set_context可以為隊列添加上下文數據,但是由於GCD是C言語接口方式的,所以其context參數類型是“void *”。需運用上述abc三種方式創立context,並且普通結合dispatch_set_finalizer_f運用,回收context內存。

四、內存和平安

略微提一下吧,由於局部人糾結於dispatch的內存問題。
內存

MRC:用dispatch_retain和dispatch_release管理dispatch_object_t內存。ARC:ARC在編譯時辰自動管理dispatch_object_t內存,運用retain和release會報錯。

平安
dispatch_queue是線程平安的,你可以隨意往外面添加義務。

五、拾遺

這裡次要提一下GCD的一些坑和線程的一些問題。

1. 死鎖

dispatch_sync

// 假定這段代碼執行於客隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t mainQueue = dispatch_get_main_queue();

// 在客隊列添加同步義務
dispatch_sync(mainQueue, ^{
    // 義務
    ...
});

// 在串行隊列添加同步義務 
dispatch_sync(serialQueue, ^{
    // 義務
    ...
    dispatch_sync(serialQueue, ^{
        // 義務
        ...
    });
};

dispatch_apply

// 由於dispatch_apply會卡住以後線程,外部的dispatch_apply會等候內部,內部的等候外部,所以死鎖。
dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(10, queue, ^(size_t) {
    // 義務
    ...
    dispatch_apply(10, queue, ^(size_t) {
        // 義務
        ...
    });
});

dispatch_barrier
dispatch_barrier_sync在串行隊列和全局並行隊列外面和dispatch_sync異樣的效果,所以需思索同dispatch_sync一樣的死鎖問題。

2. dispatch_time_t
// dispatch_time_t普通在dispatch_after和dispatch_group_wait等辦法裡作為參數運用。這裡最需求留意的是一些宏的含義。
// NSEC_PER_SEC,每秒有多少納秒。
// USEC_PER_SEC,每秒有多少毫秒。
// NSEC_PER_USEC,每毫秒有多少納秒。
// DISPATCH_TIME_NOW 從如今開端
// DISPATCH_TIME_FOREVE 永世

// time為1s的寫法
dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);


六、參考文獻

1、https://developer.apple.com/library/mac/documentation/General/Conceptual/ConcurrencyProgrammingGuide/OperationQueues/OperationQueues.html#//apple_ref/doc/uid/TP40008091-CH102-SW2
2、https://developer.apple.com/library/IOS/documentation/Performance/Reference/GCD_libdispatch_Ref/
3、http://tutuge.me/2015/04/03/something-about-gcd/
4、http://www.jianshu.com/p/85b75c7a6286
5、http://www.jianshu.com/p/d56064507fb8



文/天口三水羊(簡書作者)
原文鏈接:http://www.jianshu.com/p/665261814e24

【深化了解GCD】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!

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