iOS開發(fā)多線程-NSOperation \ GCD詳解

iOS多線程開發(fā)必須知道的概念名詞:
1. 進(jìn)程
  • 進(jìn)程(process)就是一個(gè)正在執(zhí)行的程序的實(shí)例。也就是說我們的每一個(gè)APP程序在執(zhí)行時(shí)實(shí)例都是一個(gè)進(jìn)程代态,也可以說在APP執(zhí)行時(shí)述呐,它只擁有唯一的一個(gè)進(jìn)程惩淳。
  • 每一個(gè)進(jìn)程都是獨(dú)立的,每一個(gè)進(jìn)程均在專屬的內(nèi)存空間內(nèi)乓搬,iOS中每一個(gè)App(一個(gè)進(jìn)程)都有自己獨(dú)特的內(nèi)存和磁盤空間思犁,別的App(進(jìn)程)是不允許訪問的(越獄除外)。iOS開發(fā)中應(yīng)用程序之間互相調(diào)用包括調(diào)用發(fā)短信缤谎、打電話等進(jìn)程相關(guān)操作的API都被封裝到UIApplication這個(gè)類中了。
  • 每個(gè)進(jìn)程至少擁有一個(gè)線程褐着。
  • 在UNIX和Linux系統(tǒng)中是有進(jìn)程層次結(jié)構(gòu)的坷澡,當(dāng)進(jìn)程創(chuàng)建了另一個(gè)進(jìn)程后,父進(jìn)程和子進(jìn)程就以某種方式繼續(xù)保持聯(lián)系含蓉。子進(jìn)程自身可以創(chuàng)建更多的進(jìn)程频敛,組成一個(gè)進(jìn)程的層次結(jié)構(gòu)。(進(jìn)程只有一個(gè)父進(jìn)程但是可以有0個(gè)馅扣,1個(gè)或多個(gè)子進(jìn)程斟赚。)而Windows中沒有進(jìn)程層次的概念,所有進(jìn)程地位相同差油。唯一類似進(jìn)程層次的地方是在創(chuàng)建進(jìn)程的時(shí)候父進(jìn)程得到一個(gè)特別的令牌(稱為句柄)拗军,該令牌可以用來控制子進(jìn)程任洞。但是父進(jìn)程可以把這個(gè)令牌傳送給其他進(jìn)程,這樣就不存在進(jìn)程層次了发侵。
  • 進(jìn)程有3種狀態(tài)交掏,分別是:
    1)運(yùn)行態(tài)(該時(shí)刻進(jìn)程實(shí)際占用CPU)。
    2)就緒態(tài)(可運(yùn)行刃鳄,但因?yàn)槠渌M(jìn)程正在運(yùn)行而暫時(shí)停止)盅弛。
    3)阻塞態(tài)(除非某種外部事件發(fā)生,否則進(jìn)程不能運(yùn)行)叔锐。
2. 線程
  • 進(jìn)程用于把資源集中到一起挪鹏,而線程是在CUP上被調(diào)度的實(shí)體。
  • 線程擁有自己的寄存器愉烙,用來保存當(dāng)前的工作變量讨盒,稱作線程的上下文。還擁有一個(gè)自己的堆棧齿梁,用來記錄執(zhí)行歷史催植。
  • 線程之間共享同樣的內(nèi)存空間和全局變量,一個(gè)線程可以讀勺择、寫或甚至清除另一個(gè)線程的堆棧创南。
  • 如果多個(gè)線程都是CPU密集型(也稱計(jì)算密集型)那并不能獲得性能上的增強(qiáng),如果是I/O密集型則能加快程序執(zhí)行速度省核。
進(jìn)程和線程的關(guān)系
  • 線程只是一個(gè)進(jìn)程中的不同執(zhí)行路徑稿辙。線程有自己的堆棧和局部變量,但線程之間沒有單獨(dú)的地址空間气忠,它們共享進(jìn)程的地址空間邻储。一個(gè)線程crash就等于整個(gè)進(jìn)程crash。
  • 進(jìn)程的創(chuàng)建和操作開銷很大旧噪,某種意義上講線程就相當(dāng)于輕量級(jí)的進(jìn)程吨娜。多線程使用的其中一個(gè)理由就是因?yàn)榫€程比進(jìn)程更輕量級(jí),線程比進(jìn)程更容易(即更快)的創(chuàng)建和撤銷淘钟。在許多系統(tǒng)中宦赠,創(chuàng)建一個(gè)線程比創(chuàng)建一個(gè)進(jìn)程要快10~100倍。


    進(jìn)程線程關(guān)系示意圖.jpeg

多線程(英語:multithreading)米母,是指從軟件或者硬件上實(shí)現(xiàn)多個(gè)線程并發(fā)執(zhí)行的技術(shù)勾扭。

多線程好處多的同時(shí)也易引發(fā)一些問題,“數(shù)據(jù)競爭”-多個(gè)線程操作同一資源時(shí)可能會(huì)導(dǎo)致數(shù)據(jù)的不一致铁瞒;“死鎖”-兩個(gè)或兩個(gè)以上的線程在執(zhí)行過程中妙色,因爭奪資源而造成的一種互相等待的現(xiàn)象;使用太多的線程會(huì)消耗大量內(nèi)存慧耍,因?yàn)槊總€(gè)線程都有自己的寄存器身辨。進(jìn)程太多的時(shí)候也有消耗太大的問題丐谋。

3. 串行(serial) & 并發(fā)(concurrent)& 并行

串行是同步線程的實(shí)現(xiàn)方式,就是任務(wù)A執(zhí)行結(jié)束才能開始執(zhí)行B栅表,單個(gè)線程只能執(zhí)行一個(gè)任務(wù)笋鄙。
并發(fā)并行其實(shí)是異步線程實(shí)現(xiàn)的兩種形式。并行其實(shí)是真正的異步怪瓶,多核CUP可以同時(shí)開啟多條線程供多個(gè)任務(wù)同時(shí)執(zhí)行,互不干擾萧落。并發(fā)是偽異步,單個(gè)CUP一個(gè)時(shí)刻只能有一個(gè)線程執(zhí)行洗贰,想執(zhí)行多個(gè)任務(wù)就必須不斷切換執(zhí)行任務(wù)的線程找岖。

4. 同步 & 異步

同步:多個(gè)任務(wù)情況下,一個(gè)任務(wù)A執(zhí)行結(jié)束敛滋,才可以執(zhí)行另一個(gè)任務(wù)B许布。只存在一個(gè)線程。
異步:多個(gè)任務(wù)情況下绎晃,一個(gè)任務(wù)A正在執(zhí)行蜜唾,同時(shí)可以執(zhí)行另一個(gè)任務(wù)B。任務(wù)B不用等待任務(wù)A結(jié)束才執(zhí)行庶艾。存在多條線程袁余。

5. 調(diào)度隊(duì)列(Dispatch Queue)

調(diào)度隊(duì)列是執(zhí)行處理的隊(duì)列也是GCD的基本概念,它按照?qǐng)?zhí)行任務(wù)添加的順序(即FIFO-先進(jìn)先出順序)執(zhí)行處理咱揍。調(diào)度隊(duì)列在執(zhí)行處理時(shí)存在兩種Dispatch Queue颖榜,一種是等待現(xiàn)在執(zhí)行中處理的Serial Dispatch Queue,另一種是不等待現(xiàn)在執(zhí)行中處理的Concurrent Dispatch Queue,稍后會(huì)對(duì)這兩種隊(duì)列詳細(xì)的介紹煤裙。官方的說法是有三種隊(duì)列掩完,還要一種叫Main dispatch queueMain dispatch queue其實(shí)也可以歸為Serial Dispatch Queue硼砰,不過由于它是主線程隊(duì)列所以單拿了出來且蓬。

iOS多線程技術(shù)對(duì)比

  • pthread

pthread(POSIX thread)是一套通用的多線程API,適用于Unix题翰、Linux恶阴、Windows等系統(tǒng),跨平臺(tái)遍愿、可移植的C語言框架存淫,線程生命周期由開發(fā)者管理耘斩,使用難度大沼填。GCD的底層實(shí)現(xiàn)庫中也有用到Libc(pthreads)

  • NSThread

NSThread是這幾種方法里面相對(duì)輕量級(jí)的括授,但需要管理線程的生命周期坞笙、同步岩饼、加鎖問題,這會(huì)導(dǎo)致一定的性能開銷薛夜,同時(shí)在多個(gè)線程開發(fā)時(shí)不便于開發(fā)維護(hù)籍茧。詳細(xì)使用可參考這篇博客

  • NSOperation

  • NSOperation是基于OC實(shí)現(xiàn)的梯澜,它以面向?qū)ο蟮姆绞椒庋b了需要執(zhí)行的操作寞冯,然后可以將這個(gè)操作放到一個(gè)NSOperationQueue中去異步執(zhí)行。它是線程安全的晚伙,開發(fā)者不必關(guān)心線程管理吮龄、同步等問題。
  • NSOperation類是一個(gè)抽象類來封裝一個(gè)任務(wù)相關(guān)的代碼和數(shù)據(jù)咆疗,不能直接被使用漓帚。可以使用它的兩個(gè)子類
    NSInvocationOperationNSBlockoperation來執(zhí)行實(shí)際的任務(wù)午磁,當(dāng)然你也可以自己封裝一個(gè)子類來實(shí)現(xiàn)(只需要重載-(void)main這個(gè)方法尝抖,在這個(gè)方法里面添加需要執(zhí)行的操作。)迅皇。
  • NSOperation可以取消添加的執(zhí)行任務(wù)昧辽。一個(gè)NSOperation對(duì)象是一個(gè)單次對(duì)象(single-shot object)只能執(zhí)行一次任務(wù),不能再次執(zhí)行它喧半。
  • GCD

  • Grand Central Dispatch(GCD)是蘋果開發(fā)的基于XNU內(nèi)核級(jí)線程管理技術(shù)奴迅,優(yōu)化對(duì)多核處理器的支持。

  • GCD是一個(gè)基于線程池的任務(wù)并行執(zhí)行模式挺据。其基本思想是將線程池的管理從開發(fā)人員手中轉(zhuǎn)移出來取具,并更接近操作系統(tǒng)(更高效)。開發(fā)人員不用管理線程扁耐,只需要把任務(wù)添加到執(zhí)行隊(duì)列就可以暇检。

  • GCD基于C語言實(shí)現(xiàn),不過使用了Block婉称,因而API非常簡潔易用块仆。

  • GCD需要開發(fā)者釋放自己創(chuàng)建的隊(duì)列Dispatch Queue,系統(tǒng)提供的標(biāo)準(zhǔn)隊(duì)列是全局的所以不用釋放王暗。

  • NSOperation相對(duì)于GCD的優(yōu)勢:

1>任務(wù)可以添加依賴關(guān)系悔据,即便是異步執(zhí)行也可以給部分任務(wù)執(zhí)行順序;
2>添加的任務(wù)如果不是已經(jīng)執(zhí)行俗壹,取消是比較方便的科汗;
3>可以監(jiān)聽任務(wù)的狀態(tài)進(jìn)而做其他的處理;
4>任務(wù)優(yōu)先級(jí)設(shè)置比較方便绷雏,GCD可以給隊(duì)列設(shè)置優(yōu)先級(jí)而且只有3種头滔;
5>最大并發(fā)數(shù)設(shè)置怖亭;GCD實(shí)現(xiàn)比較復(fù)雜些(用信號(hào)量);
6>繼承NSOperation自定義坤检。

NSOperation

NSOperation實(shí)現(xiàn)多線程主要步驟是:
1> 封裝執(zhí)行的操作到一個(gè)NSOperation對(duì)象中

2> 將封裝的NSOperation對(duì)象添加到NSOperationQueue

3> 系統(tǒng)會(huì)自動(dòng)為NSOperation對(duì)象封裝的任務(wù)開啟一條線程執(zhí)行 或者 不加入隊(duì)列調(diào)用-(void)start:在主線程執(zhí)行

- (void)operationManage{
    // 這樣在主線程執(zhí)行其實(shí)是畫蛇添足的兴猩,只是為了做說明而寫
    NSBlockOperation *downloadImgPng = [NSBlockOperation blockOperationWithBlock:^{
       //downloadImage 任務(wù)
        NSLog(@"png -- 當(dāng)前線程%@",[NSThread currentThread]);
    }];
    [downloadImgPng start];
}
//  NSInvocationOperation執(zhí)行方式
- (void)operationManage{
    NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadImage) object:nil];

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    // 同一時(shí)間最多開啟的線程數(shù) 
    operationQueue.maxConcurrentOperationCount = 6;
    [operationQueue addOperation:invocationOperation];

    // 取消所有隊(duì)列中的任務(wù)
//    [operationQueue cancelAllOperations];
    
    // 取消執(zhí)行的任務(wù)
//    [invocationOperation cancel];
}

- (void)downloadImage{

}
//  NSBlockOperation執(zhí)行方式
- (void)operationManage{
    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
       //downloadImage 任務(wù)
    }];
    
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    // 同一時(shí)間最多開啟的線程數(shù)
    operationQueue.maxConcurrentOperationCount = 6;
    [operationQueue addOperation:blockOperation];
}
- (void)operationManage{
    /*
     * 多任務(wù)隊(duì)列添加依賴--串行執(zhí)行
     * 一般多任務(wù)隊(duì)列默認(rèn)是并行執(zhí)行,添加依賴可按依賴條件順序執(zhí)行
     */
    NSBlockOperation *downloadImgPng = [NSBlockOperation blockOperationWithBlock:^{
       //downloadImage 任務(wù)
        NSLog(@"png -- 當(dāng)前線程%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *downloadImgJpg = [NSBlockOperation blockOperationWithBlock:^{
        //downloadImage 任務(wù)
        NSLog(@"jpg -- 當(dāng)前線程%@",[NSThread currentThread]);
    }];
    [downloadImgJpg addDependency:downloadImgPng];
    
    NSBlockOperation *downloadImgPdf = [NSBlockOperation blockOperationWithBlock:^{
        //downloadImage 任務(wù)
        NSLog(@"pdf -- 當(dāng)前線程%@",[NSThread currentThread]);
    }];
    [downloadImgPdf addDependency:downloadImgJpg];
    
    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    // 同一時(shí)間最多開啟的線程數(shù)
    operationQueue.maxConcurrentOperationCount = 6;

    [operationQueue addOperations:@[downloadImgPng,downloadImgJpg,downloadImgPdf] waitUntilFinished:NO];
}

NSOperation對(duì)象執(zhí)行狀態(tài)監(jiān)聽

NSOperation對(duì)象執(zhí)行狀態(tài).png

/*
此屬性指定應(yīng)用于添加到隊(duì)列中的操作對(duì)象的服務(wù)級(jí)別早歇。如果操作對(duì)象具有顯式的服務(wù)水平集倾芝,則使用該值。此屬性的默認(rèn)值取決于您創(chuàng)建隊(duì)列的方式箭跳。自己創(chuàng)建的隊(duì)列蛀醉,默認(rèn)值是NSOperationQualityOfServiceBackground。為隊(duì)列的mainqueue方法返回衅码,默認(rèn)值是nsoperationqualityofserviceuserinteractive和不能改變的拯刁。

服務(wù)級(jí)別影響給定操作對(duì)象訪問系統(tǒng)資源的優(yōu)先級(jí),如CPU時(shí)間逝段、網(wǎng)絡(luò)資源垛玻、磁盤資源等。具有較高服務(wù)質(zhì)量級(jí)別的操作在系統(tǒng)資源上被賦予更大的優(yōu)先權(quán)奶躯,以便它們能更快地執(zhí)行任務(wù)帚桩。您使用服務(wù)級(jí)別確保響應(yīng)顯式用戶請(qǐng)求的操作優(yōu)先于不重要的工作。
*/
@property NSQualityOfService qualityOfService;
typedef enum NSQualityOfService : NSInteger {
    NSQualityOfServiceUserInteractive = 0x21,
    NSQualityOfServiceUserInitiated = 0x19,
    NSQualityOfServiceUtility = 0x11,
    NSQualityOfServiceBackground = 0x09,
    NSQualityOfServiceDefault = -1
} NSQualityOfService;

NSQualityOfServiceUserInteractive 用于直接提供交互式UI的工作嘹黔。例如账嚎,處理控件事件或繪制到屏幕上。
NSQualityOfServiceUserInitiated 用于執(zhí)行用戶明確要求的工作儡蔓,必須立即提交結(jié)果郭蕉,以便進(jìn)一步進(jìn)行用戶交互。例如喂江,在用戶在郵件列表中選擇郵件后召锈,加載電子郵件。
NSQualityOfServiceUtility 用于執(zhí)行用戶不太可能立即等待結(jié)果的工作获询。這項(xiàng)工作可能是用戶要求的涨岁,也可能是自動(dòng)啟動(dòng)的,并且經(jīng)常使用非模態(tài)進(jìn)度指示器在用戶可見的時(shí)間尺度上運(yùn)行吉嚣。例如梢薪,周期性內(nèi)容更新或大容量文件操作,如媒體導(dǎo)入尝哆。
NSQualityOfServiceBackground用于非用戶發(fā)起或可見的工作秉撇。一般來說,用戶不知道這項(xiàng)工作甚至正在發(fā)生。例如畜疾,預(yù)取內(nèi)容,搜索索引印衔、備份或同步與外部系統(tǒng)的數(shù)據(jù)啡捶。
NSQualityOfServiceDefault指示沒有明確的服務(wù)質(zhì)量信息。只要有可能奸焙,適當(dāng)?shù)姆?wù)質(zhì)量由可用的來源決定瞎暑。否則,選擇的可能是NSQualityOfServiceUserInteractiveNSQualityOfServiceUtility之間服務(wù)水平的任意一種与帆。

/*
此屬性包含操作的相對(duì)優(yōu)先級(jí)了赌。這個(gè)值是用來影響其中的操作和執(zhí)行順序出列。
官方建議:為了確定優(yōu)先級(jí)玄糟,應(yīng)該始終使用這些常量(而不是定義的值)勿她。
*/
@property NSOperationQueuePriority queuePriority;
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
    NSOperationQueuePriorityVeryLow = -8L,
    NSOperationQueuePriorityLow = -4L,
    NSOperationQueuePriorityNormal = 0,
    NSOperationQueuePriorityHigh = 4,
    NSOperationQueuePriorityVeryHigh = 8
};

以上兩個(gè)屬性通過設(shè)置合適的值亚皂,能讓資源利用更加合理上真。其他的API可查看官方文檔使用绅喉,特別注意的是- (void)waitUntilFinished;這個(gè)接口要慎用触趴,該接口絕不能在主線程調(diào)用伶跷,會(huì)產(chǎn)生死鎖卡死主線程政勃。一般來講用- (void)addDependency:(NSOperation *)op;依賴API就夠了丈氓,簡單易用桨嫁。

- (void)waitUntilFinished;接口的文檔解釋:

An operation object must never call this method on itself and should avoid calling it on any operations submitted to the same operation queue as itself. Doing so can cause the operation to deadlock. Instead, other parts of your app may call this method as needed to prevent other tasks from completing until the target operation object finishes. It is generally safe to call this method on an operation that is in a different operation queue, although it is still possible to create deadlocks if each operation waits on the other.
A typical use for this method would be to call it from the code that created the operation in the first place. After submitting the operation to a queue, you would call this method to wait until that operation finished executing.

翻譯:操作對(duì)象絕不能自己調(diào)用這個(gè)方法贰军,應(yīng)該避免在提交給同一操作隊(duì)列的任何操作中調(diào)用它玻蝌。這樣做可能導(dǎo)致操作死鎖。相反词疼,應(yīng)用程序的其他部分可以根據(jù)需要調(diào)用此方法俯树,以防止其他任務(wù)完成,直到目標(biāo)操作對(duì)象完成為止贰盗。一般來說聘萨,在不同的操作隊(duì)列中調(diào)用這種方法是安全的,但如果每個(gè)操作都等待另一個(gè)操作童太,仍然有可能造成死鎖米辐。
這種方法的一個(gè)典型用途是首先從創(chuàng)建操作的代碼調(diào)用它。在向隊(duì)列提交操作之后书释,您將調(diào)用此方法等待該操作完成執(zhí)行翘贮。

waitUntilFinished簡單使用.png

GCD的主要API使用

  • GCD的實(shí)現(xiàn)步驟

GCD實(shí)現(xiàn)多線程的步驟主要有2步:
1>創(chuàng)建隊(duì)列
2>添加執(zhí)行任務(wù)到隊(duì)列中
也可以是1步,添加執(zhí)行任務(wù)到系統(tǒng)標(biāo)準(zhǔn)隊(duì)列
沒錯(cuò)爆惧,就是這么簡單易用狸页!以下是代碼片段:

// 創(chuàng)建隊(duì)列 ,手動(dòng)創(chuàng)建的隊(duì)列需要做釋放處理,因?yàn)镈ispatch Queue并沒有被作為OC對(duì)象處理
/*
 * 1.創(chuàng)建串行隊(duì)列
 * 串行隊(duì)列有兩種創(chuàng)建方式
 */
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.mytest", NULL);
    // 釋放隊(duì)列
    dispatch_release(serialQueue);

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_SERIAL);
    // 釋放隊(duì)列
    dispatch_release(queue);

// 2.創(chuàng)建并行隊(duì)列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_CONCURRENT);
// 3.添加同步任務(wù)到隊(duì)列中
    dispatch_sync(concurrentQueue, ^{
        // 執(zhí)行任務(wù)
    });
    
// 4.添加異步任務(wù)到隊(duì)列中
    dispatch_async(concurrentQueue, ^{
       // 執(zhí)行任務(wù)
    });
    
// 5.釋放隊(duì)列( iOS 6.0 or Mac OS X 10.8 以上系統(tǒng)可以管理GCD對(duì)象無需手動(dòng)釋放)
    dispatch_release(concurrentQueue);
// 一步搞定
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 執(zhí)行操作
    });

GCD的隊(duì)列主要有兩種Serial Dispatch QueueConcurrent Dispatch Queue芍耘。前者只有一個(gè)線程址遇,后者根據(jù)任務(wù)量,可以開啟多條線程斋竞。當(dāng)然多個(gè)Serial Dispatch Queue是可以并行執(zhí)行的倔约。

兩種隊(duì)列和線程的關(guān)系.png
  • Main Dispatch Queue / Global Dispatch Queue

Main Dispatch QueueGlobal Dispatch Queue是系統(tǒng)提供的標(biāo)準(zhǔn)Dispatch Queue,這兩個(gè)標(biāo)準(zhǔn)隊(duì)列還有一個(gè)共同的優(yōu)點(diǎn)坝初,那就是相比于手動(dòng)創(chuàng)建的隊(duì)列浸剩,這兩個(gè)隊(duì)列不需要開發(fā)者做隊(duì)列釋放的操作,因?yàn)檫@兩個(gè)隊(duì)列對(duì)應(yīng)用程序而言是全局的鳄袍,詳細(xì)可查看官方文檔绢要。

Main Dispatch Queue可能你已經(jīng)猜到了,沒錯(cuò)拗小,它就是主線程隊(duì)列重罪,追加到Main Dispatch Queue的處理在主線程的RunLoop中執(zhí)行,一些需要更新UI界面的操作可以放到這個(gè)線程中執(zhí)行哀九。

Global Dispatch QueueConcurrent Dispatch Queue類型的隊(duì)列蛆封。所以我們一般是不用逐個(gè)生成Concurrent Dispatch Queue隊(duì)列的,只要使用全局隊(duì)列Global Dispatch Queue就可以了勾栗。

    // 主隊(duì)列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    
    // 全局隊(duì)列
    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

創(chuàng)建全局隊(duì)列的APIdispatch_get_global_queue(long identifier, unsigned long flags);需要傳兩個(gè)參數(shù)惨篱,第一個(gè)是優(yōu)先級(jí),根據(jù)實(shí)際處理內(nèi)容選擇合適的優(yōu)先級(jí)围俘。第二個(gè)官方文檔稱是為將來使用預(yù)留的砸讳,一般傳數(shù)字0即可。
Global Dispatch Queue有4個(gè)執(zhí)行優(yōu)先級(jí)界牡。

#define DISPATCH_QUEUE_PRIORITY_HIGH 2               // 高優(yōu)先級(jí)
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0            // 默認(rèn)優(yōu)先級(jí) 
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)             // 低優(yōu)先級(jí)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺(tái)優(yōu)先級(jí)
  • dispatch_sync / dispatch_asycn

dispatch_asycn是異步處理函數(shù)簿寂,該函數(shù)不會(huì)等待任務(wù)執(zhí)行完,不會(huì)一直占用當(dāng)前線程宿亡。

dispatch_sync是同步處理函數(shù)常遂,在該函數(shù)中執(zhí)行的任務(wù)不執(zhí)行完,該函數(shù)會(huì)一直在當(dāng)前線程等待挽荠。也可以說這個(gè)函數(shù)是簡化版的dispatch_group_wait函數(shù)克胳。

dispatch_sync要慎用,因?yàn)槭褂貌划?dāng)就會(huì)引起死鎖圈匆。
比如在主線程調(diào)用:

    dispatch_queue_t mianQueue = dispatch_get_main_queue();
    dispatch_async(mainQueue, ^{
        dispatch_sync(mainQueue, ^{
            NSLog(@"Hello World");
        });
    });

Serial Diapatch Queue中調(diào)用也是一樣

    dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        dispatch_sync(queue, ^{
            NSLog(@"Hello World");
        });
    });

死鎖的原因很明顯就是兩個(gè)操作在同一個(gè)線程里互相等待對(duì)方執(zhí)行完漠另,一直互相等待,誰也不執(zhí)行跃赚。

  • dispatch_set_target_queue

手動(dòng)創(chuàng)建的隊(duì)列可以通過dispatch_set_target_queue設(shè)定執(zhí)行的優(yōu)先級(jí)笆搓。將Dispatch Queue指定為dispatch_set_target_queue的函數(shù)參數(shù),不僅可以變更Dispatch Queue的執(zhí)行優(yōu)先級(jí),還可以作為執(zhí)行階層满败。

比如肤频,將一個(gè)普通的Serial Diapatch Queue設(shè)定為與“后臺(tái)優(yōu)先級(jí)全局隊(duì)列”一樣的優(yōu)先級(jí)和階層,那么在執(zhí)行時(shí)算墨,它的執(zhí)行優(yōu)先級(jí)將高于其他普通的Serial Diapatch QueueConcunrrent Diapatch Queue宵荒,它的階層也要比普通的Serial Diapatch QueueConcunrrent Diapatch Queue高,當(dāng)它與“后臺(tái)優(yōu)先級(jí)全局隊(duì)列”階層一樣時(shí)意味著米同,如果它在執(zhí)行,其他普通的Serial Diapatch QueueConcunrrent Diapatch Queue都不能和它并行執(zhí)行摔竿,必須等它執(zhí)行完才能執(zhí)行面粮。

    // 創(chuàng)建串行隊(duì)列
    dispatch_queue_t serialQueue = dispatch_queue_create("com.example.gcd.mytest", NULL);
    
    // 創(chuàng)建并行隊(duì)列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_CONCURRENT);
    
    /*
     * 參照serialQueue的優(yōu)先級(jí)設(shè)置目標(biāo)隊(duì)列 即concurrentQueue的優(yōu)先級(jí)
     * 第一個(gè)參數(shù)為要設(shè)置優(yōu)先級(jí)的queue,第二個(gè)參數(shù)是參照物,既將第一個(gè)queue的優(yōu)先級(jí)和第二個(gè)queue的優(yōu)先級(jí)設(shè)置一樣继低。
     */ 
    dispatch_set_target_queue(concurrentQueue, serialQueue);
  • dispatch_after

dispatch_after用于延時(shí)處理熬苍,需要注意的是并不是dispatch_after在延時(shí)指定時(shí)間后執(zhí)行,而是在指定時(shí)間把任務(wù)添加到隊(duì)列中袁翁,相當(dāng)于加了一個(gè)計(jì)時(shí)器柴底,時(shí)間到了就把任務(wù)添加到隊(duì)列中了。

栗子:
延時(shí)3秒打印Hello Word

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull*NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{NSLog(@"Hello World");});

dispatch_time的第一個(gè)參數(shù)是起始時(shí)間可傳:DISPATCH_TIME_NOWDISPATCH_TIME_FOREVER粱胜。
DISPATCH_TIME_NOW : 表示從現(xiàn)在開始 DISPATCH_TIME_FOREVER:表示持續(xù)等待柄驻,稍后會(huì)用到。
第二個(gè)參數(shù)是持續(xù)多久焙压,"ull"是C語言的數(shù)值字面量鸿脓,是顯示表明類型時(shí)使用的字符串(表示"unsight long long")為了精確時(shí)間寫上"ull"NSEC_PER_SEC是秒時(shí)間單位的一種涯曲,表示納秒級(jí)精確的一秒野哭。

NSEC:納秒。
USEC:微妙幻件。
SEC:秒
PER:每
#define NSEC_PER_SEC 1000000000ull  // 每秒有多少納秒
#define NSEC_PER_MSEC 1000000ull    // 每毫秒有多少納秒
#define USEC_PER_SEC 1000000ull     // 每秒有多少毫秒
#define NSEC_PER_USEC 1000ull       // 每毫秒有多少納秒
  • Dispatch Group

有時(shí)我們想等添加到隊(duì)列中的所有任務(wù)都執(zhí)行完再執(zhí)行結(jié)束處理拨黔。Serial Dispatch Queue好說,本來就是串行的绰沥。但是Concurrent Dispatch Queue就不行了篱蝇,異步的我們根本不知道哪個(gè)是最后執(zhí)行完的。這個(gè)時(shí)候就可以用到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, ^{NSLog(@"block 0");});
    dispatch_group_async(group, queue, ^{NSLog(@"block 1");});
    dispatch_group_async(group, queue, ^{NSLog(@"block 2");});
    /*
     * 無論向什么樣的Dispatch Queue中添加任務(wù)态兴,使用Dispatch Group都可以監(jiān)聽這個(gè)任務(wù)的執(zhí)行結(jié)束
     * 所有任務(wù)結(jié)束時(shí),會(huì)執(zhí)行dispatch_group_notify函數(shù)的block
     */ 
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"done");});
   
  // iOS 6.0 or Mac OS X 10.8 以上系統(tǒng)可以管理GCD對(duì)象無需手動(dòng)釋放
    dispatch_release(group);

dispatch_group_wait也可以達(dá)到同樣的效果疟位,不過前者更簡潔瞻润,所以建議用dispatch_group_notify,下邊是dispatch_group_wait的實(shí)現(xiàn)。

   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(@"block 0");});
    dispatch_group_async(group, queue, ^{NSLog(@"block 1");});
    dispatch_group_async(group, queue, ^{NSLog(@"block 2");});
    
    dispatch_time_t time = dispatch_time(DISPATCH_TIME_FOREVER, 1ull*NSEC_PER_SEC);
    long result = dispatch_group_wait(group, time);
    /*
     指定DISPATCH_TIME_NOW绍撞,則不用任何等待正勒,即可判斷Dispatch Group中的處理是否執(zhí)行結(jié)束
(在主線程的Runloop的每次循環(huán)中可檢查執(zhí)行是否結(jié)束,從而不耗費(fèi)多余的等待時(shí)間)
     */
   // long result = dispatch_group_wait(group, DISPATCH_TIME_NOW);
    
    if (result == 0) {
        // group的全部任務(wù)執(zhí)行完畢
    }
    else {
        // group的某個(gè)任務(wù)還在執(zhí)行中
    }
    
    // iOS 6.0 or Mac OS X 10.8 以上系統(tǒng)可以管理GCD對(duì)象無需手動(dòng)釋放
    // dispatch_release(group);
  • Dispatch Semaphore 信號(hào)量

GCD信號(hào)量的用法主要有兩種傻铣,一種是監(jiān)聽1個(gè)異步執(zhí)行結(jié)果章贞,另外一種是控制線程并發(fā)數(shù)。
信號(hào)量的函數(shù)有3個(gè):

  1. 創(chuàng)建信號(hào)量非洲,傳入的value要大于等于0
    dispatch_semaphore_create(long value);
  2. 增加一個(gè)信號(hào)量
    dispatch_semaphore_signal(dispatch_semaphore_t dsema);
  3. 減少1個(gè)信號(hào)量鸭限,并且信號(hào)量為0則等待,等待時(shí)間取決于timeout時(shí)間參數(shù)
    dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

接下來看代碼:

  • 信號(hào)量監(jiān)聽1個(gè)異步執(zhí)行結(jié)果
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);// 信號(hào)量為0
    
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        // 信號(hào)量+1
        dispatch_semaphore_signal(semaphore);
    });
    
    // 若信號(hào)量為0則一直等待
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"繼續(xù)執(zhí)行函數(shù)");
  • 打印結(jié)果
2017-12-19 11:35:31.731268+0800 多線程[3482:317136] run task 1
2017-12-19 11:35:32.736671+0800 多線程[3482:317136] complete task 1
2017-12-19 11:35:32.737201+0800 多線程[3482:316871] 繼續(xù)執(zhí)行函數(shù)
  • 信號(hào)量控制線程數(shù)
// 信號(hào)量為2两踏,相當(dāng)于線程最大并發(fā)數(shù)為2
dispatch_semaphore_t semaphore = dispatch_semaphore_create(2);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 若信號(hào)量為0則一直等待败京,不為0則信號(hào)量-1并繼續(xù)執(zhí)行函數(shù)
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 1");
        sleep(1);
        NSLog(@"complete task 1");
        dispatch_semaphore_signal(semaphore); // 信號(hào)量+1
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 2");
        sleep(1);
        NSLog(@"complete task 2");
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"run task 3");
        sleep(1);
        NSLog(@"complete task 3");
        dispatch_semaphore_signal(semaphore);
    });
  • 打印結(jié)果
/*
1.信號(hào)量初始值為2(即線程最大并發(fā)數(shù)為2);
2.任務(wù)1執(zhí)行信號(hào)量-1梦染,任務(wù)2執(zhí)行信號(hào)量-1赡麦,此時(shí)信號(hào)量為0(即兩個(gè)線程已經(jīng)有任務(wù)在執(zhí)行,任務(wù)3只能等待)帕识;
3.當(dāng)任務(wù)1或任務(wù)2執(zhí)行完泛粹,則信號(hào)量+1,信號(hào)量不為0(即有一個(gè)可用線程肮疗,此時(shí)任務(wù)3可執(zhí)行)晶姊。
*/
2017-12-19 11:41:28.631848+0800 多線程[3542:324794] run task 2
2017-12-19 11:41:28.631849+0800 多線程[3542:324796] run task 1
2017-12-19 11:41:29.636731+0800 多線程[3542:324794] complete task 2
2017-12-19 11:41:29.636758+0800 多線程[3542:324796] complete task 1
2017-12-19 11:41:29.637094+0800 多線程[3542:324795] run task 3
2017-12-19 11:41:30.637980+0800 多線程[3542:324795] complete task 3

關(guān)于信號(hào)量和Dispatch Group異步線程的延伸閱讀

  • dispatch_once

dispatch_once大家比較熟,因?yàn)榫€程安全的單例模式常用到它伪货。

+(instancetype)sharedSingleton{
    static id instance = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

還可以用@synchronized()這個(gè)互斥鎖實(shí)現(xiàn)單例模式:

+(instancetype)sharedSingleton{
    static id instance = nil;
    @synchronized (self) {
        if (!instance) {
            instance = [[self alloc] init];
        }
    }
    return instance;
}
  • dispatch_barrier_async

在訪問數(shù)據(jù)庫或文件時(shí)帽借,為避免數(shù)據(jù)競爭,前面講到可以使用Serial Dispatch Queue超歌。但是其實(shí)如果是讀取和讀取并行執(zhí)行是不會(huì)引起數(shù)據(jù)競爭的砍艾,如果能把這部分操作拆分出來,那無疑會(huì)提高訪問效率巍举。dispatch_barrier_async配合手動(dòng)創(chuàng)建的Concurrent Dispatch Queue就可以幫我們做到脆荷。在執(zhí)行dispatch_barrier_async時(shí),它會(huì)等正在執(zhí)行的任務(wù)執(zhí)行完開始懊悯,當(dāng)它結(jié)束后其他任務(wù)才會(huì)再開始執(zhí)行蜓谋。在SDWebImage框架中也有用到它和它的同步函數(shù)dispatch_barrier_sync

dispatch_queue_t queue = dispatch_queue_create("com.example.gcd.mytest", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 執(zhí)行讀取操作
    });
    dispatch_async(queue, ^{
        // 執(zhí)行讀取操作
    });
    dispatch_barrier_async(queue, ^{
       // 執(zhí)行寫入操作
    });
    dispatch_async(queue, ^{
        // 執(zhí)行讀取操作
    });
    dispatch_async(queue, ^{
        // 執(zhí)行讀取操作
    });
  • dispatch_suspend / dospatch_resume

在隊(duì)列大量任務(wù)執(zhí)行時(shí)炭分,需要臨時(shí)掛起隊(duì)列時(shí)調(diào)用dispatch_suspend
dispatch_suspend(queue)掛起隊(duì)列
dospatch_resume (queue)恢復(fù)隊(duì)列

GCD還提供可多線程讀取同一個(gè)大型文件的APIdispatch I/Odispatch Data,還有其他很多有意思有用的API桃焕,大家盡可以去查看官方文檔學(xué)習(xí)和使用。

參考

  • 蘋果官方文檔
  • 《現(xiàn)代操作系統(tǒng)》
  • 《Objective-C高級(jí)編程- iOS與OS X多線程和內(nèi)存管理》
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捧毛,一起剝皮案震驚了整個(gè)濱河市观堂,隨后出現(xiàn)的幾起案子让网,更是在濱河造成了極大的恐慌,老刑警劉巖师痕,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件溃睹,死亡現(xiàn)場離奇詭異,居然都是意外死亡胰坟,警方通過查閱死者的電腦和手機(jī)因篇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笔横,“玉大人竞滓,你說我怎么就攤上這事〈档蓿” “怎么了商佑?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長涛菠。 經(jīng)常有香客問我莉御,道長撇吞,這世上最難降的妖魔是什么俗冻? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮牍颈,結(jié)果婚禮上迄薄,老公的妹妹穿的比我還像新娘。我一直安慰自己煮岁,他們只是感情好讥蔽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著画机,像睡著了一般冶伞。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上步氏,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天响禽,我揣著相機(jī)與錄音,去河邊找鬼荚醒。 笑死芋类,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的界阁。 我是一名探鬼主播侯繁,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼泡躯!你這毒婦竟也來了贮竟?” 一聲冷哼從身側(cè)響起丽焊,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坝锰,沒想到半個(gè)月后粹懒,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顷级,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年凫乖,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片弓颈。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡帽芽,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出翔冀,到底是詐尸還是另有隱情导街,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布纤子,位于F島的核電站搬瑰,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏控硼。R本人自食惡果不足惜泽论,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卡乾。 院中可真熱鬧翼悴,春花似錦、人聲如沸幔妨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽误堡。三九已至古话,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锁施,已是汗流浹背陪踩。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留沾谜,地道東北人膊毁。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像基跑,于是被迫代替她去往敵國和親婚温。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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