1. 程式人生 > >iOS 多執行緒 設定執行順序

iOS 多執行緒 設定執行順序

//

//  ViewController.m

//  GCDDownload

//

//  Created by ql on 2018/4/4.

//  Copyright © 2018年 carystaloptech. All rights reserved.

//

#import "ViewController.h"

@interfaceViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {

    [superviewDidLoad];

    [selfDownloadFilesWithGCD];

}

//經常有這樣的需求:

//1,有m個網路請求。

//2,先併發執行其中n幾個。

//3,待這n個請求完成之後再執行第n+1個請求。

//4然後等 第n+1個請求完成後再併發執行剩下的m-(n+1)個請求。

- (void)DownloadFilesWithGCD{

dispatch_queue_t queue = dispatch_queue_create("downQueue",DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

        NSLog(@"gcd任務1");

        sleep(3);

    });

dispatch_async(queue, ^{

        NSLog(@"gcd任務2");

        sleep

(1);

    });

dispatch_async(queue, ^{

        NSLog(@"gcd任務3");

        sleep(3);

    });

//void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

//這個函式可以設定同步執行的block,它會等到在它加入佇列之前的block執行完畢後,才開始執行。在它之後加入佇列的block,則等到這個block執行完畢後才開始執行。

dispatch_barrier_async(queue, ^{

NSLog(@"gcd處理任務 1 2 3");

    });

dispatch_async(queue, ^{

        NSLog(@"gcd任務4");

        [selfdownloadFilesWithNSOperation];

    });

dispatch_async(queue, ^{

        NSLog(@"gcd任務5");

        sleep(3);

    });

    /*

     Dispatch Queues的生成可以有這幾種方式:

     1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一個序列佇列,佇列中的block按照先進先出(FIFO)的順序去執行,實際上為單執行緒執行。第一個引數是佇列的名稱,在除錯程式時會非常有用,所有儘量不要重名了。

     2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一個併發執行佇列,block被分發到多個執行緒去執行

     3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //獲得程式程序預設產生的併發佇列,可設定優先順序來選擇高、中、低三個優先順序佇列。由於是系統預設生成的,所以無法呼叫dispatch_resume()和dispatch_suspend()來控制執行繼續或中斷。需要注意的是,三個佇列不代表三個執行緒,可能會有更多的執行緒。併發佇列可以根據實際情況來自動產生合理的執行緒數,也可理解為dispatch佇列實現了一個執行緒池的管理,對於程式邏輯是透明的。

     官網文件解釋說共有三個併發佇列,但實際還有一個更低優先順序的佇列,設定優先順序為DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode除錯時可以觀察到正在使用的各個dispatch佇列。

     4. dispatch_queue_t queue = dispatch_get_main_queue(); //獲得主執行緒的dispatch佇列,實際是一個序列佇列。同樣無法控制主執行緒dispatch佇列的執行繼續或中斷。

     接下來我們可以使用dispatch_async或dispatch_sync函式來載入需要執行的block。

     dispatch_async(queue, ^{

       //block具體程式碼

     }); //非同步執行block,函式立即返回

     dispatch_sync(queue, ^{

       //block具體程式碼

     }); //同步執行block,函式不返回,一直等到block執行完畢。編譯器會根據實際情況優化程式碼,所以有時候你會發現block其實還在當前執行緒上執行,並沒用產生新執行緒。

     實際程式設計經驗告訴我們,儘可能避免使用dispatch_sync,巢狀使用時還容易引起程式死鎖。

     如果queue1是一個序列佇列的話,這段程式碼立即產生死鎖:

     dispatch_sync(queue1, ^{

     dispatch_sync(queue1, ^{

         ......

       });

       ......

      });

     那實際運用中,一般可以用dispatch這樣來寫,常見的網路請求資料多執行緒執行模型:

     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

       //子執行緒中開始網路請求資料

       //更新資料模型

       dispatch_sync(dispatch_get_main_queue(), ^{

         //在主執行緒中更新UI程式碼

       });

     });

     程式的後臺執行和UI更新程式碼緊湊,程式碼邏輯一目瞭然。

     dispatch佇列是執行緒安全的,可以利用序列佇列實現鎖的功能。比如多執行緒寫同一資料庫,需要保持寫入的順序和每次寫入的完整性,簡單地利用序列佇列即可實現:

     dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);

     - (void)writeDB:(NSData *)data

     {

       dispatch_async(queue1, ^{

         //write database

       });

     }

     下一次呼叫writeDB:必須等到上次呼叫完成後才能進行,保證writeDB:方法是執行緒安全的。

     dispatch佇列還實現其它一些常用函式,包括:

     void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重複執行block,需要注意的是這個方法是同步返回,也就是說等到所有block執行完畢才返回,如需非同步返回則巢狀在dispatch_async中來使用。多個block的執行是否併發或序列執行也依賴queue的是否併發或序列。

     void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //這個函式可以設定同步執行的block,它會等到在它加入佇列之前的block執行完畢後,才開始執行。在它之後加入佇列的block,則等到這個block執行完畢後才開始執行。

     void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函式

     void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延遲執行block

     最後再來看看dispatch佇列的一個很有特色的函式:

     void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);

     它會把需要執行的任務物件指定到不同的佇列中去處理,這個任務物件可以是dispatch佇列,也可以是dispatch源(以後博文會介紹)。而且這個過程可以是動態的,可以實現佇列的動態排程管理等等。比如說有兩個佇列dispatchA和dispatchB,這時把dispatchA指派到dispatchB:

     dispatch_set_target_queue(dispatchA, dispatchB);

     那麼dispatchA上還未執行的block會在dispatchB上執行。這時如果暫停dispatchA執行:

     dispatch_suspend(dispatchA);

     則只會暫停dispatchA上原來的block的執行,dispatchB的block則不受影響。而如果暫停dispatchB的執行,則會暫停dispatchA的執行。

     */

//    參考:http://www.cnblogs.com/sunfrog/p/3305614.html

}

- (void)downloadFilesWithNSOperation{

NSOperationQueue *queue = [[NSOperationQueuealloc]init];

NSBlockOperation *op1 = [NSBlockOperationblockOperationWithBlock:^{

        NSLog(@"op任務1");

        sleep(5);

    }];

NSBlockOperation *op2 = [NSBlockOperationblockOperationWithBlock:^{

        NSLog(@"op任務2");

        sleep(2);

    }];

NSBlockOperation *op3 = [NSBlockOperationblockOperationWithBlock:^{

        NSLog(@"op任務3");

        sleep(3);

    }];

    [op3 addDependency:op1]; // 新增依賴關係 op3 在 op1 完成以後執行

    [op3 addDependency:op2]; // 新增依賴關係 op3 在 op2 完成以後執行

//設定佇列中操作同時執行的最大數目,也就是說當前佇列中呢最多由幾個執行緒在同時執行,一般情況下允許最大的併發數2或者3

    [queue setMaxConcurrentOperationCount:3];

    [queue addOperations:@[op1,op2,op3]waitUntilFinished:YES];

NSBlockOperation *op4 = [NSBlockOperationblockOperationWithBlock:^{

        NSLog(@"op任務4");

        sleep(3);

    }];

NSBlockOperation *op5 = [NSBlockOperationblockOperationWithBlock:^{

        NSLog(@"op任務5");

    }];

NSOperationQueue *queue1 = [[NSOperationQueuealloc]init];

    [op4 addDependency:op3]; // 新增依賴關係 op4 在 op3 完成以後執行

    [op5 addDependency:op3]; // 新增依賴關係 op5 在 op3 完成以後執行

//    - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;

//批量加入執行operation,wait標誌是否當前執行緒等待所有operation結束後,才返回

    [queue1 addOperations:@[op4,op5]waitUntilFinished:YES];

    /*

     NSOperation 常用方法:

     - (void)start; //在當前任務狀態和依賴關係合適的情況下,啟動NSOperation的main方法任務,需要注意預設實現只是在當前執行緒執行。如果需要併發執行,子類必須重寫這個方法,並且使 - (BOOL)isConcurrent 方法返回YES

     - (void)main; //定義NSOperation的主要任務程式碼

     - (BOOL)isCancelled; //當前任務狀態是否已標記為取消

     - (void)cancel; //取消當前NSOperation任務,實質是標記isCancelled狀態

     - (BOOL)isExecuting; //NSOperation任務是否在執行

     - (BOOL)isFinished; //NSOperation任務是否已結束

     NSOperation其它常用方法,包括依賴關係:

     - (BOOL)isReady; //是否能準備執行,這個值和任務的依賴關係相關

     - (void)addDependency:(NSOperation *)op; //加上任務的依賴,也就是說依賴的任務都完成後,才能執行當前任務

     - (void)removeDependency:(NSOperation *)op; //取消任務的依賴,依賴的任務關係不會自動消除,必須呼叫該方法

     - (NSArray *)dependencies; //得到所有依賴的NSOperation任務

     以及用於任務同步:

     - (void)waitUntilFinished; //阻塞當前執行緒,直到該NSOperation結束。可用於執行緒執行順序的同步

     - (void)setCompletionBlock:(void (^)(void))block; //設定NSOperation結束後執行的block程式碼,由於NSOperation有可能被取消,所以這個block執行的程式碼應該和NSOperation的核心任務無關。

     NSOperationQueue的其它常用方法:

     - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; //批量加入執行operation,wait標誌是否當前執行緒等待所有operation結束後,才返回

     - (void)addOperationWithBlock:(void (^)(void))block; //相當於加入一個NSBlockOperation執行任務

     - (NSArray *)operations; //返回已加入執行operation的陣列,當某個operation結束後會自動從這個陣列清除

     - (NSUInteger)operationCount //返回已加入執行operation的數目

     - (void)setSuspended:(BOOL)b; //是否暫停將要執行的operation,但不會暫停已開始的operation

     - (BOOL)isSuspended; //返回暫停標誌

     - (void)cancelAllOperations; //取消所有operation的執行,實質是呼叫各個operation的cancel方法

     + (id)currentQueue; //返回當前NSOperationQueue,如果當前執行緒不是在NSOperationQueue上執行則返回nil

     + (id)mainQueue; //返回主執行緒的NSOperationQueue,預設總是有一個queue

     */

//    參考: http://www.jb51.net/article/130108.htm

}

- (void)didReceiveMemoryWarning {

    [superdidReceiveMemoryWarning];

// Dispose of any resources that can be recreated.

}

@end