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

  1. pthread
  2. NSThread
  3. GCD
    1. 同步、異步、并發(fā)、串行講解
    2. 創(chuàng)建隊(duì)列的幾種方式
    3. 柵欄函數(shù)
    4. 隊(duì)列組
    5. GCD快速迭代
  4. NSOperation和NSOperationQueue
    1. NSInvocationOperation和NSBlockOperation
    2. NSOperationQueue
    3. 任務(wù)依賴
  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);
        }
        //手動把當(dāng)前線程結(jié)束掉
        // pthread_detach:設(shè)置子線程的狀態(tài)設(shè)置為detached,則該線程運(yùn)行結(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

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

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

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

//queue:隊(duì)列  block:任務(wù)
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

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

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

容易混淆的術(shù)語

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

同步和異步主要影響:能不能開啟新的線程
同步:在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力
異步:在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力(但是不一定能夠開啟新線程,比如異步在主隊(duì)列中執(zhí)行任務(wù))

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

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

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

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

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

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

3. 驗(yàn)證一下所學(xué)知識
下面我們來看看下面幾段代碼,猜一猜運(yùn)行之后結(jié)果是個(gè)啥子嘛。。。

考題一:

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

額。。。知道結(jié)果了么?下面我們來揭曉答案

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


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

為什么dispatch_sync在主線程會死鎖

考題二:

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

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


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

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

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

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

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

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

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

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

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

打印結(jié)果:

開始任務(wù)C
開始任務(wù)A
開始任務(wù)B
任務(wù)C done.
任務(wù)B done.
任務(wù)A done.
----------> barrier <----------
開始任務(wù)D

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

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

dispatch_barrier_async和dispatch_barrier_sync是 GCD 中的兩個(gè)方法。是不是和dispatch_async及dispatch_sync長得很像,就是多了一個(gè)barrier(譯:柵欄)。
沒錯(cuò),除了有dispatch_async或dispatch_sync的作用外(是否阻塞當(dāng)前線程),還有“柵欄”的效果。
意思就是,在該隊(duì)列,以他們?yōu)榻纾懊嫒蝿?wù)執(zhí)行完成,再把自己內(nèi)部的任務(wù)執(zhí)行完,才會執(zhí)行后面的任務(wù)。

知道和dispatch_async及dispatch_sync對應(yīng),就應(yīng)該想到:
dispatch_barrier_async不阻塞當(dāng)前線程,dispatch_barrier_async里面的任務(wù)異步執(zhí)行。
dispatch_barrier_sync會阻塞當(dāng)前線程,dispatch_barrier_sync里面的任務(wù)同步執(zhí)行。

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

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

輸出結(jié)果:


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

    //以下任務(wù)
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)1"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)2"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)3"); });
    dispatch_barrier_sync(concurrentQueue, ^{
        sleep(1);
        NSLog(@"I am barrier");
    });
    NSLog(@"當(dāng)前線程");
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)4"); });
    dispatch_async(concurrentQueue, ^{ NSLog(@"任務(wù)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(@"開始任務(wù)A");
            [NSThread sleepForTimeInterval:3];
            NSLog(@"任務(wù)A done.");
            dispatch_group_leave(group);
        });

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

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

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

輸出如下:

開始任務(wù)C
開始任務(wù)B
開始任務(wù)A
任務(wù)C done.
任務(wù)B done.
任務(wù)A done.
開始任務(wù)D

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

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

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

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

輸出如下:

開始任務(wù)A
任務(wù)A done.
開始任務(wù)B
任務(wù)B done.
開始任務(wù)C
任務(wù)C done.
開始任務(wù)D

在每個(gè)網(wǎng)絡(luò)請求開始前使用 dispatch_group_enter來進(jìn)行標(biāo)識,網(wǎng)絡(luò)請求有回調(diào)后使用dispatch_group_leave來進(jìn)行標(biāo)識,這樣就能保證group_notify在所有網(wǎng)絡(luò)請求都有回調(diào)之后才調(diào)用

5. GCD快速迭代
我們知道for循環(huán)中的代碼是串行執(zhí)行的,如果此時(shí)我們有一系列的耗時(shí)操作需要執(zhí)行,此時(shí)我們可以使用Dispatch_apply函數(shù),他可以異步執(zhí)行,同時(shí)可以利用多核優(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)是在多個(gè)線程中并發(fā)執(zhí)行的。

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

考題一:

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

知道結(jié)果了么?


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


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

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

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

考題二:

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

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

[73860:11959832] 任務(wù)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'

因?yàn)槲覀冊趫?zhí)行完[thread start];的時(shí)候執(zhí)行任務(wù)A,此時(shí)線程就被銷毀了,如果我們要在thread線程中執(zhí)行test方法需要保住該線程的命,即線程?;?/code>,代碼需要修改如下:

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSThread *thread = [[NSThread alloc] initWithBlock:^{
        NSLog(@"任務(wù)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分別對應(yīng) GCD 的 任務(wù) 和 隊(duì)列 。

使用步驟大致如下:

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

1. 任務(wù)

NSOperation只是一個(gè)抽象類,所以不能封裝任務(wù)。
但是我們可以使用它的兩個(gè)子類對象:NSInvocationOperationNSBlockOperation。

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

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

打印結(jié)果:


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

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

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

打印結(jié)果:


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

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

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

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

打印結(jié)果:

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

addExecutionBlock方式添加多個(gè)任務(wù):

    NSBlockOperation *operation = [[NSBlockOperation alloc] init];
    
    [operation addExecutionBlock:^{
        //---下載圖片----1---<NSThread: 0x600000260fc0>{number = 1, name = main}
        NSLog(@"---下載圖片----1---%@", [NSThread currentThread]);
    }];//這種方式只有第一個(gè)是主線程,其余都是子線程
    
    [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. 隊(duì)列

通過上面的介紹我們知道調(diào)用NSOperation對象的start()方法可以啟動任務(wù),但是這樣做他們默認(rèn)是 同步執(zhí)行 的。即使是addExecutionBlock方法,也會在 當(dāng)前線程和其他線程 中執(zhí)行,也就是說還是會占用當(dāng)前線程。此時(shí)我們就需要用到NSOperationQueue了。
只要任務(wù)添加到隊(duì)列,便會自動調(diào)用任務(wù)的start()方法

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

打印結(jié)果:

任務(wù)依賴
需求:此時(shí)有 3 個(gè)任務(wù),這三個(gè)任務(wù)因?yàn)楸容^耗時(shí),所以需要異步并發(fā)執(zhí)行。
任務(wù)一: 從服務(wù)器上下載一張圖片
任務(wù)二:給這張圖片加個(gè)水印
任務(wù)三:把圖片返回給服務(wù)器。

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

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

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

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

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

//5.創(chuàng)建隊(duì)列并加入任務(wù)
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í)行,否則會卡住界面。但有時(shí)還是需要阻塞執(zhí)行,比如進(jìn)入歡迎界面需要沉睡2秒才進(jìn)入主界面時(shí)。

非阻塞執(zhí)行方式

  1. performSelector
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:2.0];
  1. NSTimer定時(shí)器
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實(shí)現(xiàn),這是他們之間最本質(zhì)的區(qū)別,因此如果希望自定義任務(wù),建議使用NSOperation;
2、依賴關(guān)系,NSOpeartion可以通過addDependency來添加任務(wù)的依賴,GCD需要添加依賴只能通過dispatch_barrier_async
3、KVO(鍵值對觀察),可以監(jiān)測operation是否正在執(zhí)行(isExecuted)、是否結(jié)束(isFinished),是否取消(isCanceld)對此GCD無法通過KVO進(jìn)行判斷;
4、優(yōu)先級,NSOpeartion可以設(shè)置queuePriority來設(shè)置優(yōu)先級,跳轉(zhuǎn)任務(wù)的執(zhí)行先后順序,GCD只能設(shè)置隊(duì)列的優(yōu)先級,且任務(wù)是根據(jù)先進(jìn)先出FIFO的原則來執(zhí)行的,不能設(shè)置任務(wù)的優(yōu)先級。
5、繼承,NSOperation是一個(gè)抽象類。實(shí)際開發(fā)中常用的是它的兩個(gè)子類:NSInvocationOperation和NSBlockOperation,同樣我們可以自定義NSOperation,GCD執(zhí)行任務(wù)可以自由組裝,沒有繼承那么高的代碼復(fù)用度;
6、效率,直接使用GCD效率確實(shí)會更高效,NSOperation會多一點(diǎn)開銷,但是通過NSOperation可以獲得依賴,優(yōu)先級,繼承,鍵值對觀察這些優(yōu)勢,相對于多的那么一點(diǎn)開銷確實(shí)很劃算,魚和熊掌不可得兼,取舍在于開發(fā)者自己;
7、NSOperation可以設(shè)置暫停,掛起等操作,可以隨時(shí)取消準(zhǔn)備執(zhí)行的任務(wù)(已經(jīng)在執(zhí)行的不能取消),GCD沒法停止已經(jīng)加入queue 的 block(雖然也能實(shí)現(xiàn),但是需要很復(fù)雜的代碼)
基于GCD簡單高效,更強(qiáng)的執(zhí)行能力,操作不太復(fù)雜的時(shí)候,優(yōu)先選用GCD;而比較復(fù)雜的任務(wù)可以自己通過NSOperation實(shí)現(xiàn)。
8、NSOperation可以設(shè)置最大任務(wù)數(shù),

多線程的安全隱患

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


image

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

image

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容