iOS開發多線程--GCD

GCD簡介

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

GCD好處

  • GCD 會自動管理線程的生命周期(創建線程、調度任務、銷毀線程)
  • GCD更接近底層,性能較高

GCD的兩個概念

1.任務
任務就是我們實際要執行的代碼塊。而執行這代碼的方式有兩種:同步執行(sync)異步執行(async)

  • 同步執行(sync)
    • 不具備開啟新線程的能力,只能在當前線程中執行任務
    • 同步任務添加到隊列中,在添加的任務執行結束之前,會一直等待,直到隊列里面的任務完成之后才會繼續執行
  • 異步執行(async)
    • 可以開啟新線程,可以在新線程中執行任務
    • 異步任務添加到隊列中,會繼續執行任務

異步執行async雖然有開啟新線程的能力,但是不一定會開啟新線程,這根任務所指定的隊列類型有關。

2.隊列
隊列是指執行任務的等待隊列,即用來存放任務的隊列。隊列遵循FIFO(先進先出),新任務總是插到隊列的末尾,讀取任務總是從隊列的頭部開始讀取,沒讀取一個任務,就會釋放一個任務。GCD中,有兩種隊列:串行隊列并發隊列,兩者都遵循FIFO原則。

  • 串行隊列(Serial Dispatch Queue)
    • 每次只有一個任務被執行,讓任務一個接著一個執行。(只開啟一個線程)
  • 并發隊列(Concurrent Dispatch Queue)
    • 可以讓多個任務并發(同時)執行。(可以開啟多個線程,并且同時執行任務)

并發隊列的并發功能只有在異步(dispatch_async)函數下才有效

GCD使用步驟

1.創建隊列(串行或并發)

dispatch_queue_t dispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

label的參數表示隊列的唯一標識符,用于debug,可以傳空,推薦使用域名反寫,attr用來決定是串行隊列,還是并發隊列。 DISPATCH_QUEUE_SERIAL表示串行隊列,DISPATCH_QUEUE_CONCURRENT表示并發隊列。

dispatch_queue_t queue = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t queue = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
  • 串行隊列,GCD提供了一個特殊的串行隊列:主隊列(Main Dispatch Queue)
    • 所有放在主隊列中任務,都會放到主線程中執行
    • 可以使用dispatch_get_main_queue()來獲取主隊列
// 主隊列的獲取方法
dispatch_queue_t queue = dispatch_get_main_queue();
  • 并發隊列,GCD提供了全局并發隊列(Global Dispatch Queue)
    • 可以使用dispatch_get_global_queue來獲取。
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

identifier的優先級從高到低:
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND

flags:默認傳0

2.將任務添加到任務的等待隊列中
GCD提供了同步執行任務的創建方法dispatch_sync和異步執行任務的創建方法dispatch_async

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

兩種因素相互組合有四種組合方式

同步 + 并發
異步 + 并發
同步 + 串行
異步 + 串行

但是加上說的兩種特殊隊列:全局并發隊列,主隊列。全局并發隊列可以當成普通的并發隊列來處理,方式主隊列有點特殊,需要額外處理,這就多了兩種方式。

同步 + 主隊列
異步 + 主隊列

組合效果如下表格

并發隊列 串行隊列 主隊列
同步 沒有開啟新線程,串行執行任務 沒有開啟新線程,串行執行任務 主線程調用:死鎖卡住
其他線程調用:沒有開發新線程,串行執行任務
異步 開啟新線程,并發執行任務 開啟新線程,串行執行任務 沒有開啟新線程,串行執行任務

GCD的使用

1.同步+并發

/**
 同步并發執行
 在當前線程執行任務,不會開啟新線程,執行完一個任務,在執行下一個任務
 */
- (void)syncWithConcurrent{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任務
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
}

2018-05-16 14:38:55.788806+0800 MultiThread[5657:399983] currentThread -- <NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:55.789158+0800 MultiThread[5657:399983] begin
2018-05-16 14:38:56.790163+0800 MultiThread[5657:399983] 111----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:57.790860+0800 MultiThread[5657:399983] 111----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:58.791470+0800 MultiThread[5657:399983] 222----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:38:59.794281+0800 MultiThread[5657:399983] 222----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:00.795110+0800 MultiThread[5657:399983] 333----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:01.795762+0800 MultiThread[5657:399983] 333----<NSThread: 0x60400006ca80>{number = 1, name = main}
2018-05-16 14:39:01.796005+0800 MultiThread[5657:399983] end

從上述例子中可以看到

  • 所有的任務都是在當前線程(主線程)中執行的。并沒有開啟新線程。
  • 按順序執行任務。并發隊列雖然可以開啟多個線程,同時執行任務,但是不能創建新線程。所以并發并不存在,實際效果相當于串行。

2.異步 + 并發

/**
 可以開啟多個線程,交替執行任務
 */
- (void)asyncConcurrent{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任務
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

2018-05-16 14:45:25.548159+0800 MultiThread[5724:408572] currentThread -- <NSThread: 0x604000068c40>{number = 1, name = main}
2018-05-16 14:45:25.548681+0800 MultiThread[5724:408572] begin
2018-05-16 14:45:25.548907+0800 MultiThread[5724:408572] end
2018-05-16 14:45:26.549549+0800 MultiThread[5724:408638] 111----<NSThread: 0x60000007a8c0>{number = 5, name = (null)}
2018-05-16 14:45:26.549660+0800 MultiThread[5724:408804] 333----<NSThread: 0x600000263d80>{number = 7, name = (null)}
2018-05-16 14:45:26.549679+0800 MultiThread[5724:408803] 222----<NSThread: 0x600000263380>{number = 6, name = (null)}
2018-05-16 14:45:27.550524+0800 MultiThread[5724:408803] 222----<NSThread: 0x600000263380>{number = 6, name = (null)}
2018-05-16 14:45:27.550526+0800 MultiThread[5724:408804] 333----<NSThread: 0x600000263d80>{number = 7, name = (null)}
2018-05-16 14:45:27.550526+0800 MultiThread[5724:408638] 111----<NSThread: 0x60000007a8c0>{number = 5, name = (null)}

從上述例子可以看出:

  • 除了當前線程(主線程),系統有開啟了三個線程,任務交替執行。
  • begin和end先打印了,說明線程并沒有等待,開啟了新線程,在新線程中執行任務。

3.同步 + 串行

/**
 不會創建新的線程,在當前線程執行,任務串行執行
 */
- (void)syncSerial{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任務
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

2018-05-16 14:59:32.394332+0800 MultiThread[5889:426076] currentThread -- <NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:32.394566+0800 MultiThread[5889:426076] begin
2018-05-16 14:59:33.395773+0800 MultiThread[5889:426076] 111----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:34.397207+0800 MultiThread[5889:426076] 111----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:35.398690+0800 MultiThread[5889:426076] 222----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:36.399925+0800 MultiThread[5889:426076] 222----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:37.400594+0800 MultiThread[5889:426076] 333----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:38.401101+0800 MultiThread[5889:426076] 333----<NSThread: 0x604000074000>{number = 1, name = main}
2018-05-16 14:59:38.401345+0800 MultiThread[5889:426076] end

從上述例子可以看出:

  • 所有任務都是在當前線程(主線程)中執行的,并沒有開啟新的線程(同步執行不具備開啟新線程的能力)
  • 需要等待隊列任務的任務完成
  • 任務按順序執行

4.異步 + 串行

/**
 創建新線程,但是任務是串行的,會一個一個執行
 */
- (void)asyncSerial{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_queue_create("com.easyfly.queue", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任務
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

2018-05-16 15:08:48.563433+0800 MultiThread[6045:441312] currentThread -- <NSThread: 0x60400006b400>{number = 1, name = main}
2018-05-16 15:08:48.563794+0800 MultiThread[6045:441312] begin
2018-05-16 15:08:48.564524+0800 MultiThread[6045:441312] end
2018-05-16 15:08:49.569620+0800 MultiThread[6045:441364] 111----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:50.570245+0800 MultiThread[6045:441364] 111----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:51.574290+0800 MultiThread[6045:441364] 222----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:52.578832+0800 MultiThread[6045:441364] 222----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:53.580927+0800 MultiThread[6045:441364] 333----<NSThread: 0x600000464080>{number = 5, name = (null)}
2018-05-16 15:08:54.586438+0800 MultiThread[6045:441364] 333----<NSThread: 0x600000464080>{number = 5, name = (null)}

從上述例子可以看出:

  • 開啟了一個新的線程
  • 所有任務都是在begin和end打印后執行的
  • 任務是按順序執行的

5.同步 + 主隊列

  1. 在主線程中調用
/**
 卡死
 */
- (void)syncMain{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_get_main_queue();
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任務
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_sync(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

2018-05-16 15:15:40.866943+0800 MultiThread[6145:451264] currentThread -- <NSThread: 0x604000075c80>{number = 1, name = main}
2018-05-16 15:15:40.867132+0800 MultiThread[6145:451264] begin
(lldb)

我們將syncMain()方法放到主線程的隊列中執行,而syncMain()方法中,又在主線程中追加了打印任務。并發情況下,syncMain()在等值打印任務完成,而打印任務又在等著syncMain()的完成,相互等待,導致卡死。

  1. 在其他線程中調用
[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];

2018-05-16 15:21:21.114561+0800 MultiThread[6228:461243] currentThread -- <NSThread: 0x600000470600>{number = 5, name = (null)}
2018-05-16 15:21:21.114724+0800 MultiThread[6228:461243] begin
2018-05-16 15:21:22.153793+0800 MultiThread[6228:461100] 111----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:23.154493+0800 MultiThread[6228:461100] 111----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:24.156163+0800 MultiThread[6228:461100] 222----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:25.157599+0800 MultiThread[6228:461100] 222----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:26.159337+0800 MultiThread[6228:461100] 333----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:27.160866+0800 MultiThread[6228:461100] 333----<NSThread: 0x60400006f180>{number = 1, name = main}
2018-05-16 15:21:27.161208+0800 MultiThread[6228:461243] end

上述例子可以看出:

  • 所有任務都是在主線程中執行,沒有開啟新線程
  • 同步執行任務都是需要等待隊列的任務執行結束
  • 串行隊列,每次只執行一個任務

因為syncMain()任務放在了新開啟的線程中,而打印任務都追加在主隊列中,此時主隊列并沒有其他執行的任務,所以可以直接執行打印任務。

6.異步 + 主隊列

/**
 只在主線程中執行任務,執行完一個任務,再執行下一個任務
 */
- (void)asyncMain{
    NSLog(@"currentThread -- %@", [NSThread currentThread]);
    
    NSLog(@"begin");
    
    dispatch_queue_t queueu = dispatch_get_main_queue();
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"111----%@", [NSThread currentThread]);
        }
    });
    //追加任務
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"222----%@", [NSThread currentThread]);
        }
    });
    
    dispatch_async(queueu, ^{
        for (int i = 0; i < 2; i++) {
            [NSThread sleepForTimeInterval:1];
            NSLog(@"333----%@", [NSThread currentThread]);
        }
    });
    
    NSLog(@"end");
}

018-05-16 15:28:29.018407+0800 MultiThread[6308:470456] currentThread -- <NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:29.018601+0800 MultiThread[6308:470456] begin
2018-05-16 15:28:29.018752+0800 MultiThread[6308:470456] end

2018-05-16 15:28:30.056933+0800 MultiThread[6308:470456] 111----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:31.057944+0800 MultiThread[6308:470456] 111----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:32.059107+0800 MultiThread[6308:470456] 222----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:33.060579+0800 MultiThread[6308:470456] 222----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:34.062021+0800 MultiThread[6308:470456] 333----<NSThread: 0x60400007fa40>{number = 1, name = main}
2018-05-16 15:28:35.063583+0800 MultiThread[6308:470456] 333----<NSThread: 0x60400007fa40>{number = 1, name = main}

從上述例子中可以看出:

  • 所有任務都是在主隊列中執行
  • 異步執行不會做人接等待,可以繼續執行任務
  • 任務按順序執行的,主隊列是串行隊列

GCD線程之間的通信

在其他線程中處理下載圖片,請求數據等操作,在主線程中刷新UI,數據,就需要用到線程之間的通訊。

- (void)communication{
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_queue_t main = dispatch_get_main_queue();
    
    dispatch_async(global, ^{
        for (int i = 0; i < 3; i++) {
            [NSThread sleepForTimeInterval:2];
            NSLog(@"1--- %@", [NSThread currentThread]);
        }
        
        dispatch_async(main, ^{
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2---%@", [NSThread currentThread]);
        });
    });
}

2018-05-16 15:47:23.444937+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:25.450123+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:27.455633+0800 MultiThread[6516:498644] 1--- <NSThread: 0x600000277f00>{number = 5, name = (null)}
2018-05-16 15:47:29.456492+0800 MultiThread[6516:498523] 2---<NSThread: 0x6040000647c0>{number = 1, name = main}

從上述例子中可以看出:

  • 可以看到在其他線程中先執行任務,執行完了之后回到主線程執行主線程的相應操作。

GCD的其他方法

1.柵欄方法(dispatch_barrier_async)

如果我們需要實現異步請求兩個數據,同時B請求必須在A請求完成后才能繼續請求,這就需要一種手段分割開A和B請求,這就是柵欄方法dispatch_barrier_asyncdispatch_barrier_async方法會等待前面添加到并發隊列中的任務完成后,截止執行dispatch_barrier_async的block任務,最后回復正常的異步并發操作。

- (void)barrier{
    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", 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, ^{
        // 追加任務 barrier
        for (int i = 0; i < 2; ++i) {
            [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]);      // 打印當前線程
        }
    });
}

2018-05-16 16:08:35.729731+0800 MultiThread[6772:531874] 1---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:35.729844+0800 MultiThread[6772:531870] 2---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:37.732856+0800 MultiThread[6772:531870] 2---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:37.732856+0800 MultiThread[6772:531874] 1---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:39.735677+0800 MultiThread[6772:531870] barrier---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:41.736375+0800 MultiThread[6772:531870] barrier---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:43.740706+0800 MultiThread[6772:531870] 3---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:43.740710+0800 MultiThread[6772:531874] 4---<NSThread: 0x604000465b00>{number = 5, name = (null)}
2018-05-16 16:08:45.741610+0800 MultiThread[6772:531870] 3---<NSThread: 0x604000473f80>{number = 6, name = (null)}
2018-05-16 16:08:45.741610+0800 MultiThread[6772:531874] 4---<NSThread: 0x604000465b00>{number = 5, name = (null)}

上述例子可以看出:

  • 在執行完柵欄前面的操作之后,才執行柵欄操作,最后再執行柵欄后邊的操作

2.延遲執行方法(dispatch_after)

/**
 * 延時執行方法 dispatch_after
 */
- (void)after {
    NSLog(@"currentThread---%@", [NSThread currentThread]);
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"after---%@", [NSThread currentThread]);
    });
}

2018-05-16 16:16:26.406706+0800 MultiThread[6866:543950] currentThread---<NSThread: 0x60000007f6c0>{number = 1, name = main}
2018-05-16 16:16:28.600856+0800 MultiThread[6866:543950] after---<NSThread: 0x60000007f6c0>{number = 1, name = main}

可以看出來,實際還是存在誤差的,所以要求不嚴格可以說還是用這個方法。

3.GCD一次性代碼(dispatch_once)
創建單例、或者需要整個程序運行期間只執行一次的代碼,我們就用到了dispatch_once方法。

- (void)once {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 這里的代碼指揮執行一次
    });
}

4.循環遍歷方法(dispatch_apply)

dispatch_apply方法會開啟多個線程來異步遍歷數據,無論串行隊列,還是并發隊列,該方法都會等待全部任務完畢,有點像同步操作。

- (void)apply {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    NSLog(@"apply---begin");
    dispatch_apply(5, queue, ^(size_t index) {
        NSLog(@"%zd---%@",index, [NSThread currentThread]);
    });
    NSLog(@"apply---end");
}

2018-05-16 16:34:57.901523+0800 MultiThread[7186:573598] apply---begin
2018-05-16 16:34:57.901834+0800 MultiThread[7186:573598] 0---<NSThread: 0x604000061f40>{number = 1, name = main}
2018-05-16 16:34:57.901899+0800 MultiThread[7186:573672] 1---<NSThread: 0x6000002683c0>{number = 5, name = (null)}
2018-05-16 16:34:57.901937+0800 MultiThread[7186:573662] 2---<NSThread: 0x600000267400>{number = 6, name = (null)}
2018-05-16 16:34:57.901974+0800 MultiThread[7186:573665] 3---<NSThread: 0x600000268280>{number = 7, name = (null)}
2018-05-16 16:34:57.902303+0800 MultiThread[7186:573598] 4---<NSThread: 0x604000061f40>{number = 1, name = main}
2018-05-16 16:34:57.902560+0800 MultiThread[7186:573598] apply---end

5.GCD隊列組(dispatch_group)

有時候會遇到這種情況,要求異步執行兩個耗時任務,然后當兩個任務完成后調回帶主線程,執行主線程的任務。dispatch_group可以把相關的任務歸并到一個組內來執行,通過監聽組內所有任務的執行情況來做相應處理。

5.1 dispatch_group_create
用于創建任務組

dispatch_group_t dispatch_group_create(void);

5.2 dispatch_group_async
把異步任務提交到指定的任務組和指定的隊列執行

void dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);
  • group ——對應的任務組,之后可以通過dispatch_group_wait或者dispatch_group_notify監聽任務組內任務的執行情況
  • queue ——block任務執行的線程隊列,任務組內不同任務的隊列可以不同
  • block —— 執行任務的block

5.3 dispatch_group_enter
用于添加對應任務組中的未執行完畢的任務數,執行一次,未執行完畢的任務數加1,當未執行完畢任務數為0的時候,才會使dispatch_group_wait解除阻塞和dispatch_group_notify的block執行

void dispatch_group_enter(dispatch_group_t group);

dispatch_group_leave
用于減少任務組中的未執行完畢的任務數,執行一次,未執行完畢的任務數減1,dispatch_group_enterdispatch_group_leave要匹配,不然系統會認為group任務沒有執行完畢

void dispatch_group_leave(dispatch_group_t group);

5.4 dispatch_group_wait
等待組任務完成,會阻塞當前線程,當任務組執行完畢時,才會解除阻塞當前線程

long dispatch_group_wait(dispatch_group_t group, 
                         dispatch_time_t timeout);
  • group ——需要等待的任務組
  • timeout ——等待的超時時間(即等多久),單位為dispatch_time_t。如果設置為DISPATCH_TIME_FOREVER,則會一直等待(阻塞當前線程),直到任務組執行完畢

5.5 dispatch_group_notify
待任務組執行完畢時調用,不會阻塞當前線程

void dispatch_group_notify(dispatch_group_t group,
                           dispatch_queue_t queue, 
                           dispatch_block_t block);
  • group ——需要監聽的任務組
  • queue ——block任務執行的線程隊列,和之前group執行的線程隊列無關
  • block ——任務組執行完畢時需要執行的任務block

示例

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"group one start");
    NSLog(@"CurrentThread --- %@", [NSThread currentThread]);
    dispatch_group_async(group, queue, ^{
        dispatch_async(queue, ^{
            NSLog(@"dispatch_async --- %@", [NSThread currentThread]);
            sleep(3);  //模擬異步請求
            NSLog(@"group one finished");
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"dispatch_group_notify --- %@", [NSThread currentThread]);
        NSLog(@"group finished");
    });
}

2018-05-17 09:32:09.284063+0800 MultiThread[10977:949950] group one start
2018-05-17 09:32:09.284450+0800 MultiThread[10977:949950] CurrentThread --- <NSThread: 0x604000077880>{number = 1, name = main}
2018-05-17 09:32:09.284987+0800 MultiThread[10977:950236] dispatch_async --- <NSThread: 0x6000004794c0>{number = 5, name = (null)}
2018-05-17 09:32:09.285092+0800 MultiThread[10977:950242] dispatch_group_notify --- <NSThread: 0x600000479a00>{number = 6, name = (null)}
2018-05-17 09:32:09.286039+0800 MultiThread[10977:950242] group finished
2018-05-17 09:32:12.291271+0800 MultiThread[10977:950236] group one finished

可以看出來,在group中嵌套了一個異步任務是,開啟了一個新線程,新線程是直接返回,group就認為任務完成了。解決上述問題,就需要用了另外兩個方法。

- (void)group{
    dispatch_group_t group = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    NSLog(@"group one start");
    dispatch_group_enter(group);
    NSLog(@"CurrentThread --- %@", [NSThread currentThread]);
    dispatch_group_async(group, queue, ^{
        dispatch_async(queue, ^{
            NSLog(@"dispatch_async --- %@", [NSThread currentThread]);
            sleep(3);  //模擬異步請求
            NSLog(@"group one finished");
            dispatch_group_leave(group);
        });
    });
    
    dispatch_group_notify(group, queue, ^{
        NSLog(@"dispatch_group_notify --- %@", [NSThread currentThread]);
        NSLog(@"group finished");
    });
}

2018-05-17 09:36:48.794270+0800 MultiThread[11054:959258] group one start
2018-05-17 09:36:48.794480+0800 MultiThread[11054:959258] CurrentThread --- <NSThread: 0x604000075080>{number = 1, name = main}
2018-05-17 09:36:48.794824+0800 MultiThread[11054:959669] dispatch_async --- <NSThread: 0x60000046d400>{number = 5, name = (null)}
2018-05-17 09:36:51.798630+0800 MultiThread[11054:959669] group one finished
2018-05-17 09:36:51.798986+0800 MultiThread[11054:959669] dispatch_group_notify --- <NSThread: 0x60000046d400>{number = 5, name = (null)}
2018-05-17 09:36:51.799302+0800 MultiThread[11054:959669] group finished

剛開始,未完成任務數量+1,等到異步任務完成后,數量-1,然后通知group,任務完成了,就會回調dispatch_group_notify的block內容。

6. GCD信號量(dispatch_semaphore)

dispatch_semaphore是GCD中的信號量,可以處理多線程中線程并發的問題,也可以用作同步處理。

創建信號量,里面的參數是表示信號的總量,值必須>=0

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

發送一個信號,信號量的總數會+1

dispatch_semaphore_signal(semaphore);

信號等待,當信號量的總數<=0的時候,會一直等待,直到信號量的總數>0的時候才會繼續下面的執行

dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

注意:dispatch_semaphore_wait
當信號量的總數<=0時候,該函數所在的線程就會等待,而信號量的總數>0的時候,該函數就會繼續往下執行,同時信號量的總數-1
這里有個等待時間的參數,如果在等待的時間內獲得了信號量,那么函數繼續往下執行,如果等待時間內信號量一直為0,那么函數也會繼續往下執行了

/**
 線程同步
 */
- (void)semaphoreDemoOne{
    NSLog(@"currentThread --- %@", [NSThread currentThread]);
    NSLog(@"semaphore --- begin");
    
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    __block int number = 0;
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"1---%@", [NSThread currentThread]);
        
        number = 100;
        
        dispatch_semaphore_signal(semaphore);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"samaphore --- end, number = %d", number);
}

2018-05-17 10:35:31.468369+0800 MultiThread[11654:1048990] currentThread --- <NSThread: 0x60000006f280>{number = 1, name = main}
2018-05-17 10:35:31.468523+0800 MultiThread[11654:1048990] semaphore --- begin
2018-05-17 10:35:33.472110+0800 MultiThread[11654:1049057] 1---<NSThread: 0x600000460940>{number = 5, name = (null)}
2018-05-17 10:35:33.472321+0800 MultiThread[11654:1048990] samaphore --- end, number = 100

如果注釋dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);這句代碼,number 就等于0了。block塊異步執行添加到了全局并發隊列里,所以程序在主線程會跳過block塊(同時開辟子線程異步執行block塊),執行塊外的代碼dispatch_semaphore_wait,因為semaphore信號量為0,且時間為DISPATCH_TIME_FOREVER,所以會阻塞當前線程(主線程),進而只執行子線程的block塊,直到執行塊內部的dispatch_semaphore_signal使得信號量+1。正在被阻塞的線程(主線程)會恢復繼續執行。這樣保證了線程之間的同步。

售票加鎖示例

/**
 模擬兩個窗口售票
 */
- (void)initTickets{
    
    self.semaphoreTicket = dispatch_semaphore_create(1);
    self.ticketCount = 10;
    
    dispatch_queue_t queueOne = dispatch_queue_create("com.easyFly.one", 0);
    dispatch_queue_t queueTwo = dispatch_queue_create("com.easyFly.Two", 0);
    
    __weak typeof(self) weakSelf = self;
    
    dispatch_async(queueOne, ^{
        [weakSelf saleTicket];
    });

    dispatch_async(queueTwo, ^{
        [weakSelf saleTicket];
    });
    
}

- (void)saleTicket{
    while (true) {
//        dispatch_semaphore_wait(_semaphoreTicket, DISPATCH_TIME_FOREVER);
        if (self.ticketCount > 0) {
            self.ticketCount --;
            NSLog(@"剩余票 --- %ld   窗口 --- %@", self.ticketCount, [NSThread currentThread]);
        }else{
            NSLog(@"所有票均已售完");
            // 相當于解鎖
//            dispatch_semaphore_signal(_semaphoreTicket);
            break;
        }
//        dispatch_semaphore_signal(_semaphoreTicket);
    }
}

2018-05-17 10:58:17.676200+0800 MultiThread[12006:1085650] 剩余票 --- 9 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676247+0800 MultiThread[12006:1085651] 剩余票 --- 9 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085651] 剩余票 --- 8 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676370+0800 MultiThread[12006:1085650] 剩余票 --- 8 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676578+0800 MultiThread[12006:1085651] 剩余票 --- 7 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.676641+0800 MultiThread[12006:1085650] 剩余票 --- 6 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676925+0800 MultiThread[12006:1085650] 剩余票 --- 4 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.676751+0800 MultiThread[12006:1085651] 剩余票 --- 5 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.677039+0800 MultiThread[12006:1085650] 剩余票 --- 3 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.677602+0800 MultiThread[12006:1085651] 剩余票 --- 2 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.677828+0800 MultiThread[12006:1085650] 剩余票 --- 1 窗口 --- <NSThread: 0x600000471080>{number = 6, name = (null)}
2018-05-17 10:58:17.678013+0800 MultiThread[12006:1085651] 剩余票 --- 0 窗口 --- <NSThread: 0x600000471040>{number = 5, name = (null)}
2018-05-17 10:58:17.678449+0800 MultiThread[12006:1085650] 所有票均已售完
2018-05-17 10:58:17.678646+0800 MultiThread[12006:1085651] 所有票均已售完

如果注釋semaphore相關代碼,就會出現一張票賣了兩次的情況。打開注釋后

2018-05-17 11:00:22.375800+0800 MultiThread[12051:1089200] 剩余票 --- 9 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.376091+0800 MultiThread[12051:1089202] 剩余票 --- 8 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.376561+0800 MultiThread[12051:1089200] 剩余票 --- 7 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.376950+0800 MultiThread[12051:1089202] 剩余票 --- 6 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.377360+0800 MultiThread[12051:1089200] 剩余票 --- 5 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.377834+0800 MultiThread[12051:1089202] 剩余票 --- 4 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378057+0800 MultiThread[12051:1089200] 剩余票 --- 3 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.378413+0800 MultiThread[12051:1089202] 剩余票 --- 2 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378687+0800 MultiThread[12051:1089200] 剩余票 --- 1 窗口 --- <NSThread: 0x600000469d00>{number = 5, name = (null)}
2018-05-17 11:00:22.378899+0800 MultiThread[12051:1089202] 剩余票 --- 0 窗口 --- <NSThread: 0x604000463e80>{number = 6, name = (null)}
2018-05-17 11:00:22.378996+0800 MultiThread[12051:1089200] 所有票均已售完
2018-05-17 11:00:22.379244+0800 MultiThread[12051:1089202] 所有票均已售完

這就正常的售票了。
解析一下,兩個隊列相當于兩個售票窗口,初始化信號量為1,當一個隊列進入售票過程,dispatch_semaphore_wait使得信號量-1,信號量為0,并執行下方售票代碼,dispatch_semaphore_signal使得信號量+1,相當于解鎖。實現了線程安全。

參考資料

iOS多線程:『GCD』詳盡總結
iOS GCD之dispatch_semaphore(信號量)
關于iOS多線程,你看我就夠了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,572評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,071評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,409評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,569評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,360評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,895評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,979評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,123評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,643評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,559評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,742評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,250評論 5 356
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,981評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,363評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,622評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,354評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,707評論 2 370

推薦閱讀更多精彩內容

  • 本文首發于我的個人博客:「程序員充電站」[https://itcharge.cn]文章鏈接:「傳送門」[https...
    ITCharge閱讀 348,522評論 308 1,926
  • iOS多線程實踐中,常用的就是子線程執行耗時操作,然后回到主線程刷新UI。在iOS中每個進程啟動后都會建立一個主線...
    jackyshan閱讀 1,463評論 2 12
  • 簡介:為什么要用 GCD 呢?因為 GCD 有很多好處啊,具體如下:GCD 可用于多核的并行運算GCD 會自動利用...
    WorldPeace_hp閱讀 384評論 0 4
  • 1、GCD簡介 全名:Grand Central Dispatch,它是蘋果為多核的并行運算提出的解決方案,會合理...
    i_belive閱讀 3,118評論 0 25
  • 呵呵,算法一次主要由程序員使用于當他們不想解釋他們在做什么的時候。
    孫亖閱讀 501評論 0 1