上篇文章介紹了多線程是什么、線程的進程的區別,在這篇文章中,主要介紹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的執行,一個接一個的,先進先出的執行
放到串行隊列的任務,
GCD
會FIFO
(先進先出)地取出來一個,執行一個,然后取下一個,這樣一個一個的執行。
放到并行隊列的任務,
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-: %@
沒有打印出來,這是為什么?我們來一步步分析一下:
分析:
使用
DISPATCH_QUEUE_SERIAL
這個參數,創建一個串行隊列打印begain - %@這句
dispatch_async
異步執行,所以當前線程不會阻塞,于是有了2條線程,一條當前線程繼續往下打印出last-: %@
這句,另一條執行Block中的內容打印beforeSync: %@
這句,因為這兩條線程是并行的,所以打印的先后順序無所謂注意,高潮來了…現在的情況和上個例子一樣,
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
的基本功能,但它的能力遠不止這寫些,之后我會更新它的其它用途