iOS開發多線程篇-GCD

上篇文章介紹了多線程是什么、線程的進程的區別,在這篇文章中,主要介紹iOS開發中多線程GCD的使用方式和注意事項,同時會給出幾種多線程的案例。

一.概述

iOS中目前有4套多線程方案,分別是

Pthreads

NSThread

GCD

NSOperation & NSOperationQueue

接下來主要講解iOS開發中GCD的使用

二.GCD

Grand Centeral Dispatch ,是蘋果為多核的并行運算提出的解決方案,所以會自動合理地利用更多的CPU內核(比如雙核、四核),最重要的是它會自動管理線程的生命周期(創建線程、調度任務、銷毀線程),完全不需要我們管理,我們只需要告訴該干什么就行。GCD使用的是C語言,不過由于使用了Block,使用起來更加方便靈活,目前基本大家都使用GCD解決多線程問題。

  • GCD的優勢
  • GCD是蘋果為多核的并行運算提出的解決方案
  • GCD會自動利用更多的CPU內核
  • GCD會自動管理線程的生命周期創建線程 調度任務``銷毀線程
  • 程序員只需要告訴GCD想要執行什么任務,不需要管理任何線程管理代碼

三.任務和隊列

GCD中,加入了兩個非常重要的概念:任務隊列

  • 任務:即你想要進行的操作,比如說網絡請求,數據緩存等,在GCD中就是一個Block,所以添加任務十分方便。任務有兩種執行方式:同步執行和異步執行,他們之間的區別是 是否會創建新的線程

同步(sync)操作:會阻塞當前線程并等待Block中的任務執行完畢,然后當前線程才會繼續往下運行

異步(async)操作:當前線程會直接往下執行,不會阻塞當前線程

同步(sync)和異步(async)的主要區別在于會不會阻塞當前線程,直到Block中的任務執行完畢

  • 隊列:用于存放任務,一共有兩種隊列
    串行隊列中的任務會根據隊列的定義FIFO的執行,一個接一個的,先進先出的執行

放到串行隊列的任務,GCDFIFO(先進先出)地取出來一個,執行一個,然后取下一個,這樣一個一個的執行。

放到并行隊列的任務,GCD也會FIFO的取出來,但不同的是,它取出來一個就會放到別的線程,然后再取出來一個又放到另一個的線程,這樣由于取的動作很快,忽略不計,看起來,所以的任務都是一起執行的,不過需要注意,GCD會根據系統資源控制并行的數量,所以如果任務很多,它并不會讓所有任務同時執行。

| | 同步執行 |異步執行
|-----|
|串行隊列|當前線程,一個一個執行|其他線程,一個一個執行
|并行隊列|當前線程,一個一個執行| 開很多線程,一起執行

四.創建隊列

主隊列:這是一個特殊的串行隊列,用于刷新UI,任何需要刷新UI的工作都要在主隊列執行,所以一般耗時的任務都要放到別的線程執行

//Objective-C
dispatch_queue_t queue = dispatch_get_main_queue();

//Swift
let queue = DispatchQueue.main

自己創建的隊列:第一個參數是標識符,用于Debug的時候標識唯一的隊列,可以為空。具體可以查看Xcode的文檔查看參數意義

自己可以創建串行隊列,也可以創建并行隊列,它有兩個參數,第一個上面已經說了,第二個參數用了表示創建的隊列是串行的還是并行的,傳入DISPATCH_QUEUE_SERIAL或NULL標示創建串行隊列,傳入DISPATCH_QUEUE_CONCURRENT表示創建并行隊列

//Objective-C
//串行隊列
dispatch_queue_t serialQueue = dispatch_queue_create("serial1", NULL);

dispatch_queue_t seqialQueue = dispatch_queue_create("serial2", DISPATCH_QUEUE_SERIAL);

//并行隊列
dispatch_queue_t concurrentQueue = dispatch_queue_create("concurrent", DISPATCH_QUEUE_CONCURRENT);


//Swift
//主隊列(串行)
let queue = DispatchQueue.main

全局并行隊列:只要是并行任務一般都加入到這個隊列。這是系統提供的一個并發隊列

//全局并發隊列
//Objective-C
dispatch_queue_t globeQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//Swift
let serialQueue = DispatchQueue.global()

五.創建任務

  • 同步任務:會阻塞當前線程(SYNC)

Objective-C

//同步任務
dispatch_sync(serialQueue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
異步任務:不會阻塞當前線程(ASYNC)
Objective-C

//異步任務
dispatch_sync(serialQueue, ^{
        NSLog(@"%@",[NSThread currentThread]);
    });
示例一:

以下代碼在主線程調用,結果是什么?

NSLog(@"before - %@",[NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"sync- %@",[NSThread currentThread]);
});

NSLog(@"after - %@",[NSThread currentThread]);

答案:只會打印第一句:before - <NSThread: 0x600000066440>{number = 1, name = main},然后主線程就卡死,程序奔潰

解釋:同步任務會阻塞當前線程,然后把Block中的任務放到指定的隊列中執行,只有等到Block中的任務完成后才會讓線程繼續往下運行。
那么這里的步驟就是:打印完第一句后,dispatch_sync立即阻塞當前的主線程,然后把Block中的任務放到main_queue,可是main_queue中的任務會被取出來放到主線程中執行,但主線程這個時候已經被阻塞了,所以Block中的任務就不能完成,它不完成,dispatch_sync就會一直阻塞主線程,這就是死鎖現象,導致主線程一直卡死

示例二

以下代碼會產生什么結果?

dispatch_queue_t serialQueue = dispatch_queue_create("serial1", NULL);

NSLog(@"begain - %@",[NSThread currentThread]);

dispatch_async(serialQueue, ^{
        NSLog(@"beforeSync: %@",[NSThread currentThread]);

        dispatch_sync(serialQueue, ^{
            NSLog(@"sync-: %@",[NSThread currentThread]);
        });

        NSLog(@" afterSync-: %@",[NSThread currentThread]);
});

NSLog(@"last-: %@",[NSThread currentThread]);

}

答案:

2017-03-20 16:59:11.436 TestGcd[8245:264276] begain - <NSThread: 0x6000000639c0>{number = 1, name = main}
2017-03-20 16:59:11.438 TestGcd[8245:264276] last: <NSThread: 0x6000000639c0>{number = 1, name = main}
2017-03-20 16:59:11.438 TestGcd[8245:264329] beforeSync: <NSThread: 0x60000006c200>{number = 3, name = (null)}
(lldb) 

很明顯 sync-: %@afterSync-: %@沒有打印出來,這是為什么?我們來一步步分析一下:

分析:

  1. 使用DISPATCH_QUEUE_SERIAL這個參數,創建一個串行隊列

  2. 打印begain - %@這句

  3. dispatch_async異步執行,所以當前線程不會阻塞,于是有了2條線程,一條當前線程繼續往下打印出last-: %@這句,另一條執行Block中的內容打印beforeSync: %@這句,因為這兩條線程是并行的,所以打印的先后順序無所謂

  4. 注意,高潮來了…現在的情況和上個例子一樣,dispatch_sync同步執行,于是它所在的線程會被阻塞,一直等到sync里的任務執行完才會繼續往下。于是sync就高興的把自己Block中的任務放到serialQueue中,可誰想serialQueue是一個串行隊列,一次執行一個任務,所以sync的Block必須等到前一個任務執行完畢,可萬萬沒想到的是serialQueue正在執行的任務就是被sync阻塞了的那個,于是又發生了死鎖,所以sync所在的線程被卡死了,剩下的兩句代碼自然不會打印。

六.隊列組

隊列組可以將很多隊列添加到一個組里,這樣做的好處是,當這個組里所有的任務都執行完了,隊列組會通過一個方法通知我們。下面是使用方法,這是一個很實用的功能

//1.創建隊列組
dispatch_group_t group = dispatch_group_create();

//2.創建隊列
dispatch_queue_t queueGroup = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//3.多次使用隊列組的方法執行任務,只有異步方法
//3.1。執行3次循環
dispatch_group_async(group, queueGroup, ^{

    for (NSUInteger i = 0; i < 3; i++) {
        NSLog(@"group - 01 - %@",[NSThread currentThread]);
    }
});

//3.2。主隊列執行8次循環
dispatch_group_async(group, dispatch_get_main_queue(), ^{

    for (NSInteger i = 0; i < 8; i++) {
        NSLog(@"group - 02 - %@",[NSThread currentThread]);
    }
});

//3.3.執行5次循環
dispatch_group_async(group, queueGroup, ^{

    for (NSUInteger i = 0; i < 5; i++) {
        NSLog(@"group - 03 - %@", [ NSThread currentThread]);
    }
});

//4.都完成后會自動通知
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"完成 - %@",[NSThread currentThread]);
});

打印結果

2017-03-20 17:43:17.266 TestGcd[9020:294003] group - 01 - <NSThread: 0x608000074400>{number = 3, name = (null)}
2017-03-20 17:43:17.266 TestGcd[9020:294005] group - 03 - <NSThread: 0x60000006fe80>{number = 4, name = (null)}
2017-03-20 17:43:17.267 TestGcd[9020:294003] group - 01 - <NSThread: 0x608000074400>{number = 3, name = (null)}
2017-03-20 17:43:17.267 TestGcd[9020:294005] group - 03 - <NSThread: 0x60000006fe80>{number = 4, name = (null)}
2017-03-20 17:43:17.268 TestGcd[9020:294003] group - 01 - <NSThread: 0x608000074400>{number = 3, name = (null)}
2017-03-20 17:43:17.269 TestGcd[9020:294005] group - 03 - <NSThread: 0x60000006fe80>{number = 4, name = (null)}
2017-03-20 17:43:17.272 TestGcd[9020:294005] group - 03 - <NSThread: 0x60000006fe80>{number = 4, name = (null)}
2017-03-20 17:43:17.272 TestGcd[9020:294005] group - 03 - <NSThread: 0x60000006fe80>{number = 4, name = (null)}
2017-03-20 17:43:17.280 TestGcd[9020:293944] group - 02 - <NSThread: 0x608000067800>{number = 1, name = main}
2017-03-20 17:43:17.281 TestGcd[9020:293944] group - 02 - <NSThread: 0x608000067800>{number = 1, name = main}
2017-03-20 17:43:17.284 TestGcd[9020:293944] group - 02 - <NSThread: 0x608000067800>{number = 1, name = main}
2017-03-20 17:43:17.285 TestGcd[9020:293944] group - 02 - <NSThread: 0x608000067800>{number = 1, name = main}
2017-03-20 17:43:17.286 TestGcd[9020:293944] group - 02 - <NSThread: 0x608000067800>{number = 1, name = main}
2017-03-20 17:43:17.294 TestGcd[9020:293944] group - 02 - <NSThread: 0x608000067800>{number = 1, name = main}
2017-03-20 17:43:17.312 TestGcd[9020:293944] group - 02 - <NSThread: 0x608000067800>{number = 1, name = main}
2017-03-20 17:43:17.312 TestGcd[9020:293944] group - 02 - <NSThread: 0x608000067800>{number = 1, name = main}
2017-03-20 17:43:17.315 TestGcd[9020:293944] 完成 - <NSThread: 0x608000067800>{number = 1, name = main}

以上就是GCD的基本功能,但它的能力遠不止這寫些,之后我會更新它的其它用途

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

推薦閱讀更多精彩內容