iOS開發(fā)——多線程GCD入門

0.前言

0.什么是GCD?

GCD全程為Grand Central Dispatch,是異步執(zhí)行的技術(shù)之一。一般將應(yīng)用程序中記述的線程管理代碼在系統(tǒng)級實現(xiàn)。開發(fā)者只用定義向執(zhí)行的任務(wù),并追加到適當(dāng)?shù)腄ispatch Queue中就完事兒了,GCD能自動生成必要的線程并執(zhí)行任務(wù)。

1.單線程

CPU一次只能執(zhí)行一個命令 ,不能一心兩用,同時執(zhí)行兩個分開的并列命令,因此通過CPU執(zhí)行命令就像是一條五分叉的路,及時地址分散,看似凌亂,實則也只有一條路徑。這就是線程。

2.上下文切換

macOS和iOS中的核心XNU內(nèi)核在發(fā)生操作系統(tǒng)事件時,會切換執(zhí)行路徑,執(zhí)行中路徑的狀態(tài),會被保存到各自路徑專用的內(nèi)存塊中,從切換目標(biāo)路徑專用的內(nèi)存塊中,復(fù)原CPU寄存器等信息,繼續(xù)執(zhí)行切換路徑的CPU命令列。這就是上下文切換。

3.為什么長時間的處理不放在主線程執(zhí)行?

程序在啟動時,最先執(zhí)行的線程是主線程,用來描繪用戶界面、處理屏幕觸摸的事件。如果主線程進行長時間的處理,機會導(dǎo)致主線程阻塞,妨礙主線程中RunLoop主循環(huán)的執(zhí)行,導(dǎo)致不能更新用戶界面、應(yīng)用畫面長時間停滯等問題。

1.GCD的API

1. DIspatch Queue

名詞解釋

官方對GCD的說明:

開發(fā)者要做的只是定義想執(zhí)行的任務(wù),并追加到適當(dāng)?shù)腄ispatch Queue中。

源碼的表示就是:

dispatch_sync(queue, ^{
        //your work here;
    });
[4] → | [3] [2] [1] | → [0]
追加    Dispatch Queue  已處理 

Dispatch Queue遵循FIFO原則,先進先出。

Dispatch Queue的種類

  • Serial Dispatch Queue :串行,一個線程
  • Concurrent Dispatch Queue :并行,多線程

2.如何得到Dispatch Queue

1.通過CGD的API生成

以下代碼生成了Serial Dispatch Queue

dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.MyGCDSerialDispatchQueue", NULL);

以下代碼生成了Concurrent Queue

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.myGCDConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

關(guān)于Serial Dispatch Queue的個數(shù)

一個Serial Dispatch Queue同時只能追加一個處理。

多個Serial DIspatch Queue各追加處理,并同時執(zhí)行,就變成并行執(zhí)行。

注意,一旦生成1個Serial DIspatch Queue,系統(tǒng)就對一個Queue生成并使用一個線程,如果生成2000個,那就生成2000個線程。

因此,只是在為了避免多個線程更新相同資源導(dǎo)致的數(shù)據(jù)競爭時使用Serial DIspatch Queue

關(guān)于dispatch_queue_create方法

dispatch_queue_create(const char * _Nullable label, dispatch_queue_attr_t  _Nullable attr)

第一個參數(shù)為queue的名稱,推薦輸入逆序全程域名,崩潰時會出現(xiàn)在log中。
第二個參數(shù)為queue的類型,如果輸入NULL則為Serial,如果輸入DISPATCH_QUEUE_CONCURRENT則為Concurrent。

另:iOS6之后,DIspatch Queue也加入了ARC,如果手動設(shè)置Retain和Release,會報錯。

將Block內(nèi)的執(zhí)行追加到Queue中

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.myGCDConcurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(concurrentQueue, ^{
    NSLog(@"v-tech is the BEST!");
});

2.獲取系統(tǒng)標(biāo)準(zhǔn)提供的Dispatch Queue

系統(tǒng)提供的Dispatch Queue有:

  • Main Dispatch Queue
  • Global Dispatch Queue

Main Dispatch Queue是在主線程執(zhí)行的Dispatch Queue,只有一個,是Serial Dispatch Queue。追加到Main Dispatch Queue的處理在主線程的RunLoop中執(zhí)行,因此必須將用戶界面更新等一些必須在主線程中執(zhí)行的處理追加到Main Dispatch Queue中。

Global Dispatch Queue是所有程序都可以使用的Concurrent Dispatch Queue。沒有必要通過dispatch_queue_create逐個生成Concurrent Dispatch Queue,直接獲取Global Dispatch Queue就行了。

獲取Main Dispatch Queue和 Global Dispatch Queue的方法

//main
dispatch_queue_t mainDispatchQueue = dispatch_get_main_queue();
    
//global
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(long identifier, unsigned long flags);

//global example
dispatch_queue_t globalDispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

獲取Main Dispatch Queue的方法只有一個。

獲取Global的方法里有兩個變量,第一個是設(shè)定優(yōu)先級,有以下四個可以選擇:


第二個參數(shù)傳入0即可。

文檔關(guān)于dispatch_get_global_queue的介紹:

/*!
 * @function dispatch_get_global_queue
 *
 * @abstract
 * Returns a well-known global concurrent queue of a given quality of service
 * class.
 *
 * @discussion
 * The well-known global concurrent queues may not be modified. Calls to
 * dispatch_suspend(), dispatch_resume(), dispatch_set_context(), etc., will
 * have no effect when used with queues returned by this function.
 *
 * @param identifier
 * A quality of service class defined in qos_class_t or a priority defined in
 * dispatch_queue_priority_t.
 *
 * It is recommended to use quality of service class values to identify the
 * well-known global concurrent queues:
 *  - QOS_CLASS_USER_INTERACTIVE
 *  - QOS_CLASS_USER_INITIATED
 *  - QOS_CLASS_DEFAULT
 *  - QOS_CLASS_UTILITY
 *  - QOS_CLASS_BACKGROUND
 *
 * The global concurrent queues may still be identified by their priority,
 * which map to the following QOS classes:
 *  - DISPATCH_QUEUE_PRIORITY_HIGH:         QOS_CLASS_USER_INITIATED
 *  - DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
 *  - DISPATCH_QUEUE_PRIORITY_LOW:          QOS_CLASS_UTILITY
 *  - DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND
 *
 * @param flags
 * Reserved for future use. Passing any value other than zero may result in
 * a NULL return value.
 *
 * @result
 * Returns the requested global queue or NULL if the requested global queue
 * does not exist.
 */

3.延遲處理

場景:在3秒后將指定的Block追加到Main Dispatch Queue

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);

dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"wait at least 3 second");
    });

注意,代碼指的不是3秒后執(zhí)行Block內(nèi)的處理,而是在3秒后將執(zhí)行追加到Queue中執(zhí)行。

因為Main Dispatch Queue 在主線程的RunLoop中執(zhí)行,所以在比如每隔1/60秒執(zhí)行的RunLoop中,Block最快在3秒后執(zhí)行,最慢在3秒+1/60秒后執(zhí)行,并且在Main Dispatch Queue有大量處理追加或者主線程的處理本身有延遲,這個時間會更長。

dispatch_time方法解析

dispatch_time(dispatch_time_t when, int64_t delta)

第一個參數(shù)是時間點,DISPATCH_TIME_NOW意思是現(xiàn)在。

第二個參數(shù)是距離時間點的長度,也就是多少時間之后追加到Queue中,格式如下:

3ull * NSEC_PER_SEC 
/*
* 3ull * NSEC_PER_SEC 表示3秒
* 
* ull = unsigned long long
*
* #define NSEC_PER_SEC 1000000000ull
* #define NSEC_PER_MSEC 1000000ull
* #define USEC_PER_SEC 1000000ull
* #define NSEC_PER_USEC 1000ull
*
*/

4.等待所有任務(wù)完成后執(zhí)行其他處理

1.Dispatch Group

場景:在追加到Dispatch Queue中的多個處理全部結(jié)束之后執(zhí)行其他處理。

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, ^{
        NSLog(@"blk0");
});
dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
});
dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
});
    
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"other method");
});

dispatch_group_create()生成dispatch_group_t類的Dispatch Group。

dispatch_group_asyncdispatch——async相同,都是追加Block到指定的Dispatch Queue中。不同的是,指定的Block屬于指定的Dispatch Group。

在追加到Dispatch Group中的處理全部執(zhí)行結(jié)束后,該源代碼中使用的dispatch_group_notify函數(shù)會將執(zhí)行的Block追加到Dispatch Queue中,將第一個參數(shù)指定為要監(jiān)視的Dispatch Group。在追加到改Group的全部處理執(zhí)行解釋后,將第三個參數(shù)的Block追加到第二個參數(shù)的Dispatch Queue中。在dispatch_group_notify中不管制定什么樣的Dispatch Queue,屬于Dispatch Group的全部處理在追加指定的Block時已經(jīng)結(jié)束。

dispatch_group_async的介紹

/*!
 * @function dispatch_group_async
 *
 * @abstract
 * Submits a block to a dispatch queue and associates the block with the given
 * dispatch group.
 *
 * @discussion
 * Submits a block to a dispatch queue and associates the block with the given
 * dispatch group. The dispatch group may be used to wait for the completion
 * of the blocks it references.
 *
 * @param group
 * A dispatch group to associate with the submitted block.
 * The result of passing NULL in this parameter is undefined.
 *
 * @param queue
 * The dispatch queue to which the block will be submitted for asynchronous
 * invocation.
 *
 * @param block
 * The block to perform asynchronously.
 */

2.Dispatch Group Wait

在Group中也可以使用dispatch_group_wait函數(shù)僅等待全部處理執(zhí)行結(jié)束。

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

forever的意思是永遠(yuǎn)等待,只要Group里面的處理尚未執(zhí)行,就會一直等待。

如同dispatch_after一樣,制定等待間隔為1秒的處理如下:

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, ^{
        NSLog(@"blk0");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk1");
    });
    dispatch_group_async(group, queue, ^{
        NSLog(@"blk2");
    });
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
    
    long result = dispatch_group_wait(group, time);
    
    if(result == 0){
        //屬于Dispatch Group的處理全部執(zhí)行結(jié)束
        NSLog(@"done");
    }else{
        //屬于Dispatch Group的某一個處理還在進行 或者 超時了
        NSLog(@"doing");
    }

其中,dispatch_group_wait(group, time)中的time是time-ou時間,如果到了time制定的時間還沒有完成處理,就直接返回非零值,表示超時了。

如果不想等待,直接判定,可以把時間設(shè)定為DISPATCH_TIME_NOW。如果要一直等待,就設(shè)定為DISPATCH_TIME_FOREVER。

3.notifywait

在主線程RunLoop的每次循環(huán)中,可檢查執(zhí)行是否結(jié)束,從而不耗費多余的等待時間,雖然這樣也可以,但一般這種情況下,還是推薦使用notify,函數(shù)追加結(jié)束處理到Main Dispatch Queue中們可以簡化源代碼。

5.處理寫入處理的數(shù)據(jù)競爭

寫入處理不能并行執(zhí)行,會引發(fā)數(shù)據(jù)競爭,但是讀取處理可以,因此,為了高效訪問,讀取處理可以追加到Concurrent Dispatch Queue,寫入處理可以在任一個讀取處理沒有執(zhí)行的狀態(tài)下,追加到Serial Dispatch Queue中(寫入處理結(jié)束之前,讀取處理不可執(zhí)行)。

使用dispatch_barrier_async函數(shù)

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_async(queue, ^{
        NSLog(@"read 0");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 1");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 2");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 3");
    });
    
    /*
     * 寫入處理
     */
    dispatch_barrier_async(queue, ^{
        NSLog(@"write");
    });
    
    dispatch_async(queue, ^{
        NSLog(@"read 4");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 5");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 6");
    });
    dispatch_async(queue, ^{
        NSLog(@"read 7");
    });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容