iOS 多線程(NNSThread,GCD,NSOperation)

線程基本概念

線程是進程內假想的持有 cpu 使用權的執行單位,一個進程下能夠建立多個線程並行執行;使用多線程的程序稱爲多線程運行,從程序開始執行是運行的程序成爲主線程,除此以外以後生成的線程爲次線程或子線程。ios

線程安全和注意點

多個線程操做某個實例時,沒有獲得錯誤的結果或實例時,那麼該類就稱爲線程安全。結果不能保證時,則稱爲非線程安全。git

通常狀況下,常數對象是線程安全的,變量對象不是線程安全的。程序員

要想使用多線程不出錯且高效執行,並行編程的知識必不可少,線程間的任務分配和信息交換、共享資源的互斥、與 GUI 的交互以及動畫顯示等,使用時都要格外當心。macos

iOS 多線程對比

NSThread

簡介

NSThread 是蘋果官方提供的面向對象操做線程技術,簡單方便,能夠直接操做對象,不過須要本身控制線程的生命週期,在平時較少使用。初始化建立 NSThread 的方法有以下幾種:編程

/*
使用target對象的selector做爲線程的任務執行體,該selector方法最多能夠接收一個參數,該參數即爲argument
*/
- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument API_AVAILABLE(macos(10.5), ios(2.0), watchos(2.0), tvos(9.0));

/*
使用block做爲線程的任務執行體
*/
- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/*
類方法,返回值爲void
使用一個block做爲線程的執行體,並直接啓動線程
上面的實例方法返回NSThread對象須要手動調用start方法來啓動線程執行任務
*/
+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));

/*
類方法,返回值爲void
使用target對象的selector做爲線程的任務執行體,該selector方法最多接收一個參數,該參數即爲argument
一樣的,該方法建立完縣城後會自動啓動線程不須要手動觸發
*/
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;

線程的相關用法和線程狀態的控制方法

// 得到主線程
+ (NSThread *)mainThread;    

// 判斷是否爲主線程(對象方法)
- (BOOL)isMainThread;

// 判斷是否爲主線程(類方法)
+ (BOOL)isMainThread;    

// 得到當前線程
NSThread *current = [NSThread currentThread];

// 線程的名字——setter方法
- (void)setName:(NSString *)n;    

// 線程的名字——getter方法
- (NSString *)name;    

// 線程進入就緒狀態 -> 運行狀態。當線程任務執行完畢,自動進入死亡狀態
- (void)start;


// 線程進入阻塞狀態
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;

// 線程進入死亡狀態
+ (void)exit;

線程之間的通訊

// 在主線程上執行操做
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray<NSString *> *)array;
  // equivalent to the first method with kCFRunLoopCommonModes

// 在指定線程上執行操做
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait modes:(NSArray *)array NS_AVAILABLE(10_5, 2_0);
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);

// 在當前線程上執行操做,調用 NSObject 的 performSelector:相關方法
- (id)performSelector:(SEL)aSelector;
- (id)performSelector:(SEL)aSelector withObject:(id)object;
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

GCD

概念

Grand Central Dispatch(GCD) 是 Apple 開發的一個多核編程的較新的解決方法。它主要用於優化應用程序以支持多核處理器以及其餘對稱多處理系統。它是一個在線程池模式的基礎上執行的併發任務。在 Mac OS X 10.6 雪豹中首次推出,也可在 iOS 4 及以上版本使用。數組

優勢

  • GCD 可用於多核的並行運算
  • GCD 會自動利用更多的 CPU 內核(好比雙核、四核)
  • GCD 會自動管理線程的生命週期(建立線程、調度任務、銷燬線程)
  • 程序員只須要告訴 GCD 想要執行什麼任務,不須要編寫任何線程管理代碼

GCD 的使用

一、建立隊列(串行隊列或併發隊列)
二、將任務追加到隊列中,系統根據任務類型執行任務(同步或者異步)安全

隊列的建立方法/獲取方法

// 串行隊列的建立方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);
// 併發隊列的建立方法
dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);
// 主隊列的獲取方法
dispatch_queue_t queue = dispatch_get_main_queue();
// 全局併發隊列的獲取方法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

任務的建立方法

// 同步執行任務建立方法
dispatch_sync(queue, ^{
    // 這裏放同步執行任務代碼
});
// 異步執行任務建立方法
dispatch_async(queue, ^{
    // 這裏放異步執行任務代碼
});

GCD的基本使用

咱們能夠看到,GCD 有兩種建立任務的方法:同步或異步;三種隊列:併發隊列、串行隊列和主隊列,一共有六種的組合方式,咱們逐個進行分析:多線程

#pragma mark ------------------------GCD 基本使用(六種不一樣的組合)
#pragma mark -----------------------異步執行主隊列:在主線程中串行執行任務
- (void)asyncMain {
    NSLog(@"mainThread---%@",[NSThread currentThread]); // 打印當前線程
    NSLog(@"asyncSerial---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_async(queue, ^{
        for(int i=0;i<2;i++){//        任務1
            [NSThread sleepForTimeInterval:2];//模擬耗時操做
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任務2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任務3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"main---end");
}

#pragma mark -----------------------同步執行主隊列
//在主線程中使用 同步執行主隊列,程序會出現死鎖
- (void)syncMain {
    NSLog(@"mainThread---%@",[NSThread currentThread]); // 打印當前線程
    NSLog(@"syncSerial---begin");
    dispatch_queue_t queue = dispatch_get_main_queue();
    dispatch_sync(queue, ^{
        for(int i=0;i<2;i++){//        任務1
            [NSThread sleepForTimeInterval:2];//模擬耗時操做
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任務2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任務3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"main---end");
}



#pragma mark -----------------------異步執行串行對類:開啓新線程,在當前線程下串行執行任務,任務不作等待
- (void)asyncSerial {
    NSLog(@"serialThread---%@",[NSThread currentThread]); // 打印當前線程
    NSLog(@"asyncSerial---begin");
    dispatch_queue_t queue = dispatch_queue_create("serial.queue.test", DISPATCH_QUEUE_SERIAL);
    dispatch_async(queue, ^{
        //        任務1
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];//模擬耗時操做
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任務2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任務3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"asyncSerial---end");
}

#pragma mark -----------------------同步執行串行對類:不開啓新線程,在當前線程下串行執行任務
- (void)syncSerial {
    NSLog(@"serialThread---%@",[NSThread currentThread]); // 打印當前線程
    NSLog(@"syncSerial---begin");
    dispatch_queue_t queue = dispatch_queue_create("serial.queue.test", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        //        任務1
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];//模擬耗時操做
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任務2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任務3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncSerial---end");
}



#pragma mark -------------------------異步執行併發隊列:開啓多個線程,任務交替(同時)執行
- (void)asyncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印當前線程
    NSLog(@"asyncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue.test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //        任務1
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];//模擬耗時操做
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任務2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        //        任務3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"asyncConcurrent---end");
}
#pragma mark ------------------------同步執行併發隊列:不開啓新線程,執行完一個任務在執行下一個任務,由於只有一個線程
- (void)syncConcurrent {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"syncConcurrent---begin");
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue.test", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
//        任務1
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];//模擬耗時操做
            NSLog(@"1-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任務2
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2-------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        //        任務3
        for(int i=0;i<2;i++){
            [NSThread sleepForTimeInterval:2];
            NSLog(@"3-------%@",[NSThread currentThread]);
        }
    });
    NSLog(@"syncConcurrent---end");
}

線程間的通訊

/**
 * 線程間通訊
 */
- (void)communication {
    // 獲取全局併發隊列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    // 獲取主隊列
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    
    dispatch_async(queue, ^{
        // 異步追加任務
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
        
        // 回到主線程
        dispatch_async(mainQueue, ^{
            // 追加在主線程中執行的任務
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        });
    });
}

GCD 柵欄方法:dispatch_barrier_async

咱們有時須要異步執行兩組操做,並且第一組操做執行完以後,才能開始執行第二組操做。這樣咱們就須要一個至關於柵欄同樣的一個方法將兩組異步執行的操做組給分割起來,固然這裏的操做組裏能夠包含一個或多個任務。這就須要用到dispatch_barrier_async方法在兩個操做組間造成柵欄。併發

- (void)barrier {
    dispatch_queue_t queue = dispatch_queue_create("concurrent.queue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    dispatch_barrier_async(queue, ^{
        [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
        NSLog(@"barrier---%@",[NSThread currentThread]);      // 打印當前線程
    });
    dispatch_async(queue, ^{
        // 追加任務3
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    dispatch_async(queue, ^{
        // 追加任務4
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"4---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
}

GCD 延時執行方法:dispatch_after

/**
 * 延時執行方法 dispatch_after
 */
- (void)after {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"asyncMain---begin");
    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0秒後異步追加任務代碼到主隊列,並開始執行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印當前線程
    });
}

GCD 一次性代碼(只執行一次):dispatch_once

/**
 * 一次性代碼(只執行一次)dispatch_once
 */
- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只執行1次的代碼(這裏面默認是線程安全的)
    });
}

GCD 快速迭代方法:dispatch_apply

  • 一般咱們會用 for 循環遍歷,可是 GCD 給咱們提供了快速迭代的函數dispatch_apply。dispatch_apply按照指定的次數將指定的任務追加到指定的隊列中,並等待所有隊列執行結束。
  • dispatch_apply 能夠 在多個線程中同時(異步)遍歷多個數字。
  • 不管是在串行隊列,仍是異步隊列中,dispatch_apply 都會等待所有任務執行完畢,這點就像是同步操做,也像是隊列組中的 dispatch_group_wait方法。

GCD 隊列組:dispatch_group

dispatch_group_notify

監聽 group 中任務的完成狀態,當全部的任務都執行完成後,追加任務到 group 中,並執行任務。app

/**
 * 隊列組 dispatch_group_notify
 */
- (void)groupNotify {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步任務一、任務2都執行完畢後,回到主線程執行下邊任務
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
        NSLog(@"group---end");
    });
}

dispatch_group_wait

暫停當前線程(阻塞當前線程),等待指定的 group 中的任務執行完成後,纔會往下繼續執行。

隊列組 dispatch_group_wait

-(void)groupWait {
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");
    
    dispatch_group_t group =  dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
    });
    
    // 等待上面的任務所有完成後,會往下繼續執行(會阻塞當前線程)
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    
    NSLog(@"group---end");
}

dispatch_group_enter、dispatch_group_leave

  • dispatch_group_enter 標誌着一個任務追加到 group,執行一次,至關於 group 中未執行完畢任務數+1
  • dispatch_group_leave 標誌着一個任務離開了 group,執行一次,至關於 group 中未執行完畢任務數-1。
  • 當 group 中未執行完畢任務數爲0的時候,纔會使dispatch_group_wait解除阻塞,以及執行追加到dispatch_group_notify中的任務。
- (void)groupEnterAndLeave
{
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"group---begin");
    
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任務1
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        // 追加任務2
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印當前線程
        }
        dispatch_group_leave(group);
    });
    
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 等前面的異步操做都執行完畢後,回到主線程.
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
            NSLog(@"3---%@",[NSThread currentThread]);      // 打印當前線程
        }
        NSLog(@"group---end");
    });
    
    //    // 等待上面的任務所有完成後,會往下繼續執行(會阻塞當前線程)
    //    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    //
    //    NSLog(@"group---end");
}

Dispatch Semaphore 線程同步

使用Dispatch Semaphore 能夠實現線程同步,將異步執行任務轉換爲同步執行任務。

- (void)semaphoreSync {
    
    NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印當前線程
    NSLog(@"semaphore---begin");
    
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        // 追加任務1
        [NSThread sleepForTimeInterval:2];              // 模擬耗時操做
        NSLog(@"1---%@",[NSThread currentThread]);      // 打印當前線程
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"semaphore---end,number = %zd",number);
}

NSOperation

簡介

NSOperation、NSOperationQueue 是蘋果提供給咱們的一套多線程解決方案。實際上 NSOperation、NSOperationQueue 是基於 GCD 更高一層的封裝,徹底面向對象。可是比 GCD 更簡單易用、代碼可讀性也更高。

NSOperation、NSOperationQueue 使用步驟

NSOperation 須要配合 NSOperationQueue 來實現多線程。由於默認狀況下,NSOperation 單獨使用時系統同步執行操做,配合 NSOperationQueue 咱們能更好的實現異步執行。

NSOperation 實現多線程的使用步驟分爲三步:

  • 建立操做:先將須要執行的操做封裝到一個 NSOperation 對象中。
  • 建立隊列:建立 NSOperationQueue 對象。
  • 將操做加入到隊列中:將 NSOperation 對象添加到 NSOperationQueue 對象中。

NSOperation 是個抽象類,不能用來封裝操做。咱們只有使用它的子類來封裝操做。咱們有三種方式來封裝操做。

使用子類 NSInvocationOperation
使用子類 NSBlockOperation
自定義繼承自 NSOperation 的子類,經過實現內部相應的方法來封裝操做。

使用子類 NSInvocationOperation

/**
 * 使用子類 NSInvocationOperation
 */
- (void)useInvocationOperation {

    // 1.建立 NSInvocationOperation 對象
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 2.調用 start 方法開始執行操做
    [op start];
}

/**
 * 任務1
 */
- (void)task1 {
    for (int i = 0; i < 2; i++) {
        [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
        NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
    }
}

使用子類 NSBlockOperation

/**
 * 使用子類 NSBlockOperation
 */
- (void)useBlockOperation {

    // 1.建立 NSBlockOperation 對象
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];

    // 2.調用 start 方法開始執行操做
    [op start];
}

addExecutionBlock :

NSBlockOperation 還提供了一個方法 addExecutionBlock:經過 addExecutionBlock: 就能夠爲 NSBlockOperation 添加額外的操做。

NSOperationQueue

NSOperationQueue 一共有兩種隊列:主隊列、自定義隊列。其中自定義隊列同時包含了串行、併發功能。下邊是主隊列、自定義隊列的基本建立方法和特色。

// 主隊列獲取方法 隊列中代碼在主線程運行
NSOperationQueue *queue = [NSOperationQueue mainQueue];

// 自定義隊列建立方法  隊列中代碼在子線程運行
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

將操做加入到隊列中

/**
 * 使用 addOperation: 將操做加入到操做隊列中
 */
- (void)addOperationToQueue {

    // 1.建立隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.建立操做
    // 使用 NSInvocationOperation 建立操做1
    NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];

    // 使用 NSInvocationOperation 建立操做2
    NSInvocationOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task2) object:nil];

    // 使用 NSBlockOperation 建立操做3
    NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"3---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [op3 addExecutionBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"4---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];

    // 3.使用 addOperation: 添加全部操做到隊列中
    [queue addOperation:op1]; // [op1 start]
    [queue addOperation:op2]; // [op2 start]
    [queue addOperation:op3]; // [op3 start]
}

NSOperationQueue 控制串行執行、併發執行

最大併發操做數:maxConcurrentOperationCount

  • maxConcurrentOperationCount 默認狀況下爲-1,表示不進行限制,可進行併發執行。
  • maxConcurrentOperationCount 爲1時,隊列爲串行隊列。只能串行執行。
  • maxConcurrentOperationCount 大於1時,隊列爲併發隊列。操做併發執行,固然這個值不該超過系統限制,即便本身設置一個很大的值,系統也會自動調整爲 min{本身設定的值,系統設定的默認最大值}。
/**
 * 設置 MaxConcurrentOperationCount(最大併發操做數)
 */
- (void)setMaxConcurrentOperationCount {

    // 1.建立隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.設置最大併發操做數
    queue.maxConcurrentOperationCount = 1; // 串行隊列
// queue.maxConcurrentOperationCount = 2; // 併發隊列
// queue.maxConcurrentOperationCount = 8; // 併發隊列

    // 3.添加操做
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"3---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    [queue addOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"4---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
}

NSOperation 操做依賴

NSOperation、NSOperationQueue 最吸引人的地方是它能添加操做之間的依賴關係。經過操做依賴,咱們能夠很方便的控制操做之間的執行前後順序

  • -(void)addDependency:(NSOperation *)op; 添加依賴,使當前操做依賴於操做 op 的完成。
  • -(void)removeDependency:(NSOperation )op; 移除依賴,取消當前操 做對操做 op 的依賴。
  • @property (readonly, copy) NSArray<NSOperation > dependencies; 在當前操做開始執行以前完成執行的全部操做對象數組。

例:好比說有 A、B 兩個操做,其中 A 執行完操做,B 才能執行操做。

- (void)addDependency {

    // 1.建立隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    // 2.建立操做
    NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];
    NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
        }
    }];

    // 3.添加依賴
    [op2 addDependency:op1]; // 讓op2 依賴於 op1,則先執行op1,在執行op2

    // 4.添加操做到隊列中
    [queue addOperation:op1];
    [queue addOperation:op2];

NSOperation、NSOperationQueue 線程間的通訊

- (void)communication {

    // 1.建立隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];

    // 2.添加操做
    [queue addOperationWithBlock:^{
        // 異步進行耗時操做
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
            NSLog(@"1---%@", [NSThread currentThread]); // 打印當前線程
        }

        // 回到主線程
        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // 進行一些 UI 刷新等操做
            for (int i = 0; i < 2; i++) {
                [NSThread sleepForTimeInterval:2]; // 模擬耗時操做
                NSLog(@"2---%@", [NSThread currentThread]); // 打印當前線程
            }
        }];
    }];
}

線程安全

把這一塊單獨提出來是由於不管是使用 NSThread、GCD、NSOperation 等,在多個地方異步同時調用同一方法,會形成結果不符合預期,也就是線程不安全。

線程不安全解決方案:能夠給線程加鎖,在一個線程執行該操做的時候,不容許其餘線程進行操做。iOS 實現線程加鎖有不少種方式。@synchronized、 NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) 等等各類方式,@synchronized、 NSLock這兩種方式比較經常使用。

銀行取錢案例(例子是使用NSThread開闢的線程,給執行的代碼加鎖(或同步代碼塊),其餘兩種多線程方式同理)

- (void)getMoney {
    Account *account = [[Account alloc] init];
    account.accountNumber = @"1603121434";
    account.balance = 1500.0;
    
    NSThread *thread1 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread1 setName:@"Thread1"];
    
    NSThread *thread2 = [[NSThread alloc] initWithTarget:account selector:@selector(draw:) object:@(1000)];
    [thread2 setName:@"Thread2"];
    
    [thread1 start];
    [thread2 start];
}

- (void)draw:(id)money
{
//    當多個線程同時操做的時候,會存在競爭條件,數據結果就沒法保證
//    double drawMoney = [money doubleValue];
//    //判斷餘額是否足夠
//    if (self.balance >= drawMoney)
//    {
//        //當前線程睡1毫秒
//        [NSThread sleepForTimeInterval:0.001];
//        self.balance -= drawMoney;
//        NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
//    }else{
//        //餘額不足,提示
//        NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
//    }
    
    
//    咱們對draw:方法添加了一個同步代碼塊,使用@synchronized包圍的代碼即爲同步代碼塊,同步代碼塊須要一個監聽器,咱們使用account對象自己做爲監聽器,由於是account對象產生的競爭條件,當執行同步代碼塊時須要先獲取監聽器,若是獲取不到則線程會被阻塞,當同步代碼塊執行完成則釋放監聽器
//    @synchronized (self) {
//        double drawMoney = [money doubleValue];
//        if (self.balance >= drawMoney)
//        {
//            //當前線程睡1毫秒
//            [NSThread sleepForTimeInterval:1];
//            self.balance -= drawMoney;
//            NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
//        }else{
//            //餘額不足,提示
//            NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
//        }
//    }
    
    
    
//    咱們使用鎖機制,建立了一個NSLock類的鎖對象,lock方法用於獲取鎖,若是鎖被其餘對象佔用則線程被阻塞,unlock方法用於釋放鎖,以便其餘線程加鎖。
    [self.lock lock];
    double drawMoney = [money doubleValue];
    if (self.balance >= drawMoney)
    {
        //當前線程睡1毫秒
        [NSThread sleepForTimeInterval:1];
        self.balance -= drawMoney;
        NSLog(@"%@ draw money %lf balance left %lf", [[NSThread currentThread] name], drawMoney, self.balance);
    }else{
        //餘額不足,提示
        NSLog(@"%@ Balance Not Enouth", [[NSThread currentThread] name]);
    }
    [self.lock unlock];
}

本文所涉及的代碼:threads

相關文章
相關標籤/搜索