多線程- NSThread、GCD、NSOperation/NSOperationQueue

一、多線程簡介:

所謂多線程是指一個 進(jìn)程 -- process可以理解為系統(tǒng)中正在運行的一個應(yīng)用程序)中可以開啟多條 線程 -- thread線程是進(jìn)程的基本執(zhí)行單元,一個進(jìn)程的所有任務(wù)都在線程中執(zhí)行,每1個進(jìn)程至少要有1條線程),多條線程可以同時執(zhí)行不同的任務(wù) -- task。CPU每一個核同一時間只能執(zhí)行一條線程。多線程對于單核來講就是讓CPU快速的在多個線程之間進(jìn)行調(diào)度(并發(fā))。而多核處理系統(tǒng)可以讓多個線程實現(xiàn)真正的同時進(jìn)行(并行)。

多線程優(yōu)點:

  • 可以提高應(yīng)用程序的執(zhí)行效率。
  • 可以提高應(yīng)用程序在多核系統(tǒng)上的實時性能,提高資源利用率。

多線程缺點:

  • 開啟線程需要一定的內(nèi)存空間,默認(rèn)的,主線程占用1M,一條子線程占用棧區(qū)內(nèi)存512KB,同時,線程過多會導(dǎo)致CPU在線程上調(diào)度上的開銷比較大。
  • 在應(yīng)用程序中執(zhí)行多個路徑可能會為代碼增加相當(dāng)多的復(fù)雜性。同時,每個線程必須與其他線程協(xié)調(diào)其動作,以防止它破壞應(yīng)用程序的狀態(tài)信息。因為單個應(yīng)用程序中的線程共享相同的內(nèi)存空間,所以它們可以訪問所有相同的數(shù)據(jù)結(jié)構(gòu)。如果兩個線程同時操作相同的數(shù)據(jù)結(jié)構(gòu),一個線程可能會以破壞生成的數(shù)據(jù)結(jié)構(gòu)的方式覆蓋其他線程的更改。

在iOS中,一個iOS程序運行后,默認(rèn)會開啟1條線程,稱為“主線程”或“UI線程”,主線程的主要作用是顯示\刷新UI界面、處理UI事件,如點擊、滾動、拖拽等事件。需要注意的是:別將比較耗時的操作放到主線程中,會影響程序流暢度。

iOS中常見的多線程技術(shù)一般有三個:NSThread、NSOperation、GCD,其中GCD是目前蘋果官方比較推薦的方式。

二、NSThread:

NSThread可以直接操控線程對象,非常直觀和方便。但是它的生命周期有時需要我們手動管理,一般用來查看當(dāng)前線程是否是主線程。下面來看看它的一些常見用法:

  • @property (class, readonly, strong) NSThread *currentThread;: 獲取當(dāng)前線程。一般與isMainThread結(jié)合判斷當(dāng)前線程是否為主線程。

  • + (void)detachNewThreadWithBlock:(void (^)(void))block:采用block形式創(chuàng)建一個新的線程,創(chuàng)建后自動啟動,iOS10.0系統(tǒng)以后才能使用。

  • + (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;:采用方法選擇器方法創(chuàng)建線程,創(chuàng)建后自動啟動,可以傳遞參數(shù)。

  • @property (readonly, retain) NSMutableDictionary *threadDictionary;:每個線程都維護(hù)一個在線程任何地方都能獲取的字典。獲取一個NSMutableDictionary對象后,可以添加我們需要的字段和數(shù)據(jù)。默認(rèn)是空的。注意這里是retain!

  • + (void)sleepUntilDate:(NSDate *)date;+ (void)sleepForTimeInterval:(NSTimeInterval)ti;:阻塞線程,需要注意的是:線程死了不能復(fù)生!

  • + (void)exit;:退出當(dāng)前線程。

  • + (double)threadPriority;+ (BOOL)setThreadPriority:(double)p;:設(shè)置線程優(yōu)先級,線程優(yōu)先級的取值范圍為0.0~1.0之間,1.0表示線程的優(yōu)先級最高,默認(rèn)為0.5。需要注意的是,不是優(yōu)先級高就會先走當(dāng)前線程,結(jié)束后再走其他線程,依然是來回調(diào)用,但是優(yōu)先級高的線程,調(diào)用的概率、頻率會很大。

  • @property double threadPriority:優(yōu)先級,將要被廢棄,可以使用下面的屬性替代。

  • @property NSQualityOfService qualityOfService:該屬性,8.0后才能使用。其中NSQualityOfService是枚舉類型。

// 以下服務(wù)質(zhì)量(QoS)用于向系統(tǒng)表明工作的性質(zhì)和重要性。 它們被系統(tǒng)用于管理各種資源。
// 資源爭奪期間,較高的QoS類別獲得更多的資源(除default外優(yōu)先級依次降低)
typedef NS_ENUM(NSInteger, NSQualityOfService) {
    //用于直接涉及提供交互式UI的工作,例如處理事件或繪圖到屏幕
    NSQualityOfServiceUserInteractive = 0x21,
    
    //用于執(zhí)行用戶明確請求的工作,并且必須立即呈現(xiàn)哪些結(jié)果,以便進(jìn)一步進(jìn)行用戶交互。
    NSQualityOfServiceUserInitiated = 0x19,
    
    //用于執(zhí)行用戶不太可能立即等待結(jié)果的工作。
    //這項工作將以節(jié)能的方式運行,在資源受到限制時尊重更高的QoS工作。
    NSQualityOfServiceUtility = 0x11,
    
    //用于非用戶啟動或可見的工作。
    //它將以最有效的方式運行,同時最大限度地尊重更高的QoS工作。
    NSQualityOfServiceBackground = 0x09,

    //表示缺少Q(mào)oS信息。每當(dāng)有可能的QoS信息,將從其他來源推斷。
    //如果這樣的推斷是不可能的,則使用UserInitiated和Utility之間的QoS。
    NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);
  • @property (nullable, copy) NSString *name:設(shè)置線程名字。

  • @property NSUInteger stackSize:設(shè)定新的??臻g大小。??臻g的大小必須在線程的啟動之前設(shè)定,即在調(diào)用NSThread的start方法之前通過setStackSize: 設(shè)置。

  • @property (readonly) BOOL isMainThread/- (BOOL)isMainThread;/+ (BOOL)isMainThread;:判斷當(dāng)前線程是否為主線程。

  • + (NSThread *)mainThread;:獲取主線程。

  • - (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument:創(chuàng)建線程,需要調(diào)用-start方法才會開啟線程。

  • - (instancetype)initWithBlock:(void (^)(void))block:采用block形式創(chuàng)建一個新的線程,同樣需要調(diào)用-start方法才會開啟線程,10.0以后才能使用。

  • @property (readonly, getter=isExecuting) BOOL executing:判斷線程是否正在執(zhí)行。

  • @property (readonly, getter=isFinished) BOOL finished:判斷線程是否已經(jīng)完成。

  • @property (readonly, getter=isCancelled) BOOL cancelled:判斷線程是否已經(jīng)被取消。

  • - (void)cancel:取消線程。

  • - (void)start: 開始線程。

  • - (void)main:線程入口函數(shù)(包含start功能)。 相當(dāng)于重新走一次線程。

基于NSThread來實現(xiàn)的NSObject的擴(kuò)展方法:

  • - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;:在主線程中執(zhí)行,wait表示是否線程任務(wù)完成執(zhí)行,array:指定了線程中 Runloop 的 Modes(有兩種:kCFRunLoopDefaultModekCFRunLoopCommonModes)。

  • - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;:相當(dāng)于上面的方法指定modes為kCFRunLoopCommonModes。

  • - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array / - (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait:在指定的線程中運行。

以上四個方法常用于線程間的通訊


  • - (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg:所有對象都有能力產(chǎn)生一個新的線程并使用它來執(zhí)行其中的方法。這一方法隱式的創(chuàng)建一個新的線程并執(zhí)行其方法。

二、GCD:

Grand Central Dispatch (GCD)是異步執(zhí)行任務(wù)的技術(shù)之一,它使用了非常簡潔的方法實現(xiàn)了極為復(fù)雜繁瑣的多線編程。

1、隊列的理解:
  • 調(diào)度隊列 -- Dispatch Queues :顯示如何在應(yīng)用程序中同時執(zhí)行任務(wù)。一個調(diào)度隊列會順序或同時卻始終執(zhí)行一個先入先出(FIFO)的順序任務(wù)。(換句話說,調(diào)度隊列總是以與隊列中相同的順序進(jìn)行隊列啟動任務(wù)。)調(diào)度隊列用dispatch_queue_t 類型表示,其創(chuàng)建函數(shù)為dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attire);,其中第一個參數(shù)用于指定隊列名稱,一般用 逆序全程域名+對象名 的格式來命名,第二個參數(shù)是調(diào)度隊列的屬性可以傳:
    • DISPATCH_QUEUE_SERIAL/NULL -- 創(chuàng)建活躍的(不需要手動激活的)串行隊列;
    • DISPATCH_QUEUE_CONCURRENT -- 創(chuàng)建活躍的并行隊列;
    • DISPATCH_QUEUE_SERIAL_INACTIVE -- 創(chuàng)建非活躍狀態(tài)的串行隊列,需要調(diào)用 dispatch_activate()函數(shù)激活,未激活隊列無法使用。可以使用dispatch_set_target_queue()更改處于非活動狀態(tài)的隊列的優(yōu)先級。一旦最初不活動的隊列被激活,優(yōu)先級的更改就不再允許。
    • DISPATCH_QUEUE_CONCURRENT_INACTIVE -- 創(chuàng)建非活躍狀態(tài)的并行隊列`

調(diào)度隊列一般有兩種類型:

  • 串行調(diào)度隊列 -- Serial Dispatch Queues :也稱為 private dispatch queues ,按照任務(wù)被添加到隊列的順序一次執(zhí)行一個任務(wù)。串行隊列通常用于同步對特定資源的訪問。可以根據(jù)需要創(chuàng)建任意數(shù)量的串行隊列,并且每個隊列相對于所有其他隊列并發(fā)運行。也就是說,如果你創(chuàng)建四個串行隊列,則每個隊列一次只會執(zhí)行一個任務(wù),但最多可以同時執(zhí)行四個任務(wù)。

  • 并發(fā)調(diào)度隊列 -- Concurrent Dispatch Queues,這是全局隊列 global dispatch queue 的一種,同時執(zhí)行一個或多個任務(wù),但是任務(wù)仍按照添加到隊列的順序啟動。當(dāng)前執(zhí)行的任務(wù)在由調(diào)度隊列管理的不同線程上運行。

除了自己創(chuàng)建隊列外,還可以獲取系統(tǒng)提供的隊列:

  • 主調(diào)度隊列 -- Main Dispatch Queue,主調(diào)度隊列是一個全局可用的 串行隊列,用于執(zhí)行應(yīng)用程序主線程上的任務(wù)。該隊列與應(yīng)用程序的運行循環(huán)(runLoop)一起工作。

    • 獲取方法:dispatch_queue_t mainQueue = dispatch_get_main_queue();
  • 全局隊列 -- Global Dispatch Queue,是所有應(yīng)用程序都能夠使用的并發(fā)調(diào)度隊列(Concurrent Dispatch Queues)。

    • 獲取方法:dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
      • 當(dāng)使用此函數(shù)返回的隊列時調(diào)用dispatch_suspend() -- 掛起某個隊列,dispatch_resume() -- 恢復(fù)某個隊列,dispatch_set_context() -- 等函數(shù)將不起作用。
      • 參數(shù)identifier可以使用qos_class_t中定義的服務(wù)質(zhì)量類或dispatch_queue_priority_t中定義的優(yōu)先級。下面是其常量定義與對應(yīng)關(guān)系:
        • QOS_CLASS_USER_INTERACTIVE :用戶交互即便額
        • QOS_CLASS_USER_INITIATED -- DISPATCH_QUEUE_PRIORITY_HIGH : 高優(yōu)先級
        • QOS_CLASS_DEFAULT -- DISPATCH_QUEUE_PRIORITY_DEFAULT:默認(rèn)優(yōu)先級
        • QOS_CLASS_UTILITY -- DISPATCH_QUEUE_PRIORITY_LOW:低優(yōu)先級
        • QOS_CLASS_BACKGROUND --DISPATCH_QUEUE_PRIORITY_BACKGROUND`:后臺執(zhí)行
      • 參數(shù)flags:現(xiàn)階段暫時用不到,一般傳 0。
2、GCD中有2個用來執(zhí)行任務(wù)的函數(shù):
  • void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);:異步執(zhí)行任務(wù)。

  • void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);:同步執(zhí)行任務(wù)。

關(guān)于同步、異步、并發(fā)、串行的概念,簡單來講同步異步是針對是否開辟新線程來講的,而并發(fā)串行是針對隊列中任務(wù)的執(zhí)行順序來講的,關(guān)于具體概念及關(guān)系:

  • 同步 -- sync: 只能在當(dāng)前線程按先后順序依次執(zhí)行,不開啟新線程。
  • 異步 -- async: 可以在當(dāng)前線程開啟多個新線程執(zhí)行,可不按順序執(zhí)行。
  • 并發(fā) -- concurrency: 線程執(zhí)行可以同時一起進(jìn)行執(zhí)行。
  • 串行 -- serial: 線程執(zhí)行只能依次逐一先后有序的執(zhí)行。
?? 系統(tǒng)全局隊列 系統(tǒng)主隊列 creat串行隊列 creat并發(fā)隊列
同步(sync),均不開辟新線程,在本線程上運行 不能這樣使用 不能這樣使用,會造成死鎖?。?! 在某一線程上串行 在某一線程上串行
異步(async) 每次都會開辟新線程,并發(fā) 不開辟新線程,在某一線程上串行,獨立直接使用就是主線程上 會開辟一條新線程,在新的線程上串行 每次都會開辟新線程,并發(fā)

可以如下示例一樣去驗證以加深理解:

    NSLog(@"主線程 == %@", [NSThread currentThread]);
    // 調(diào)度隊列類型 dispatch_queue_t
    dispatch_queue_t serialQueue, concurrentQueue, mainQueue, globalQueue;
    // 調(diào)度隊列創(chuàng)建函數(shù)創(chuàng)建串行隊列
    serialQueue = dispatch_queue_create("com.atx610.NewMTX.serialQueue", DISPATCH_QUEUE_SERIAL);
    // 調(diào)度隊列創(chuàng)建函數(shù)創(chuàng)建并行隊列
    concurrentQueue = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    // 獲取主隊列
    mainQueue = dispatch_get_main_queue();
    // 獲取全局隊列
    globalQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    
    dispatch_async(serialQueue, ^{
        NSLog(@"異步,串行 == %@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"異步,串行 == %@", [NSThread currentThread]);
    });
    dispatch_async(serialQueue, ^{
        NSLog(@"異步,串行 == %@", [NSThread currentThread]);
    });
    
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步,串行 == %@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步,串行 == %@", [NSThread currentThread]);
    });
    dispatch_sync(serialQueue, ^{
        NSLog(@"同步,串行 == %@", [NSThread currentThread]);
    });
3、調(diào)整隊列的優(yōu)先級

對于主隊列和全局隊列的優(yōu)先級是一般是無法更改的,如果更改會發(fā)生不可預(yù)知的狀況,但不一定會崩潰。一般調(diào)整只是針對使用dispatch_queue_creat ()函數(shù)創(chuàng)建的隊列,且一般更改的是將要進(jìn)行異步的并發(fā)的對列。默認(rèn)生成的隊列優(yōu)先級為默認(rèn)優(yōu)先級,一般操作不立即生效,但是如果隊列處于未激活狀態(tài)則會立即生效。

  • 函數(shù):void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t _Nullable queue);,第一個參數(shù)是目的參數(shù),即想要更改優(yōu)先級的隊列,第二個參數(shù)是目標(biāo)參數(shù)也是隊列,一般使用全局隊列dispatch_get_global_queue (),即讓第一個參數(shù)隊列的優(yōu)先級與第二個參數(shù)隊列的優(yōu)先級保持一致。
4、時間函數(shù)的應(yīng)用

在GCD中時間一般使用dispatch_time_t類型來表示,系統(tǒng)提供了兩個相對時間常量:

  • DISPATCH_TIME_NOW: 現(xiàn)在
  • DISPATCH_TIME_FOREVER:永遠(yuǎn)

(1)、創(chuàng)建相對時間:
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);函數(shù)相對于默認(rèn)時鐘創(chuàng)建新的dispatch_time_t或修改現(xiàn)有的dispatch_time_t

  • 第一個參數(shù)when: 是一個時間參照,可以使用自定義的dispatch_time_t對象,也可以使用通提供的時間常量,一般使用DISPATCH_TIME_NOW,如果傳入0,那將使用mach_absolute_time()是一個CPU依賴函數(shù),返回一個基于系統(tǒng)啟動后的時鐘"嘀嗒"數(shù))的結(jié)果。
  • 第二個參數(shù)delta:指相對于第一個參數(shù)的時間增量,單位是ull 即納秒數(shù)。1s(秒) = 1000 ms(毫秒)= 1000000 μm(微妙)= 1000000000 ns(納秒),所以這里可以用系統(tǒng)提供的幾個常量:
#define NSEC_PER_SEC 1000000000ull // 1s
#define NSEC_PER_MSEC 1000000ull // 1ms
#define USEC_PER_SEC 1000000ull // 1ms
#define NSEC_PER_USEC 1000ull // 1μm

//舉例,表示距離現(xiàn)在10秒后時間點
dispatch_time_t newTime = dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC);

(2)、創(chuàng)建絕對時間:
dispatch_time_t dispatch_walltime(const struct timespec *_Nullable when, int64_t delta); 這一函數(shù)常用來創(chuàng)建基于一個絕對時間的絕對時間,例如想得到2018年8月8號8時8分8秒向后再添加8秒的時間。

  • 第一個參數(shù)when: 是一個結(jié)構(gòu)體類型時間參數(shù)(如下:)。其中tv_sec為創(chuàng)建struct timeval時到Epoch的秒數(shù),tv_usec為微秒數(shù),即秒后面的零頭。比如當(dāng)前我敲代碼時的tv_sec為6666666666,tv_usec為666666,即當(dāng)前時間距Epoch時間6666666666秒,666666微秒。
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC    struct timespec
_STRUCT_TIMESPEC
{
    __darwin_time_t tv_sec;
    long            tv_nsec;
};
#endif 
  • 第二個參數(shù)delta:與上面創(chuàng)建相對時間第二個參數(shù)一樣。

使用:

    NSString * dateString = @"2018-08-08 08:08:08";
    NSDateFormatter * formatDate = [[NSDateFormatter alloc] init];
    formatDate.dateFormat = @"yyyy-MM-dd HH:mm:ss";
    NSDate * yearDate = [formatDate dateFromString:dateString];
    NSTimeInterval interval = [yearDate timeIntervalSince1970];
    
    double second, subSecond;
    struct timespec time;
    // 將浮點數(shù)分解為整數(shù)和小數(shù)
    subSecond = modf(interval, &second);
    time.tv_sec = second;
    time.tv_nsec = subSecond * NSEC_PER_SEC;
    dispatch_time_t absoluteTime = dispatch_walltime(&time, 8 * NSEC_PER_SEC);
// 這樣就獲得了一個相對于具體時間的絕對時間

(3)、延遲函數(shù):
iOS中做一個延遲一段時間的方法有很多,其中在GDC中可以使用void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);函數(shù)來做延遲操作。

首先來舉個??:

    dispatch_time_t newTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
    dispatch_after(newTime, dispatch_get_main_queue(), ^{
        NSLog(@"三秒后的我~");
    });
  • 在這里需要注意:dispatch_after函數(shù)不是在指定時間后執(zhí)行某種處理,而只是在指定時間將某種處理追加到Dispatch Queue中,但是不一定是立即執(zhí)行該處理!

  • 第一個參數(shù):when:指定時間,可以直接使用dispatch_time()或者dispatch_walltime()函數(shù)做參數(shù)。第二個參數(shù):queue:指定要將處理追加到那個隊列中。第三個參數(shù):block:要追加的處理。

  • 如上面的例子所示,三秒后將追加處理加入主隊列中,因為Main Dispatch Queue在主線程中的RunLoop中執(zhí)行,所以在每隔1/60秒執(zhí)行一次的RunLoop中,Block最快會在三秒后執(zhí)行,最慢會在3 + 1/60秒后執(zhí)行,但是其他線程不一定。

5、調(diào)度組 -- Dispatch groups

Dispatch groups是在一個或多個任務(wù)執(zhí)行完成之前阻止線程的一種方式。類似于將所有任務(wù)加入到串行隊列中,當(dāng)最后一個任務(wù)執(zhí)行完畢后再做下一步操作,但是不同的是Dispatch groups一般針對異步并發(fā)隊列,因為你無法檢測幾個任務(wù)誰是最后執(zhí)行完的,但是這幾個任務(wù)不全部完成就無法進(jìn)行下一步操作,或者下一步操作出現(xiàn)未知狀況,Dispatch groups就是解決這樣的問題的。

  • 使用dispatch_group_t表示組類型,其創(chuàng)建函數(shù):dispatch_group_t dispatch_group_create(void);

  • 使用調(diào)度組進(jìn)行異步操作時使用dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);函數(shù)來代替dispatch_async()函數(shù)。

  • long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);:直到與組關(guān)聯(lián)的所有任務(wù)塊都已完成 或 指定的時間已經(jīng)超過時返回。成功返回0,在即指定時間內(nèi)完成了組相關(guān)的所有任務(wù),錯誤為非0,即超時。

  • void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);:該函數(shù)與上面最大的不同就是會一直等到組關(guān)聯(lián)的所有任務(wù)塊都已完成然后在會將本函數(shù)將執(zhí)行的Block追加到Dispatch Queue中!

以上兩個方法是配合dispatch_group_async ()一起使用的


  • void dispatch_group_enter(dispatch_group_t group);:手動指示Block已進(jìn)入Dispatch Queue。調(diào)用此函數(shù)表示另一個Block通過dispatch_async()的方法加入了組。 調(diào)用此函數(shù)必須與dispatch_group_leave()配合使用。

  • void dispatch_group_leave(dispatch_group_t group);:手動指示Dispatch Queue中的Block已經(jīng)完成。調(diào)用此函數(shù)表示Block已完成,并通過除dispatch_group_async()之外的方法離開調(diào)度組。

示例:

    dispatch_group_t newGroup = dispatch_group_create();
    dispatch_queue_t queue_change = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_group_async(newGroup, queue_change, ^{
        sleep(1);
        NSLog(@"test_1");
    });
    dispatch_group_async(newGroup, queue_change, ^{
        NSLog(@"test_2");
    });

    dispatch_group_enter(newGroup);
    dispatch_async(queue_change, ^{
        NSLog(@"test_3");
        dispatch_group_leave(newGroup);
    });
    
    dispatch_group_enter(newGroup);
    dispatch_async(queue_change, ^{
        sleep(2);
        NSLog(@"test_4");
        dispatch_group_leave(newGroup);
    });

    dispatch_group_notify(newGroup, queue_change, ^{
        NSLog(@"done,and start new work");
    });

//    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
//    long result = dispatch_group_wait(newGroup, time);
//    if (result == 0) {
//        NSLog(@"在設(shè)置的時間內(nèi),組中的任務(wù)全部完成");
//    }else{
//        NSLog(@"在設(shè)置的時間內(nèi),組中的任務(wù)未全部完成");
//    }

結(jié)果:

test_2
test_3
test_1
test_4
done,and start new work
6、調(diào)度障礙 -- Dispatch Barrier

調(diào)度障礙API是一種向dispatch queue提交障礙Block的機(jī)制,類似于dispatch_async()/ dispatch_sync()API。主要用于在訪問數(shù)據(jù)庫或者文件時,實現(xiàn)高效的讀/寫方案。障礙Block 僅在提交到使用DISPATCH_QUEUE_CONCURRENT屬性 創(chuàng)建的 隊列(并發(fā)隊列)時才有特殊的作用; 在個隊列上,只有在障礙Block前添加到隊列中的所有任務(wù)都已經(jīng)完成,障礙Block才會運行,而在障礙Block完成前,障礙Block之后提交到隊列的任何任務(wù)都將不會運行,直到障礙Block完成。
?需要特別注意的是:當(dāng)提交到全局隊列(global queue)或 未使用 DISPATCH_QUEUE_CONCURRENT屬性創(chuàng)建的隊列時,障礙Block與dispatch_async()/ dispatch_sync()API提交的Block的行為相同!!!

  • void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //異步執(zhí)行障礙Block
  • void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);//同步執(zhí)行障礙Block

示例:

    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.atx610.NewMTX.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
//    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"test_1");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"test_2");
        sleep(2);
        NSLog(@"睡醒了~");
    });
    
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"I Am Barrier!");
    });
    
    dispatch_async(concurrentQueue, ^{
        NSLog(@"test_3");
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"test_4");
    });

使用create函數(shù)創(chuàng)建的隊列運行結(jié)果如下:

test_1
test_2
睡醒了~
I Am Barrier!
test_3
test_4

使用全局隊列運行結(jié)果如下:

test_1
test_2
I Am Barrier!
test_3
test_4
睡醒了~
7、與執(zhí)行次數(shù)相關(guān)的函數(shù):

(1)、dispatch_apply 函數(shù):
?該函數(shù)功能:將一個任務(wù)提交給調(diào)度隊列進(jìn)行多次調(diào)用。此函數(shù)在返回之前等待任務(wù)塊完成,即 會將指定次數(shù)的指定Block追加到指定的隊列中并等待全部處理執(zhí)行結(jié)束! 如果目標(biāo)隊列是并發(fā)的,則該塊可能會同時被調(diào)用。

  • void dispatch_apply(size_t iterations, dispatch_queue_t queue, DISPATCH_NOESCAPE void (^block)(size_t));:第一個參數(shù) iterations表示重復(fù)次數(shù),第三個參數(shù)block中有一個參數(shù),表示該block按照重復(fù)次數(shù)添加到queue中時是第幾個添加的。

常見使用方式:

    dispatch_queue_t globalQueue = dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0);
    dispatch_async(globalQueue, ^{
        dispatch_apply(5, globalQueue, ^(size_t index) {
            // 在次并列處理某些事件
            NSLog(@"%zu", index);
        });
        NSLog(@"apply循環(huán) 結(jié)束");
        // 只有apply中的事件全部處理完畢了才會進(jìn)行后面的操作
        dispatch_async(dispatch_get_main_queue(), ^{
            // 主隊列更新界面等操作
            NSLog(@"主隊列更新");
        });
    });
0
1
2
3
4
apply循環(huán) 結(jié)束
主隊列更新

(2)、dispatch_once 函數(shù):
?dispatch_once_t 類型來定義一個變量,dispatch_once 函數(shù)來保證在應(yīng)用程序中只執(zhí)行一次指定處理的任務(wù)。

  • void dispatch_once(dispatch_once_t *predicate, DISPATCH_NOESCAPE dispatch_block_t block); :第一個參數(shù)是指向dispatch_once_t的指針,用于測試塊是否已完成。其主要用于實現(xiàn)單例模式:
+ (instancetype)shareManager {
    static dispatch_once_t onceToken;
    static Manager * manager;
    dispatch_once(&onceToken, ^{
        manger = [[self alloc] init];
    });
    return manager;
}
8、調(diào)度信號量 -- Dispatch Semaphore

調(diào)度信號量用dispatch_semaphore_t表示,調(diào)度信號量與傳統(tǒng)信號量相似,但通常效率更高。 調(diào)度信號量只有當(dāng)因為信號量不可用而線程需要被阻塞時才調(diào)用到內(nèi)核。 如果信號量可用,則不進(jìn)行內(nèi)核調(diào)用。

  • dispatch_semaphore_t dispatch_semaphore_create(long value);創(chuàng)建具有初始值的新計數(shù)信號量,傳遞小于零的值將導(dǎo)致返回NULL。

  • long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);;作用:減少計數(shù)信號量,將信號量減1.第二個參數(shù)是超時時間。成功返回零,如果發(fā)生超時則返回非零值。如果在時間內(nèi),當(dāng)信號總量少于等于0的時候,這個函數(shù)就阻塞當(dāng)前線程等待timeout,如果等待的期間信號量值被dispatch_semaphore_signal函數(shù)加1了,即該函數(shù)所處線程獲得了信號量,那么就繼續(xù)向下執(zhí)行并將信號量減1。如果等待期間沒有獲取到信號量或者信號量的值一直為0,那么等到timeout時,其所處線程自動執(zhí)行其后語句。

  • long dispatch_semaphore_signal(dispatch_semaphore_t dsema);:增加計數(shù)信號量, 如果以前的值等于零,此函數(shù)會在返回之前喚醒等待的線程。如果線程被喚醒,此函數(shù)返回非零值。 否則返回零。當(dāng)返回值為0時表示當(dāng)前并沒有線程等待其處理的信號量,其處理的信號量的值加1即可。當(dāng)返回值不為0時,表示其當(dāng)前有一個或多個線程等待其處理的信號量,并且該函數(shù)喚醒了一個等待的線程(當(dāng)線程有優(yōu)先級時,喚醒優(yōu)先級最高的線程;否則隨機(jī)喚醒)。

使用:

    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semap = dispatch_semaphore_create(0);
    dispatch_async(global, ^{
        dispatch_semaphore_wait(semap, DISPATCH_TIME_FOREVER);
        NSLog(@"first");
    });
    NSLog(@"second");
    dispatch_semaphore_signal(semap);

結(jié)果:

second
first
9、其他常見使用GCD函數(shù)
  • void dispatch_suspend(dispatch_object_t object);:暫停在調(diào)度對象上的剩余任務(wù)。這會增加內(nèi)部暫停計數(shù),調(diào)用dispatch_suspend()必須與調(diào)用dispatch_resume()進(jìn)行平衡。

  • void dispatch_resume(dispatch_object_t object);: 是dispatch_suspend()的反向操作,并且消耗暫停計數(shù)。當(dāng)最后一個暫停計數(shù)被消耗時,即暫停計數(shù)為零時,與該對象關(guān)聯(lián)的任務(wù)將再次被調(diào)用。如果指定的對象具有零暫停計數(shù),并且不是非活動的源,則此函數(shù)將導(dǎo)致斷言并且進(jìn)程終止,即Crash。

三、NSOperation / NSOperationQueue:

操作隊列是一種面向?qū)ο蟮姆椒▉矸庋b要異步執(zhí)行的工作,顯示如何使用Objective-C對象來封裝和執(zhí)行任務(wù)。 NSOperation本身是抽象類,只能使用其子類,另外Foundation框架提供了兩個具體的子類:NSBlockOperation、NSInvocationOperation,除此之外還可以自定義繼承自NSOperation的類并實現(xiàn)內(nèi)部相應(yīng)的方法來使用NSOperation。一般NSOperation和NSOperationQueue結(jié)合使用實現(xiàn)多線程并發(fā)。

1、關(guān)于抽象類NSOperation:

  • - (void)start; : 開啟任務(wù)操作。NSOperation對象默認(rèn)按同步方式執(zhí)行,也就是在調(diào)用start方法的那個線程中直接執(zhí)行。

  • - (void)main;: 類的主函數(shù),一般在自己定義繼承自NSOperation的類中重寫該方法以實現(xiàn)執(zhí)行任務(wù)。

  • @property (readonly, getter=isCancelled) BOOL cancelled; : 操作是否已經(jīng)取消。

  • - (void)cancel;:operation開始執(zhí)行之后, 默認(rèn)會一直執(zhí)行操作直到完成,我們也可以調(diào)用cancel方法中途取消操作。

  • @property (readonly, getter=isExecuting) BOOL executing;:操作是否正在執(zhí)行。

  • @property (readonly, getter=isFinished) BOOL finished;:操作是否已經(jīng)完成。

  • @property (readonly, getter=isAsynchronous) BOOL asynchronous;:是否是異步的。

  • @property (readonly, getter=isReady) BOOL ready;:是否準(zhǔn)備就緒。線程start后并不是立即執(zhí)行,而是進(jìn)入一個就緒的狀態(tài)(isReady),由系統(tǒng)調(diào)度執(zhí)行。

  • - (void)addDependency:(NSOperation *)op; :添加依賴關(guān)系。

  • - (void)removeDependency:(NSOperation *)op;:移除依賴關(guān)系。

  • @property (readonly, copy) NSArray<NSOperation *> *dependencies;依賴關(guān)系數(shù)組。

  • @property NSOperationQueuePriority queuePriority;:優(yōu)先級,具體如下:

typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

-@property (nullable, copy) void (^completionBlock)(void);:任務(wù)操作完成后的Block

  • - (void)waitUntilFinished;:阻塞當(dāng)前線程直到此任務(wù)執(zhí)行完畢。

  • @property double threadPriority;:線程的優(yōu)先級

  • @property NSQualityOfService qualityOfService;:服務(wù)質(zhì)量,具體如下:

typedef NS_ENUM(NSInteger, NSQualityOfService) {
    NSQualityOfServiceUserInteractive = 0x21,
    NSQualityOfServiceUserInitiated = 0x19,
    NSQualityOfServiceUtility = 0x11,
    NSQualityOfServiceBackground = 0x09,
    NSQualityOfServiceDefault = -1
} NS_ENUM_AVAILABLE(10_10, 8_0);
  • @property (nullable, copy) NSString *name ;:給操作起個名字,做標(biāo)記。

2、創(chuàng)建NSInvocationOperation對象:
?主要是使用- (nullable instancetype)initWithTarget:(id)target selector:(SEL)sel object:(nullable id)arg;這一方法,在你指定的對象上調(diào)用你指定的方法,還可以傳遞一個參數(shù)。如下所示:

NSString * name = @"lucy";
NSInvocationOperation * invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(logSomethingWithString:) object:name];
[invocationOperation start];
--------------
- (void)logSomethingWithString:(NSString *)name
{
    NSLog(@"%@", name);
}

3、創(chuàng)建NSBlockOperation對象:
?相對而言NSBlockOperation要比NSInvocationOperation好用一點,該類有兩個方法和一個屬性:

  • + (instancetype)blockOperationWithBlock:(void (^)(void))block;:創(chuàng)建并添加任務(wù)。
  • - (void)addExecutionBlock:(void (^)(void))block;:添加并發(fā)任務(wù)。
  • @property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;:獲取并發(fā)的block。

示例:

    NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"001");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"002");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"003");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"004");
    }];
    [blockOperation start];
    NSLog(@"%@", blockOperation.executionBlocks);

4、NSOperationQueue -- 操作隊列:
?操作隊列提供一個異步執(zhí)行的線程,同NSOperation配合使用可以高效利用管理操作和線程。

  • - (void)addOperation:(NSOperation *)op;:添加任務(wù)。
  • - (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;:添加一組任務(wù),并設(shè)置是否阻塞當(dāng)前線程知道操作完成。
  • - (void)addOperationWithBlock:(void (^)(void))block ;:添加一個block形式的operation。
  • @property (readonly, copy) NSArray<__kindof NSOperation *> *operations;:獲取隊列中的操作。
  • @property (readonly) NSUInteger operationCount ;:獲取隊列中的操作的數(shù)量。
  • @property NSInteger maxConcurrentOperationCount;:設(shè)置、獲取最大并發(fā)數(shù)量。設(shè)置完后如果任務(wù)數(shù)量超過了最大并發(fā)數(shù)量,會等待某一任務(wù)完成后再執(zhí)行。
  • @property (getter=isSuspended) BOOL suspended;:隊列是否已經(jīng)暫停。當(dāng)設(shè)置為YES后,它會暫停在此之后添加到隊列中或正在運行任務(wù)之后的任務(wù),在此之前添加的或者正在運行的任務(wù)不會受到影響。繼續(xù)已暫停的隊列只需將該屬性設(shè)為NO即可。
  • @property (nullable, copy) NSString *name;:隊列標(biāo)識。
  • @property NSQualityOfService qualityOfService;:服務(wù)質(zhì)量。
  • @property (nullable, assign) dispatch_queue_t underlyingQueue;:潛在隊列,此屬性的默認(rèn)值為nil??梢詫⒋藢傩缘闹翟O(shè)置為現(xiàn)有的某一隊列,以使本排隊的操作任務(wù)可以散布提交給該調(diào)度隊列中執(zhí)行。需要注意:只有當(dāng)前隊列中沒有任何操作任務(wù)時,才能設(shè)置此屬性的值; 如果operationCount不等于0時設(shè)置此屬性的值會引發(fā)NSInvalidArgumentException。 該屬性的值不能是dispatch_get_main_queue返回的值。 為潛在隊列設(shè)置的服務(wù)質(zhì)量級別將覆蓋當(dāng)前隊列的qualityOfService屬性值。
  • - (void)cancelAllOperations;:取消所有操作。
  • - (void)waitUntilAllOperationsAreFinished;:阻塞當(dāng)前線程,等待所有操作任務(wù)都完成。
  • @property (class, readonly, strong, nullable) NSOperationQueue *currentQueue:獲取執(zhí)行當(dāng)前的NSOperation的NSOperationQueue列隊。
  • @property (class, readonly, strong) NSOperationQueue *mainQueue:獲取系統(tǒng)主線程的NSOperationQueue列隊。

示例:

    - (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    imageViewArray = [NSMutableArray array];
    for (int i = 0; i < 3; i++) {
        UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100 + 100*i, 100, 100)];
        [imageViewArray addObject:imageView];
        [self.view addSubview:imageView];
    }
    NSBlockOperation * blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"001 --- 001, thread == %@", [NSThread currentThread]);
        [imageViewArray[0] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504690123016&di=00957249b318d00f04c80439063671e0&imgtype=0&src=http%3A%2F%2Finuyasha.manmankan.com%2FUploadFiles_6178%2F201004%2F20100427165757723.jpg"]]]];
    }];
    
    NSBlockOperation * blockOperation_copy = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"copy --- 001, thread == %@", [NSThread currentThread]);
        [imageViewArray[1] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504690048974&di=ddc4518682a2e884cd9e88bbf7d9aa00&imgtype=0&src=http%3A%2F%2Fimg.qqu.cc%2Fuploads%2Fallimg%2F150601%2F1-150601201258.jpg"]]]];
    }];
    
    NSBlockOperation * blockOperation_black = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"black --- 001, thread == %@", [NSThread currentThread]);
        [imageViewArray[2] setImage:[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1504689993435&di=c52ca4a5d5f46fc2552d3e97dd4eb241&imgtype=0&src=http%3A%2F%2Fp4.gexing.com%2Fshaitu%2F20120825%2F1336%2F5038647615f85.jpg"]]]];
    }];
    // 添加依賴關(guān)系
    [blockOperation addDependency:blockOperation_copy];
    [blockOperation_copy addDependency:blockOperation_black];
    queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    [queue addOperations:@[blockOperation, blockOperation_copy, blockOperation_black] waitUntilFinished:NO];
    [queue setSuspended:YES];
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [queue setSuspended:NO];
}
效果圖.gif
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容