iOS多線程之NSOperation、NSOperationQueue

經過一道小問題引出今天的內容

咱們有A、B、C、D四個異步任務,AB執行結束才能執行C,A執行完成才能執行D數組

使用GCD解決

分兩步實現安全

  • AB執行結束才能執行C,這個比較好實現,使用dispatch_group_t+dispatch_barrier就能夠很容易實現
dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t conQueue = dispatch_queue_create("conQueue", DISPATCH_QUEUE_CONCURRENT);

    dispatch_group_async(group, conQueue, ^{
        sleep(6);
        NSLog(@"taskA");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_group_async(group, conQueue, ^{
        NSLog(@"taskB");
    });

    dispatch_barrier_async(conQueue, ^{
        NSLog(@"taskC");
    });
複製代碼

注意要本身建立併發隊列不要使用globalQueue,這裏有坑點markdown

  • A執行完成才能執行D

這一步彷佛就沒那麼好實現,咱們須要引入狀態變量或者鎖才能解決A執行完成才能執行D的問題,咱們能夠使用GCD的信號量來解決網絡

dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t conQueue = dispatch_queue_create("conQueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    dispatch_group_async(group, conQueue, ^{
        sleep(6);
        NSLog(@"taskA");
        dispatch_semaphore_signal(semaphore);
    });

    dispatch_group_async(group, conQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        NSLog(@"taskD");
    });

    dispatch_group_async(group, conQueue, ^{
        NSLog(@"taskB");
    });

    dispatch_barrier_async(conQueue, ^{
        NSLog(@"taskC");
    });
複製代碼

使用NSOperation、NSOperationQueue來解決

NSBlockOperation *taskA = [NSBlockOperation blockOperationWithBlock:^{
        sleep(6);
        NSLog(@"taskA");
    }];

    NSBlockOperation *taskB = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"taskB");
    }];

    NSBlockOperation *taskC = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"taskC");
    }];

    NSBlockOperation *taskD = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"taskD");
    }];

    [taskC addDependency:taskA];
    [taskC addDependency:taskB];
    [taskD addDependency:taskA];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:taskA];
    [queue addOperation:taskB];
    [queue addOperation:taskC];
    [queue addOperation:taskD];
複製代碼

只須要添加這幾行任務依賴關係就能夠了多線程

    [taskC addDependency:taskA];
    [taskC addDependency:taskB];
    [taskD addDependency:taskA];
複製代碼

這樣看來NSOperation、NSOperationQueue比GCD更加的面向對象,而不須要經過信號量這種過程變量來控制。併發

GCD已經能知足咱們解決線程問題的須要,NSOperation、NSOperationQueue是基於GCD的一層封裝,更加面向對象,使用起來更加簡單,在解決諸如任務的依賴時尤其簡單。異步

參考文章 iOS 多線程:『NSOperation、NSOperationQueue』詳盡總結async

NSOperation、NSOperationQueue 簡介

NSOperation、NSOperationQueue是基於CGD的封裝,那麼NSOperation、NSOperationQueue一樣有任務隊列的概念oop

  • 操做(Operation):spa

    • 執行操做的意思,換句話說就是你在線程中執行的那段代碼。
    • 在 GCD 中是放在 block 中的。在 NSOperation 中,咱們使用 NSOperation 子類 NSInvocationOperationNSBlockOperation,或者自定義子類來封裝操做。
  • 操做隊列(Operation Queues):

    • 這裏的隊列指操做隊列,即用來存放操做的隊列。不一樣於 GCD 中的調度隊列 FIFO(先進先出)的原則。NSOperationQueue 對於添加到隊列中的操做,首先進入準備就緒的狀態(就緒狀態取決於操做之間的依賴關係),而後進入就緒狀態的操做的開始執行順序(非結束執行順序)由操做之間相對的優先級決定(優先級是操做對象自身的屬性)。
    • 操做隊列經過設置最大併發操做數(maxConcurrentOperationCount) 來控制併發、串行。
    • NSOperationQueue 爲咱們提供了兩種不一樣類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在後臺執行。

NSInvocationOperation

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(fun) object:nil];
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(fun1) object:nil];

    [operation start];
    [operation1 start];
複製代碼

能夠看到NSInvocationOperation是在當前線程同步執行,須要手動調起start方法

NSBlockOperation

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        sleep(3);
        NSLog(@"fun");
    }];

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1");
    }];

    [operation start];
    [operation1 start];
複製代碼

NSInvocationOperation相似,也是在當前線程同步執行,也須要手動調用start方法,另外還能夠經過``addExecutionBlock`添加任務

NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock1-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock2-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock3-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock4-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock5-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock6-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock7-%@",[NSThread currentThread]);
    }];

    [operation addExecutionBlock:^{
        NSLog(@"exectionBLock8-%@",[NSThread currentThread]);
    }];

    [operation start];
複製代碼

image.png

經過打印結果能夠看出經過這兩種方式添加的任務是異步執行的,他們均可能在子線程中執行,注意:經過blockOperationWithBlock設置的任務也不必定在主線程(當前線程)中執行

NSOperationQueue

NSOperationQueue 一共有兩種隊列:主隊列、自定義隊列。其中自定義隊列同時包含了串行、併發功能。主隊列必定在主線程執行,執行順序爲串行同步執行

  • 主隊列
NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun-%@",[NSThread currentThread]);
    }];

    [[NSOperationQueue mainQueue] addOperation:operation];
複製代碼

必定在主線程執行

  • 自定義隊列
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun2-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun3-%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun4-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation5 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun5-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation6 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun6-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation7 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun7-%@",[NSThread currentThread]);
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
    [queue addOperation:operation5];
    [queue addOperation:operation6];
    [queue addOperation:operation7];
複製代碼

image.png

能夠看到自定義隊列的任務必定在子線程併發執行,具備開啓新線程的能力

maxConcurrentOperationCount

經過maxConcurrentOperationCount能夠控制隊列的最大併發操做數量,默認值爲-1,表示不進行限制,最大開啓併發數由系統控制,若是設置爲1那就是串行執行,若是設置爲大於1那麼就取 min{自定義值,系統設定最大值},即便咱們設置了一個很大的值,系統也不會無限制的開啓如此多的併發操做.

  • 同步執行
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun2-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun3-%@",[NSThread currentThread]);
    }];
    
    NSBlockOperation *operation4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun4-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation5 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"fun5-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation6 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun6-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation7 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun7-%@",[NSThread currentThread]);
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.maxConcurrentOperationCount = 1;
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
    [queue addOperation:operation4];
    [queue addOperation:operation5];
    [queue addOperation:operation6];
    [queue addOperation:operation7];
複製代碼

image.png

NSOperation 操做依賴

前面問題中咱們就用了操做依賴addDependency,例如operation3依賴operation1operation2

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"fun2-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun3-%@",[NSThread currentThread]);
    }];

    [operation3 addDependency:operation1];
    [operation3 addDependency:operation2];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
複製代碼

image.png

operation1operation2執行結束纔會執行operation3

NSOperation 優先級queuePriority

優先級有五種

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

例如上面的例子咱們將operation1優先級設置爲veryLow,將operation3優先級設置爲veryHigh,那麼是否是operation3會比operation1更早執行呢????

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun1-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(1);
        NSLog(@"fun2-%@",[NSThread currentThread]);
    }];

    NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"fun3-%@",[NSThread currentThread]);
    }];

    [operation3 addDependency:operation1];
    [operation3 addDependency:operation2];

    [operation1 setQueuePriority:NSOperationQueuePriorityVeryLow];
    [operation3 setQueuePriority:NSOperationQueuePriorityVeryHigh];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    [queue addOperation:operation3];
複製代碼

image.png

能夠看到仍然按照依賴關係先執行,優先級只是控制就緒狀態的任務的開始執行順序,注意是開始執行順序,而不是結束執行順序,先開始的任務不必定先結束,那麼什麼是就緒狀態呢??

例如操做C依賴操做A和操做B,那麼當AB直接結束以前C是處於非就緒狀態的,操做A和操做B能夠同時處於就緒狀態,這個時候若是A的優先級高於B那麼A就比B先開始執行,可是不必定比B早結束執行

線程間通信

例如,網絡耗時操做在子線程執行,結束以後在主線程刷新頁面

NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        sleep(3);//模擬耗時操做
        NSLog(@"fun1-%@",[NSThread currentThread]);
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            //刷新頁面
            NSLog(@"fun2-%@",[NSThread currentThread]);
        }];
    }];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
複製代碼

image.png

線程安全

能夠加索保證線程安全

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);

    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
        while (1) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            if (num>0) {
                num--;
                NSLog(@"%d-%@",num,[NSThread currentThread]);
            }
            dispatch_semaphore_signal(semaphore);
            if (num<=0) {
                break;
            }
        }
    }];

    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
        while (1) {
            dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
            if (num>0) {
                num--;
                NSLog(@"%d-%@",num,[NSThread currentThread]);
            }

            dispatch_semaphore_signal(semaphore);
            if (num<=0) {
                break;
            }
        }
    }];
    
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation1];
    [queue addOperation:operation2];
複製代碼

NSOperation 經常使用屬性和方法

  1. 取消操做方法
  • - (void)cancel; 可取消操做,實質是標記 isCancelled 狀態。
  1. 判斷操做狀態方法
  • - (BOOL)isFinished; 判斷操做是否已經結束。
  • - (BOOL)isCancelled; 判斷操做是否已經標記爲取消。
  • - (BOOL)isExecuting; 判斷操做是否正在在運行。
  • - (BOOL)isReady; 判斷操做是否處於準備就緒狀態,這個值和操做的依賴關係相關。
  1. 操做同步
  • - (void)waitUntilFinished; 阻塞當前線程,直到該操做結束。可用於線程執行順序的同步。
  • - (void)setCompletionBlock:(void (^)(void))block; completionBlock 會在當前操做執行完畢時執行 completionBlock。
  • - (void)addDependency:(NSOperation *)op; 添加依賴,使當前操做依賴於操做 op 的完成。
  • - (void)removeDependency:(NSOperation *)op; 移除依賴,取消當前操做對操做 op 的依賴。
  • @property (readonly, copy) NSArray<NSOperation *> *dependencies; 在當前操做開始執行以前完成執行的全部操做對象數組。

# NSOperationQueue 經常使用屬性和方法

  • 取消/暫停/恢復操做

    • - (void)cancelAllOperations; 能夠取消隊列的全部操做。
    • - (BOOL)isSuspended; 判斷隊列是否處於暫停狀態。 YES 爲暫停狀態,NO 爲恢復狀態。
    • - (void)setSuspended:(BOOL)b; 可設置操做的暫停和恢復,YES 表明暫停隊列,NO 表明恢復隊列。
  • 操做同步

    • - (void)waitUntilAllOperationsAreFinished; 阻塞當前線程,直到隊列中的操做所有執行完畢。
  • 添加/獲取操做`

    • - (void)addOperationWithBlock:(void (^)(void))block; 向隊列中添加一個 NSBlockOperation 類型操做對象。
    • - (void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait; 向隊列中添加操做數組,wait 標誌是否阻塞當前線程直到全部操做結束
    • - (NSArray *)operations; 當前在隊列中的操做數組(某個操做執行結束後會自動從這個數組清除)。
    • - (NSUInteger)operationCount; 當前隊列中的操做數。
  • 獲取隊列

    • + (id)currentQueue; 獲取當前隊列,若是當前線程不是在 NSOperationQueue 上運行則返回 nil。
    • + (id)mainQueue; 獲取主隊列。

注意:

  1. 這裏的暫停和取消(包括操做的取消和隊列的取消)並不表明能夠將當前的操做當即取消,而是噹噹前的操做執行完畢以後再也不執行新的操做。
  2. 暫停和取消的區別就在於:暫停操做以後還能夠恢復操做,繼續向下執行;而取消操做以後,全部的操做就清空了,沒法再接着執行剩下的操做。

參考文章

iOS 多線程:『NSOperation、NSOperationQueue』詳盡總結

相關文章
相關標籤/搜索