GCD

同步、異步、串行、并行的概念

同步/異步:指的是能否開啟新的線程,同步不能開啟新的線程,異步可以。
串行/并行:指的是任務的執(zhí)行方式,串行是指有多個任務時,各個任務按順序執(zhí)行,完成一個之后才能進行下一個。并行指的是多個任務可以同時執(zhí)行。
異步是多個任務并行的前提條件。

名稱 特點
同步執(zhí)行 不具備開啟新線程的能力,
任務創(chuàng)建后要執(zhí)行完才能繼續(xù)往下走
異步執(zhí)行 具備開啟新線程的能力,
任務創(chuàng)建后可以先繞過,然后再執(zhí)行
串行隊列 隊列中的任務要按順序執(zhí)行
并行隊列 隊列中的任務同時執(zhí)行

線程、任務、隊列的概念

名稱 特點
線程 程序執(zhí)行任務的最小調(diào)度單位
任務 說白了就是一段代碼,在GCD中,
任務就是Block中要執(zhí)行的內(nèi)容
隊列 用來存放“任務”的一個數(shù)組

所有組合

并行隊列 串行隊列 主隊列
異步執(zhí)行 開啟多個新線程,任務同時執(zhí)行 開啟一個新線程,任務按順序執(zhí)行 不開啟新的線程,任務按順序執(zhí)行
同步執(zhí)行 不開啟新線程,任務按順序執(zhí)行 不開啟新線程,任務按順序執(zhí)行 死鎖

死鎖:兩個(多個)線程都要等待對方完成某個操作才能進行下一步,這時就會發(fā)生死鎖。

代碼編程實現(xiàn)

獲取隊列(三種方式)

1、自定義隊列

//自定義并行隊列
-(dispatch_queue_t)createConcurrentQueue{
    dispatch_queue_t queue = dispatch_queue_create("LN_Concurrent", DISPATCH_QUEUE_CONCURRENT);
    return queue;
}

//自定義串行隊列
-(dispatch_queue_t)createSerialQueue{
    dispatch_queue_t queue = dispatch_queue_create("LN_Serial", DISPATCH_QUEUE_SERIAL);
    return queue;
}

2、主線程串行隊列

//獲取主線程串行隊列
-(dispatch_queue_t)getMainSerialQueue{
    dispatch_queue_t queue = dispatch_get_main_queue();
    return queue;
}

3、全局并發(fā)隊列

//獲取全局并發(fā)隊列
-(dispatch_queue_t)getGlobalConcurrentQueue{
    /*
     * 第一個參數(shù):優(yōu)先級別
     DISPATCH_QUEUE_PRIORITY_HIGH
     DISPATCH_QUEUE_PRIORITY_DEFAULT
     DISPATCH_QUEUE_PRIORITY_LOW
     DISPATCH_QUEUE_PRIORITY_GACKGROUND
     */
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    return queue;
}

為隊列添加任務(兩種方式)

1、異步添加任務

//異步
-(void)addTaskWithAsyncInQueue:(dispatch_queue_t)queue{
    dispatch_async(queue, ^{
        NSLog(@"任務1開始");
        sleep(5);
        NSLog(@"任務1結束");
    });
    dispatch_async(queue, ^{
        NSLog(@"任務2開始");
        sleep(2);
        NSLog(@"任務2結束");
    });
    dispatch_async(queue, ^{
        NSLog(@"任務3開始");
        sleep(1);
        NSLog(@"任務3結束");
    });
}

2、同步添加任務

-(void)addTaskWithSyncInQueue:(dispatch_queue_t)queue{
    dispatch_sync(queue, ^{
        NSLog(@"任務1開始");
        sleep(5);
        NSLog(@"任務1結束");
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務2開始");
        sleep(2);
        NSLog(@"任務2結束");
    });
    dispatch_sync(queue, ^{
        NSLog(@"任務3開始");
        sleep(1);
        NSLog(@"任務3結束");
    });
}

組合執(zhí)行

(一)異步+并行

//異步+并行
-(void)lnAsyncConcurrent{
    dispatch_queue_t queue = [self createConcurrentQueue];
    NSLog(@"======start=====");
    [self addTaskWithAsyncInQueue:queue];
    NSLog(@"======end=====");
}

執(zhí)行輸出結果:

2018-04-17 14:28:03.797234+0800 ThreadProject[1708:124655] ======start=====
2018-04-17 14:28:03.797451+0800 ThreadProject[1708:124655] ======end=====
2018-04-17 14:28:03.797510+0800 ThreadProject[1708:124714] 任務1開始
2018-04-17 14:28:03.797512+0800 ThreadProject[1708:124711] 任務3開始
2018-04-17 14:28:03.797512+0800 ThreadProject[1708:124713] 任務2開始
2018-04-17 14:28:04.802118+0800 ThreadProject[1708:124711] 任務3結束
2018-04-17 14:28:05.799360+0800 ThreadProject[1708:124713] 任務2結束
2018-04-17 14:28:08.801884+0800 ThreadProject[1708:124714] 任務1結束

在代碼的任務3中設置斷點,查看線程數(shù)


開啟了三個線程
總結:
- 開了三個新線程
- 函數(shù)在執(zhí)行時,先打印了start和end,再回頭執(zhí)行這三個任務
這是異步執(zhí)行的結果,異步執(zhí)行會開啟新線程,任務可以先繞過不執(zhí)行,回頭再來執(zhí)行。
- 三個任務同時開始
這是并發(fā)的結果

(二)異步+串行

//異步+串行
-(void)lnAsyncSerial{
    dispatch_queue_t queue = [self createSerialQueue];
    NSLog(@"======start=====");
    [self addTaskWithAsyncInQueue:queue];
    NSLog(@"======end=====");
}

執(zhí)行輸出結果:

2018-04-17 15:35:17.971527+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:35:17.971778+0800 ThreadProject[2071:164583] ======end=====
2018-04-17 15:35:17.971823+0800 ThreadProject[2071:164636] 任務1開始
2018-04-17 15:35:22.974270+0800 ThreadProject[2071:164636] 任務1結束
2018-04-17 15:35:22.974649+0800 ThreadProject[2071:164636] 任務2開始
2018-04-17 15:35:24.978868+0800 ThreadProject[2071:164636] 任務2結束
2018-04-17 15:35:24.979185+0800 ThreadProject[2071:164636] 任務3開始
2018-04-17 15:35:25.983574+0800 ThreadProject[2071:164636] 任務3結束
開啟了新線程
總結:相比異步+并行,這個的任務執(zhí)行順序是一個一個來的,上一個任務結束了才開始下一個
任務。
這是串行的結果

(三)異步+主隊列

//異步+主隊列
-(void)lnAsyncMain{
    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"======start=====");
    [self addTaskWithAsyncInQueue:queue];
    NSLog(@"======end=====");
}

執(zhí)行輸出結果:

2018-04-17 15:41:25.099769+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:41:25.099955+0800 ThreadProject[2071:164583] ======end=====
2018-04-17 15:41:25.101869+0800 ThreadProject[2071:164583] 任務1開始
2018-04-17 15:41:30.103308+0800 ThreadProject[2071:164583] 任務1結束
2018-04-17 15:41:30.103591+0800 ThreadProject[2071:164583] 任務2開始
2018-04-17 15:41:32.104805+0800 ThreadProject[2071:164583] 任務2結束
2018-04-17 15:41:32.105079+0800 ThreadProject[2071:164583] 任務3開始
2018-04-17 15:42:34.792503+0800 ThreadProject[2071:164583] 任務3結束
未開啟新線程
總結:執(zhí)行輸出結果與異步+串行是一樣的,這是因為主隊列就是一個串行隊列。
不同的是:不開啟新的線程,而是在主線程上運行。

(四)同步+并行

//同步+并行
-(void)lnSyncConcurrent{
    dispatch_queue_t queue = [self createConcurrentQueue];
    NSLog(@"======start=====");
    [self addTaskWithSyncInQueue:queue];
    NSLog(@"======end=====");
}

執(zhí)行輸出結果:

2018-04-17 15:47:48.893351+0800 ThreadProject[2071:164583] ======start=====
2018-04-17 15:47:48.893553+0800 ThreadProject[2071:164583] 任務1開始
2018-04-17 15:47:53.894956+0800 ThreadProject[2071:164583] 任務1結束
2018-04-17 15:47:53.895313+0800 ThreadProject[2071:164583] 任務2開始
2018-04-17 15:47:55.896732+0800 ThreadProject[2071:164583] 任務2結束
2018-04-17 15:47:55.897079+0800 ThreadProject[2071:164583] 任務3開始
2018-04-17 15:47:56.898450+0800 ThreadProject[2071:164583] 任務3結束
2018-04-17 15:47:56.898782+0800 ThreadProject[2071:164583] ======end=====
總結:根據(jù)程序代碼從上往下走,不開啟新線程。

(五)同步+串行
輸出結果與同步+并行是相同的。

總結:
同步+并行與同步+串行的區(qū)別:同步+并行使用嵌套調(diào)用不會產(chǎn)生死鎖,同步+串行嵌套調(diào)用會產(chǎn)生死鎖。

(六)同步+主隊列

//同步+主隊列
-(void)lnSyncMain{
    dispatch_queue_t queue = dispatch_get_main_queue();
    NSLog(@"======start=====");
    [self addTaskWithSyncInQueue:queue];
    NSLog(@"======end=====");
}

死鎖

死鎖產(chǎn)生原因:主隊列上先有了一個lnSyncMain這個任務,在lnSyncMain方法中又在主隊列上添加了任務。由于是串行,先要lnSyncMain這個任務完成,才執(zhí)行后添加的任務。但是lnSyncMain這個任務的完成又依賴于添加的block。所以就出現(xiàn)了循環(huán)等待,導致死鎖。

死鎖測試:

//嵌套 同步+并行 (不會產(chǎn)生死鎖)
-(void)testForLock{
    dispatch_queue_t queue = [self createConcurrentQueue];
    NSLog(@"======start=====");
    dispatch_sync(queue, ^{
        NSLog(@"任務1開始");
        dispatch_sync(queue, ^{
            NSLog(@"任務2開始");
            NSLog(@"任務2結束");
        });
        NSLog(@"任務1結束");
    });
    NSLog(@"======end=====");
}

//嵌套 同步+串行(會產(chǎn)生死鎖)
-(void)testForLockTwo{
    dispatch_queue_t queue = [self createSerialQueue];
    NSLog(@"======start=====");
    dispatch_sync(queue, ^{
        NSLog(@"任務1開始");
        dispatch_sync(queue, ^{
            NSLog(@"任務2開始");
            NSLog(@"任務2結束");
        });
        NSLog(@"任務1結束");
    });
    NSLog(@"======end=====");
}

注意:不要嵌套使用同步執(zhí)行的串行隊列任務

GCD其他方法

  • dispatch_once
    保證在app運行期間,block中的代碼只執(zhí)行一次。常用于單例的初始化。
  • dispatch_barrier_async
    1、在并行隊列中,等待在dispatch_barrier_async之前加入的任務全部執(zhí)行完成之后(這些任務是并發(fā)執(zhí)行的)
    2、再執(zhí)行dispatch_barrier_async中的任務
    3、dispatch_barrier_async中的任務執(zhí)行完成之后,再去執(zhí)行在dispatch_barrier_async之后加入到隊列中的任務(這些任務是并發(fā)執(zhí)行的)。
    使用場景:多讀單寫
//異步柵欄(多讀單寫場景)
-(void)lnAsyncBarrier{
    dispatch_queue_t queue = [self createConcurrentQueue];
    NSLog(@"======start=====");
    [self addTaskWithAsyncInQueue:queue];
    /*
     *1、等待dispatch_barrier_async之前的任務全部執(zhí)行完
     *2、執(zhí)行dispatch_barrier_async的任務
     *3、執(zhí)行dispatch_barrier_async之后的任務
     */
    dispatch_barrier_async(queue, ^{
        NSLog(@"柵欄方法");
    });
    dispatch_async(queue, ^{
        NSLog(@"任務5開始");
        sleep(3);
        NSLog(@"任務5結束");
    });
    dispatch_async(queue, ^{
        NSLog(@"任務6開始");
        sleep(1);
        NSLog(@"任務6結束");
    });
    NSLog(@"======end=====");
}
  • dispatch_group_notify
    結合dispatch_group_t一起使用,等待組里的任務全部完成后,調(diào)用dispatch_group_notify的block
    使用場景:同時下載多個圖片,所有圖片下載完成之后去更新UI(回到主線程)
//group queue
-(void)lnGroupQueue{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = [self createConcurrentQueue];
    //假設這個數(shù)組用于存放圖片的下載地址
    NSArray *arrayURLs = @[@"圖片下載地址1",@"圖片下載地址2",@"圖片下載地址3"];
    for(NSString *url in arrayURLs){
        dispatch_group_async(group, queue, ^{
            //根據(jù)url去下載圖片
            
            NSLog(@"%@",url);
        });
    }
    //主線程上操作
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        // 當添加到組中的所有任務執(zhí)行完成之后會調(diào)用該Block
        NSLog(@"所有圖片已全部下載完成");
    });
}

項目源碼

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

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