關(guān)于iOS多線程--這些是你必須知道的

  1. pthread
  2. NSThread
  3. GCD
    1. 同步、異步、并發(fā)、串行講解
    2. 創(chuàng)建隊列的幾種方式
    3. 柵欄函數(shù)
    4. 隊列組
    5. GCD快速迭代
  4. NSOperation和NSOperationQueue
    1. NSInvocationOperation和NSBlockOperation
    2. NSOperationQueue
    3. 任務依賴
  5. GCD和NSOperation的比較
  6. 多線程的安全隱患

關(guān)于多線程巷查,在 iOS 中目前有 4 套方案,他們分別是:


下面我們分別來為大家一一介紹上述方案:

方案一:pthread

#import <pthread.h>


      //創(chuàng)建線程對象
        pthread_t thread = NULL;
        
        //傳遞的參數(shù)
        id str = @"i'm pthread param";
        
        //創(chuàng)建線程
        /* 參數(shù)一:線程對象 傳遞線程對象的地址
           參數(shù)二:線程屬性 包括線程的優(yōu)先級等
           參數(shù)三:子線程需要執(zhí)行的方法
           參數(shù)四:需要傳遞的參數(shù)
         */
        int result = pthread_create(&thread, NULL, operate, (__bridge void *)(str));
        if (result == 0) {
            NSLog(@"創(chuàng)建線程 OK");
        } else {
            NSLog(@"創(chuàng)建線程失敗 %d", result);
        }
        //手動把當前線程結(jié)束掉
        // pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運行結(jié)束后會自動釋放所有資源抹腿。
        pthread_detach(thread);
void *operate(void *params){
    NSString *str = (__bridge NSString *)(params);
    
    NSLog(@"%@ - %@", [NSThread currentThread], str);
    
    return NULL;
}

方案二:NSThread

  • 先創(chuàng)建線程類岛请,再啟動
 // 創(chuàng)建
  NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];

  // 啟動
  [thread start];
  • 創(chuàng)建后立即啟動
 [NSThread detachNewThreadSelector:@selector(run:) toTarget:self withObject:nil];

方案三:GCD

它是蘋果為多核的并行運算提出的解決方案,所以它會自動合理地利用更多的CPU內(nèi)核警绩,最重要的是它會自動管理線程的生命周期(比如創(chuàng)建線程崇败、調(diào)度任務、銷毀線程)

1. 同步房蝉、異步僚匆、并發(fā)、串行講解
GCD中有2個用來執(zhí)行任務的函數(shù)

用同步的方式執(zhí)行任務

//queue:隊列  block:任務
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

用異步的方式執(zhí)行任務

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

容易混淆的術(shù)語

有4個術(shù)語比較容易混淆:同步搭幻、異步咧擂、并發(fā)串行

同步和異步主要影響:能不能開啟新的線程
同步:在當前線程中執(zhí)行任務檀蹋,不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務松申,具備開啟新線程的能力(但是不一定能夠開啟新線程,比如異步在主隊列中執(zhí)行任務)

并發(fā)和串行主要影響:任務的執(zhí)行方式
并發(fā):多個任務并發(fā)(同時)執(zhí)行
串行:一個任務執(zhí)行完畢后,再執(zhí)行下一個任務

各種隊列的執(zhí)行效果

注意:使用sync函數(shù)往當前串行隊列中添加任務俯逾,會卡住當前的串行隊列(產(chǎn)生死鎖)

2. 創(chuàng)建隊列的幾種方式

  • 主隊列: 它是一個特殊的 串行隊列, 任何需要刷新 UI 的工作都要在主隊列執(zhí)行贸桶。
 dispatch_queue_t queue = dispatch_get_main_queue();
  • 自定義隊列: 自己可以創(chuàng)建 串行隊列, 也可以創(chuàng)建 并行隊列
  //串行隊列
  dispatch_queue_t queue = dispatch_queue_create("test1", NULL);
  dispatch_queue_t queue = dispatch_queue_create("test2", DISPATCH_QUEUE_SERIAL);

  //并行隊列
  dispatch_queue_t queue = dispatch_queue_create("test3", DISPATCH_QUEUE_CONCURRENT);
  • 全局并行隊列
  dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

3. 驗證一下所學知識
下面我們來看看下面幾段代碼桌肴,猜一猜運行之后結(jié)果是個啥子嘛皇筛。。坠七。

考題一:

    NSLog(@"任務一");
    dispatch_sync(dispatch_get_main_queue(), ^{
         NSLog(@"任務二");
    });
    NSLog(@"任務三");

額水醋。旗笔。。知道結(jié)果了么拄踪?下面我們來揭曉答案

執(zhí)行結(jié)果:


為什么會這樣呢?
因為同步任務會阻塞當前線程蝇恶,然后把 Block 中的任務放到主隊列中執(zhí)行,隊列是FIFO,所以Block中的任務只有等到dispatch_sync執(zhí)行完畢后才會執(zhí)行,但是dispatch_sync要想執(zhí)行完成必須Block中的任務執(zhí)行完畢后才會結(jié)束.這就是非常經(jīng)典的死鎖現(xiàn)象.

為什么dispatch_sync在主線程會死鎖

考題二:

        dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
        NSLog(@"任務一");
        dispatch_async(queue, ^{
            NSLog(@"任務二");
            dispatch_sync(queue, ^{
                NSLog(@"任務三");
            });
            NSLog(@"任務四");
        });
        NSLog(@"任務五");

看了考題一的分析 我相信考題二難不住你的惶桐,我們來看看打印結(jié)果:


其實原因跟上一個例子我們分析的原因類似,記住這句結(jié)論就好:

注意:使用sync函數(shù)往當前串行隊列中添加任務撮弧,會卡住當前的串行隊列(產(chǎn)生死鎖)

3. 柵欄函數(shù)
在項目中有很多場景需要控制任務的執(zhí)行順序,比如需要等任務A, 任務B, 任務C都完成后(其中A, B, C沒有順序要求), 才進行下一步的處理任務, 可以使用 dispatch_group很方便的完成 (也可以使用柵欄函數(shù))
如果上面的A, B, C任務順序也有順序要求呢? 必須A任務完成后, 才能進行B任務, B完成后才進行C任務, 這時我們就需要用到柵欄函數(shù)

dispatch_barrier_async:在進程管理中起到一個柵欄的作用,該函數(shù)需要同dispatch_queue_create函數(shù)生成的并發(fā)隊列一起使用才能生效姚糊。

第一種情況
A, B, C任務完成之后(A, B, C無順序要求), 進行任務D
1.使用dispatch_barrier

        dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);

        dispatch_async(queue, ^{
            NSLog(@"開始任務A");
            [NSThread sleepForTimeInterval:1];
            NSLog(@"任務A done.");
        });
        dispatch_async(queue, ^{
            NSLog(@"開始任務B");
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"任務B done.");
        });
        dispatch_async(queue, ^{
            NSLog(@"開始任務C");
            [NSThread sleepForTimeInterval:0.2];
            NSLog(@"任務C done.");
        });

        dispatch_barrier_async(queue, ^{
            NSLog(@"----------> barrier <----------");
        });

        dispatch_async(queue, ^{
            NSLog(@"開始任務D");
        });

打印結(jié)果:

開始任務C
開始任務A
開始任務B
任務C done.
任務B done.
任務A done.
----------> barrier <----------
開始任務D

可以看出在執(zhí)行完柵欄前面的操作之后才執(zhí)行柵欄操作贿衍,然后再執(zhí)行柵欄后邊的操作。
如果不加barrier 函數(shù), 輸出如下:

開始任務B
開始任務A
開始任務D
開始任務C
任務C done.
任務B done.
任務A done.
dispatch_barrier_async和dispatch_barrier_sync使用區(qū)別:

dispatch_barrier_async和dispatch_barrier_sync是 GCD 中的兩個方法叛拷。是不是和dispatch_async及dispatch_sync長得很像舌厨,就是多了一個barrier(譯:柵欄)岂却。
沒錯忿薇,除了有dispatch_async或dispatch_sync的作用外(是否阻塞當前線程),還有“柵欄”的效果躏哩。
意思就是署浩,在該隊列,以他們?yōu)榻缟ǔ撸懊嫒蝿請?zhí)行完成筋栋,再把自己內(nèi)部的任務執(zhí)行完,才會執(zhí)行后面的任務正驻。

知道和dispatch_async及dispatch_sync對應弊攘,就應該想到:
dispatch_barrier_async不阻塞當前線程,dispatch_barrier_async里面的任務異步執(zhí)行姑曙。
dispatch_barrier_sync會阻塞當前線程襟交,dispatch_barrier_sync里面的任務同步執(zhí)行。

 dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQ", DISPATCH_QUEUE_CONCURRENT);

    //以下任務
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務1"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務2"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務3"); });
    dispatch_barrier_async(concurrentQueue, ^{
        sleep(1);
        NSLog(@"I am barrier");
    });
    NSLog(@"當前線程");
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務4"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務5"); });

輸出結(jié)果:


image.png
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrentQ", DISPATCH_QUEUE_CONCURRENT);

    //以下任務
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務1"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務2"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務3"); });
    dispatch_barrier_sync(concurrentQueue, ^{
        sleep(1);
        NSLog(@"I am barrier");
    });
    NSLog(@"當前線程");
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務4"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務5"); });

輸出結(jié)果

2.使用 dispatch_group

      dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);

        dispatch_group_t group = dispatch_group_create();

        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"開始任務A");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"任務A done.");
            dispatch_group_leave(group);
        });

        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"開始任務B");
            [NSThread sleepForTimeInterval:2];
            NSLog(@"任務B done.");
            dispatch_group_leave(group);
        });

        dispatch_group_enter(group);
        dispatch_async(queue, ^{
            NSLog(@"開始任務C");
            [NSThread sleepForTimeInterval:1];
            NSLog(@"任務C done.");
            dispatch_group_leave(group);
        });

        dispatch_group_notify(group, queue, ^{
            NSLog(@"開始任務D");
        });

輸出如下:

開始任務C
開始任務B
開始任務A
任務C done.
任務B done.
任務A done.
開始任務D

第二種情況伤靠,任務依賴
A, B, C任務完成之后(A, B, C順序要求依次執(zhí)行), 進行任務D

        dispatch_queue_t queue = dispatch_queue_create("com.test.queue", DISPATCH_QUEUE_CONCURRENT);

        dispatch_barrier_async(queue, ^{
            NSLog(@"開始任務A");
            [NSThread sleepForTimeInterval:1];
            NSLog(@"任務A done.");
        });
        dispatch_barrier_async(queue, ^{
            NSLog(@"開始任務B");
            [NSThread sleepForTimeInterval:0.5];
            NSLog(@"任務B done.");
        });
        dispatch_barrier_async(queue, ^{
            NSLog(@"開始任務C");
            [NSThread sleepForTimeInterval:0.2];
            NSLog(@"任務C done.");
        });

        dispatch_barrier_async(queue, ^{
            NSLog(@"開始任務D");
        });

輸出如下:

開始任務A
任務A done.
開始任務B
任務B done.
開始任務C
任務C done.
開始任務D

在每個網(wǎng)絡請求開始前使用 dispatch_group_enter來進行標識捣域,網(wǎng)絡請求有回調(diào)后使用dispatch_group_leave來進行標識,這樣就能保證group_notify在所有網(wǎng)絡請求都有回調(diào)之后才調(diào)用

5. GCD快速迭代
我們知道for循環(huán)中的代碼是串行執(zhí)行的宴合,如果此時我們有一系列的耗時操作需要執(zhí)行焕梅,此時我們可以使用Dispatch_apply函數(shù),他可以異步執(zhí)行卦洽,同時可以利用多核優(yōu)勢贞言,完美替代for循環(huán)。

  dispatch_apply(10, dispatch_get_global_queue(0, 0), ^(size_t index) {
        NSLog(@"%zd = %@",index,[NSThread currentThread]);
    });

執(zhí)行結(jié)果如下:


可以看到上述循環(huán)是在多個線程中并發(fā)執(zhí)行的阀蒂。

6. 考題:猜測打印結(jié)果

考題一:

- (void)test{
    NSLog(@"任務B");
}
- (void)viewDidLoad {
    [super viewDidLoad];    
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務A");
        [self performSelector:@selector(test) withObject:nil afterDelay:1.0];
         NSLog(@"任務C");
    });
}

知道結(jié)果了么?


我們來看看打印結(jié)果:


為什么只輸出了任務A和任務C而沒有任務B呢?其實這里涉及到了RunLoop的知識,因為performSelector:withObject:afterDelay:的本質(zhì)是向RunLoop中添加定時器,而子線程中默認是沒有開啟RunLoop的,所以這里我們需要稍微改動下代碼,如下;

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"任務A");
       
        [self performSelector:@selector(hahha) withObject:nil afterDelay:1.0];
        
        NSLog(@"任務C");
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    });
}

關(guān)于RunLoop 有興趣的朋友可以看看我的這篇文章: RunLoop的使用

考題二:

- (void)test{
    NSLog(@"任務B");
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"任務A");
    }];
    [thread start];
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

執(zhí)行結(jié)果:

[73860:11959832] 任務A
[73860:11959410] *** Terminating app due to uncaught exception 'NSDestinationInvalidException', reason: '*** -[ViewController performSelector:onThread:withObject:waitUntilDone:modes:]: target thread exited while waiting for the perform'

因為我們在執(zhí)行完[thread start];的時候執(zhí)行任務A,此時線程就被銷毀了,如果我們要在thread線程中執(zhí)行test方法需要保住該線程的命,即線程备么埃活,代碼需要修改如下:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"任務A");
        
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    }];
    [thread start];
    [self performSelector:@selector(test) onThread:thread withObject:nil waitUntilDone:YES];
}

方案四:NSOperation和NSOperationQueue

NSOperation 是蘋果公司對 GCD面向?qū)ο蟮姆庋b打肝,所以使用起來非常方便。
NSOperationNSOperationQueue分別對應 GCD 的 任務 和 隊列 挪捕。

使用步驟大致如下:

  1. 先將需要執(zhí)行的操作封裝到一個NSOperation對象中
  2. 然后將NSOperation對象添加到NSOperationQueue中
  3. 系統(tǒng)會?動將NSOperationQueue中的NSOperation取出來
  4. 將取出的NSOperation封裝的操作放到?條新線程中執(zhí)?

1. 任務

NSOperation只是一個抽象類粗梭,所以不能封裝任務。
但是我們可以使用它的兩個子類對象:NSInvocationOperation级零、NSBlockOperation断医。

  • NSInvocationOperation : 需要傳入一個方法名。
 //1.創(chuàng)建NSInvocationOperation對象
  NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];

  //2.開始執(zhí)行
  [operation start];

打印結(jié)果:


其實等價于[self run];在主線程中執(zhí)行奏纪。

如果我們想讓任務在子線程中執(zhí)行鉴嗤,我們需要創(chuàng)建一個NSOperationQueue,如下:

     // 創(chuàng)建隊列
     NSOperationQueue *queue = [[NSOperationQueue alloc] init];  
       // 創(chuàng)建操作
    NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(run) object:nil];
       // 添加操作到隊列中序调,會自動異步執(zhí)行
    [queue addOperation:operation];

打印結(jié)果:


注意:操作對象默認在主線程中執(zhí)行醉锅,只有將NSOperation放到一個 NSOperationQueue中,才會異步執(zhí)行操作

  • NSBlockOperation:用來并發(fā)的執(zhí)行一個或者多個Block對象发绢。

注意:addExecutionBlock:該方法只要NSBlockOperation封裝的操作數(shù) > 1笋婿,就會異步執(zhí)行操作

       //1.創(chuàng)建NSBlockOperation對象
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"%@", [NSThread currentThread]);
    }];
    
    //2.開始任務
    [operation start];

打印結(jié)果:

<NSThread: 0x604000074780>{number = 1, name = main}

addExecutionBlock方式添加多個任務:

    NSBlockOperation *operation = [[NSBlockOperation alloc] init];
    
    [operation addExecutionBlock:^{
        //---下載圖片----1---<NSThread: 0x600000260fc0>{number = 1, name = main}
        NSLog(@"---下載圖片----1---%@", [NSThread currentThread]);
    }];//這種方式只有第一個是主線程,其余都是子線程
    
    [operation addExecutionBlock:^{
        //---下載圖片----2---<NSThread: 0x600000263f40>{number = 3, name = (null)}
        NSLog(@"---下載圖片----2---%@", [NSThread currentThread]);
    }];
    
    [operation addExecutionBlock:^{
        //---下載圖片----3---<NSThread: 0x60800026c440>{number = 4, name = (null)}
        NSLog(@"---下載圖片----3---%@", [NSThread currentThread]);
    }];
    
    [operation start];

2. 隊列

通過上面的介紹我們知道調(diào)用NSOperation對象的start()方法可以啟動任務赶袄,但是這樣做他們默認是 同步執(zhí)行 的挟憔。即使是addExecutionBlock方法,也會在 當前線程和其他線程 中執(zhí)行墩朦,也就是說還是會占用當前線程坯认。此時我們就需要用到NSOperationQueue了。
只要任務添加到隊列氓涣,便會自動調(diào)用任務的start()方法

      //1.創(chuàng)建隊列
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"---下載圖片----1---%@", [NSThread currentThread]);
    }];
    [operation addExecutionBlock:^{
        NSLog(@"---下載圖片----2---%@", [NSThread currentThread]);
    }];
    
    // 2.添加操作到隊列中(自動異步執(zhí)行)
    [queue addOperation:operation];

打印結(jié)果:

任務依賴
需求:此時有 3 個任務牛哺,這三個任務因為比較耗時,所以需要異步并發(fā)執(zhí)行劳吠。
任務一: 從服務器上下載一張圖片
任務二:給這張圖片加個水印
任務三:把圖片返回給服務器引润。

這時候就需要控制任務的執(zhí)行順序了

//1.任務一:下載圖片
NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"下載圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//2.任務二:打水印
NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"打水印   - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//3.任務三:上傳圖片
NSBlockOperation *operation3 = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"上傳圖片 - %@", [NSThread currentThread]);
    [NSThread sleepForTimeInterval:1.0];
}];

//4.設(shè)置依賴
[operation2 addDependency:operation1];      //任務二依賴任務一
[operation3 addDependency:operation2];      //任務三依賴任務二

//5.創(chuàng)建隊列并加入任務
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperations:@[operation3, operation2, operation1] waitUntilFinished:NO];

打印結(jié)果

  • 注意:不能添加相互依賴,比如 A依賴B赴背,B又依賴A椰拒,否則會造成死鎖
1. 從其他線程回到主線程的方法

我們都知道在其他線程操作完成后必須到主線程更新UI。所以凰荚,介紹完所有的多線程方案后燃观,我們來看看有哪些方法可以回到主線程。

  • NSThread
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];
  • GCD
dispatch_async(dispatch_get_main_queue(), ^{

});

  • NSOperationQueue
[[NSOperationQueue mainQueue] addOperationWithBlock:^{

}];

2. 延遲執(zhí)行方案

公用延遲執(zhí)行方法

- (void)delayMethod{
    NSLog(@"delayMethodEnd");
}

線程阻塞式

1.NSThread線程的sleep

[NSThread sleepForTimeInterval:2.0];

此方法是一種阻塞執(zhí)行方式便瑟,建議放在子線程中執(zhí)行缆毁,否則會卡住界面。但有時還是需要阻塞執(zhí)行到涂,比如進入歡迎界面需要沉睡2秒才進入主界面時脊框。

非阻塞執(zhí)行方式

  1. performSelector
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:2.0];
  1. NSTimer定時器
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(delayMethod) userInfo:nil repeats:NO];
  1. GCD的方式
dispatch_time_t delayTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0  NSEC_PER_SEC));
dispatch_after(delayTime, dispatch_get_main_queue(), ^{
    [weakSelf delayMethod];
});

此方法可以在參數(shù)中選擇執(zhí)行的線程颁督,是一種非阻塞執(zhí)行方式

GCD和NSOperation的比較

GCD 和 NSOperation的區(qū)別主要表現(xiàn)在以下幾方面:

1、GCD是一套 C 語言API,執(zhí)行和操作簡單高效浇雹,NSOperation底層也通過GCD實現(xiàn)沉御,這是他們之間最本質(zhì)的區(qū)別,因此如果希望自定義任務昭灵,建議使用NSOperation吠裆;
2、依賴關(guān)系烂完,NSOpeartion可以通過addDependency來添加任務的依賴试疙,GCD需要添加依賴只能通過dispatch_barrier_async
3、KVO(鍵值對觀察)抠蚣,可以監(jiān)測operation是否正在執(zhí)行(isExecuted)祝旷、是否結(jié)束(isFinished),是否取消(isCanceld)對此GCD無法通過KVO進行判斷嘶窄;
4怀跛、優(yōu)先級,NSOpeartion可以設(shè)置queuePriority來設(shè)置優(yōu)先級护侮,跳轉(zhuǎn)任務的執(zhí)行先后順序敌完,GCD只能設(shè)置隊列的優(yōu)先級,且任務是根據(jù)先進先出FIFO的原則來執(zhí)行的羊初,不能設(shè)置任務的優(yōu)先級。
5什湘、繼承长赞,NSOperation是一個抽象類。實際開發(fā)中常用的是它的兩個子類:NSInvocationOperation和NSBlockOperation闽撤,同樣我們可以自定義NSOperation得哆,GCD執(zhí)行任務可以自由組裝,沒有繼承那么高的代碼復用度哟旗;
6贩据、效率,直接使用GCD效率確實會更高效闸餐,NSOperation會多一點開銷饱亮,但是通過NSOperation可以獲得依賴,優(yōu)先級舍沙,繼承近上,鍵值對觀察這些優(yōu)勢,相對于多的那么一點開銷確實很劃算拂铡,魚和熊掌不可得兼壹无,取舍在于開發(fā)者自己葱绒;
7、NSOperation可以設(shè)置暫停斗锭,掛起等操作地淀,可以隨時取消準備執(zhí)行的任務(已經(jīng)在執(zhí)行的不能取消),GCD沒法停止已經(jīng)加入queue 的 block(雖然也能實現(xiàn),但是需要很復雜的代碼)
基于GCD簡單高效,更強的執(zhí)行能力岖是,操作不太復雜的時候,優(yōu)先選用GCD;而比較復雜的任務可以自己通過NSOperation實現(xiàn)骚秦。
8、NSOperation可以設(shè)置最大任務數(shù)璧微,

多線程的安全隱患

當多個線程同時訪問同一個資源時作箍,很容易引發(fā)數(shù)據(jù)錯亂和數(shù)據(jù)安全問題,比如下圖:


image

那么我們該如何去解決這個問題呢前硫?
我們可以使用線程同步技術(shù)胞得。所謂同步,就是協(xié)同步調(diào)屹电,按預定的先后次序進行阶剑。常見的線程同步技術(shù)就是加鎖

image

關(guān)于鎖的實現(xiàn)方案 網(wǎng)上有很多,這里我就不再列舉了危号,可以參考:
iOS中保證線程安全的幾種方式與性能對比
iOS 常見知識點(三):Lock
深入理解iOS開發(fā)中的鎖

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末牧愁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子外莲,更是在濱河造成了極大的恐慌猪半,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件偷线,死亡現(xiàn)場離奇詭異磨确,居然都是意外死亡,警方通過查閱死者的電腦和手機声邦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門乏奥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亥曹,你說我怎么就攤上這事邓了。” “怎么了媳瞪?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵骗炉,是天一觀的道長。 經(jīng)常有香客問我材失,道長痕鳍,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮笼呆,結(jié)果婚禮上熊响,老公的妹妹穿的比我還像新娘。我一直安慰自己诗赌,他們只是感情好汗茄,可當我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著铭若,像睡著了一般洪碳。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上叼屠,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天瞳腌,我揣著相機與錄音,去河邊找鬼镜雨。 笑死嫂侍,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的荚坞。 我是一名探鬼主播挑宠,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼颓影!你這毒婦竟也來了各淀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤诡挂,失蹤者是張志新(化名)和其女友劉穎碎浇,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體咆畏,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡南捂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了旧找。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡麦牺,死狀恐怖钮蛛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剖膳,我是刑警寧澤魏颓,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站吱晒,受9級特大地震影響甸饱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一叹话、第九天 我趴在偏房一處隱蔽的房頂上張望偷遗。 院中可真熱鬧,春花似錦驼壶、人聲如沸氏豌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽泵喘。三九已至,卻和暖如春般妙,著一層夾襖步出監(jiān)牢的瞬間纪铺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工碟渺, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留鲜锚,地道東北人。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓止状,卻偏偏與公主長得像烹棉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子怯疤,可洞房花燭夜當晚...
    茶點故事閱讀 44,974評論 2 355

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