目前多線程主要分為三類:
1 NSThread
2 NSOperation
3 GCD
前面兩種網(wǎng)上資料很多,本文就蘋果推薦使用的GCD理一理。
多線程相關(guān)概念
進(jìn)程與線程的關(guān)系?
進(jìn)程:app打開的時候會打開相應(yīng)的進(jìn)程,一般情況下,一個App會有一個或多個進(jìn)程,但是iOS的App一般只有一個進(jìn)程(后臺播放,IM除外)。在iOS系統(tǒng)中,打開一個新的App會掛起其他App的進(jìn)程。一個進(jìn)程會包含一個或多個線程。
線程:一個線程同時只可以執(zhí)行一個任務(wù),多線程就可以同時執(zhí)行多個任務(wù)。一個進(jìn)程可以包含多個線程。
主線程和子線程(非主線程)到底有什么區(qū)別?
- 主線程:iOS程序中,主線程(又叫作UI線程)主要任務(wù)是處理UI事件,顯示和刷新UI,(只有主線程有直接修改UI的能力)耗時的操作放在子線程(又叫作后臺線程、異步線程)。
- 子線程:在iOS中開子線程去處理耗時的操作,可以有效提高程序的執(zhí)行效率,提高資源利用率。
但是開啟線程會占用一定的內(nèi)存,(主線程的堆棧大小是1M,第二個線程開始都是512KB,并且該值不能通過編譯器開關(guān)或線程API函數(shù)來更改)降低程序的性能。所以一般不要同時開很多線程。
注:查看當(dāng)前線程信息用[NSThread currentThread],而不要用[NSOperationQueue currentQueue],因前者內(nèi)容可以看到更全面的線程信息!
下面進(jìn)入正題:
GCD
關(guān)鍵字:
(1). 異步與同步
異步執(zhí)行:具備開新線程的能力,可以先繞過線程任務(wù),回頭再執(zhí)行。
同步執(zhí)行:不具備開新線程的能力,因此只能在當(dāng)前線程中執(zhí)行!任務(wù)執(zhí)行必須按順序執(zhí)行(必須執(zhí)行完當(dāng)前任務(wù)才會繼續(xù)走代碼)。
(2). 并發(fā)隊列與串行隊列
并發(fā)隊列:隊列中的任務(wù)同時執(zhí)行(Concurrent Dispatch Queue)
串行隊列:隊列中的任務(wù)按添加任務(wù)順序執(zhí)行(Serial Dispatch Queue)
注:這里說的任務(wù)就是GCD中block中的代碼。
這里的關(guān)鍵字一般都是組合用,理解單個是沒有意義的,組合后的效果:
以下兩種同步執(zhí)行的情況都是在主線程執(zhí)行的:
dispatch_queue_t concurrentQueue = dispatch_queue_create("abcd", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊列
//這里的abcd僅僅作為調(diào)試時用的標(biāo)識符,打印線程屬性時并不是name
dispatch_sync(concurrentQueue, ^{
[self loadImage:@10];
});
dispatch_sync(concurrentQueue, ^{
[self loadImage:@11];
});
dispatch_sync(concurrentQueue, ^{
[self loadImage:@12];
});
dispatch_sync(concurrentQueue, ^{
[self loadImage:@13];
});
打印log:
2017-06-28 10:38:54.797 多線程GCD[80605:13173361] 執(zhí)行:10 線程信息:<NSThread: 0x600000073ec0>{number = 1, name = main}
2017-06-28 10:38:57.342 多線程GCD[80605:13173361] 執(zhí)行:11 線程信息:<NSThread: 0x600000073ec0>{number = 1, name = main}
2017-06-28 10:38:59.681 多線程GCD[80605:13173361] 執(zhí)行:12 線程信息:<NSThread: 0x600000073ec0>{number = 1, name = main}
2017-06-28 10:39:01.966 多線程GCD[80605:13173361] 執(zhí)行:13 線程信息:<NSThread: 0x600000073ec0>{number = 1, name = main}
dispatch_queue_t serialQueue = dispatch_queue_create("aa", DISPATCH_QUEUE_SERIAL);//串行隊列
//這里的DISPATCH_QUEUE_SERIAL等價于NULL
dispatch_sync(serialQueue, ^{
[self loadImage:@5];
});
dispatch_sync(serialQueue, ^{
[self loadImage:@6];
});
dispatch_sync(serialQueue, ^{
[self loadImage:@7];
});
dispatch_sync(serialQueue, ^{
[self loadImage:@8];
});
打印log:
2017-06-28 10:45:30.928 多線程GCD[81007:13250389] 執(zhí)行:5 線程信息:<NSThread: 0x600000065000>{number = 1, name = main}
2017-06-28 10:45:37.232 多線程GCD[81007:13250389] 執(zhí)行:6 線程信息:<NSThread: 0x600000065000>{number = 1, name = main}
2017-06-28 10:45:39.713 多線程GCD[81007:13250389] 執(zhí)行:7 線程信息:<NSThread: 0x600000065000>{number = 1, name = main}
2017-06-28 10:45:42.314 多線程GCD[81007:13250389] 執(zhí)行:8 線程信息:<NSThread: 0x600000065000>{number = 1, name = main}
正確的打開方式:
dispatch_queue_t serialQueue = dispatch_queue_create("aa", DISPATCH_QUEUE_SERIAL);//串行隊列
dispatch_async(dispatch_queue_create("abcd", DISPATCH_QUEUE_SERIAL), ^{
dispatch_sync(serialQueue, ^{
[self loadImage:@5];
});
dispatch_sync(serialQueue, ^{
[self loadImage:@6];
});
dispatch_sync(serialQueue, ^{
[self loadImage:@7];
});
dispatch_sync(serialQueue, ^{
[self loadImage:@8];
});
});
打印log:
2017-06-28 10:47:15.677 多線程GCD[81128:13270066] 執(zhí)行:5 線程信息:<NSThread: 0x60800007e000>{number = 3, name = (null)}
2017-06-28 10:47:18.103 多線程GCD[81128:13270066] 執(zhí)行:6 線程信息:<NSThread: 0x60800007e000>{number = 3, name = (null)}
2017-06-28 10:47:20.470 多線程GCD[81128:13270066] 執(zhí)行:7 線程信息:<NSThread: 0x60800007e000>{number = 3, name = (null)}
2017-06-28 10:47:22.774 多線程GCD[81128:13270066] 執(zhí)行:8 線程信息:<NSThread: 0x60800007e000>{number = 3, name = (null)}
以上說的是同步的時候需要注意的,下面說一下異步的情況:
dispatch_queue_t serialQueue = dispatch_queue_create("aa", DISPATCH_QUEUE_SERIAL);//串行隊列
dispatch_async(serialQueue, ^{
[self loadImage:@0];
});
dispatch_async(serialQueue, ^{
[self loadImage:@1];
});
dispatch_async(serialQueue, ^{
[self loadImage:@2];
});
dispatch_async(serialQueue, ^{
[self loadImage:@3];
});
打印log:
2017-06-28 10:49:14.498 多線程GCD[81251:13288476] 執(zhí)行:0 線程信息:<NSThread: 0x600000262680>{number = 3, name = (null)}
2017-06-28 10:49:17.125 多線程GCD[81251:13288476] 執(zhí)行:1 線程信息:<NSThread: 0x600000262680>{number = 3, name = (null)}
2017-06-28 10:49:19.599 多線程GCD[81251:13288476] 執(zhí)行:2 線程信息:<NSThread: 0x600000262680>{number = 3, name = (null)}
2017-06-28 10:49:21.934 多線程GCD[81251:13288476] 執(zhí)行:3 線程信息:<NSThread: 0x600000262680>{number = 3, name = (null)}
dispatch_queue_t concurrentQueue = dispatch_queue_create("abcd", DISPATCH_QUEUE_CONCURRENT);//并發(fā)隊列
dispatch_async(concurrentQueue, ^{
[self loadImage:@0];
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@1];
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@2];
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@3];
});
打印log:
2017-06-28 10:54:06.805 多線程GCD[81549:13342819] 執(zhí)行:1 線程信息:<NSThread: 0x608000072f80>{number = 4, name = (null)}
2017-06-28 10:54:06.805 多線程GCD[81549:13341379] 執(zhí)行:0 線程信息:<NSThread: 0x60800007b200>{number = 3, name = (null)}
2017-06-28 10:54:06.805 多線程GCD[81549:13342821] 執(zhí)行:3 線程信息:<NSThread: 0x6080000743c0>{number = 6, name = (null)}
2017-06-28 10:54:06.805 多線程GCD[81549:13342820] 執(zhí)行:2 線程信息:<NSThread: 0x60000026a400>{number = 5, name = (null)}
在串行隊列異步執(zhí)行過程中,會創(chuàng)建一個線程,按順序執(zhí)行當(dāng)前線程中的任務(wù)。有阻塞!
在并發(fā)隊列異步執(zhí)行過程中,可能會創(chuàng)建多個線程,同時執(zhí)行當(dāng)前線程中的任務(wù)。沒有阻塞!
dispatch_async(dispatch_queue_create("aaa", DISPATCH_QUEUE_SERIAL), ^{
[self loadImage:@0];
});
dispatch_async(dispatch_queue_create("bbb", DISPATCH_QUEUE_SERIAL), ^{
[self loadImage:@1];
});
dispatch_async(dispatch_queue_create("ccc", DISPATCH_QUEUE_SERIAL), ^{
[self loadImage:@2];
});
dispatch_async(dispatch_queue_create("ddd", DISPATCH_QUEUE_SERIAL), ^{
[self loadImage:@3];
});
dispatch_async(dispatch_queue_create("eee", DISPATCH_QUEUE_SERIAL), ^{
[self loadImage:@4];
});
打印log:
2017-06-28 11:05:10.966 多線程GCD[82282:13469275] 執(zhí)行:2 線程信息:<NSThread: 0x608000076100>{number = 5, name = (null)}
2017-06-28 11:05:10.966 多線程GCD[82282:13469905] 執(zhí)行:4 線程信息:<NSThread: 0x6080000738c0>{number = 6, name = (null)}
2017-06-28 11:05:10.966 多線程GCD[82282:13469278] 執(zhí)行:3 線程信息:<NSThread: 0x608000075b40>{number = 7, name = (null)}
2017-06-28 11:05:10.966 多線程GCD[82282:13469331] 執(zhí)行:1 線程信息:<NSThread: 0x600000076d00>{number = 4, name = (null)}
2017-06-28 11:05:10.966 多線程GCD[82282:13469276] 執(zhí)行:0 線程信息:<NSThread: 0x60800006dcc0>{number = 3, name = (null)}
以上會創(chuàng)建5個串行隊列線程,而不是1個串行隊列線程,因為不是同一個線程,所以執(zhí)行順序是同時執(zhí)行,互相沒有影響(對于并發(fā)隊列是同樣的)。
正確使用方法應(yīng)該是1中創(chuàng)建的方式,對于并發(fā)隊列,計算機(jī)會根據(jù)性能和需要創(chuàng)建合理的線程數(shù)量。保證系統(tǒng)高性能和流暢性。
dispatch_async(dispatch_queue_create("a", DISPATCH_QUEUE_CONCURRENT), ^{
[self loadImage:@14];
});
dispatch_async(dispatch_queue_create("b", DISPATCH_QUEUE_CONCURRENT), ^{
[self loadImage:@15];
});
dispatch_async(dispatch_queue_create("c", DISPATCH_QUEUE_CONCURRENT), ^{
[self loadImage:@16];
});
dispatch_async(dispatch_queue_create("d", DISPATCH_QUEUE_CONCURRENT), ^{
[self loadImage:@17];
});
打印log:
2017-06-28 11:03:51.193 多線程GCD[82177:13453338] 執(zhí)行:15 線程信息:<NSThread: 0x608000263040>{number = 4, name = (null)}
2017-06-28 11:03:51.193 多線程GCD[82177:13449499] 執(zhí)行:14 線程信息:<NSThread: 0x608000261580>{number = 3, name = (null)}
2017-06-28 11:03:51.193 多線程GCD[82177:13453339] 執(zhí)行:16 線程信息:<NSThread: 0x608000272f00>{number = 5, name = (null)}
2017-06-28 11:03:51.193 多線程GCD[82177:13453340] 執(zhí)行:17 線程信息:<NSThread: 0x60000026e740>{number = 6, name = (null)}
Main Dispatch Queue/Global Dispatch Queue
實(shí)際上不用特意生成Dispatch Queue,系統(tǒng)也會為我們準(zhǔn)備幾個。那就是Main Dispatch Queue和Global Dispatch Queue。
Main Dispatch Queue就是主線程,因為主線程只有一個,所以Main Dispatch Queue是Serial Dispatch Queue。Main Dispatch Queue的處理在主線程的Runloop中執(zhí)行。
相應(yīng)的,Global Dispatch Queue是系統(tǒng)為我們準(zhǔn)備的Concurrent Dispatch Queue。Global Dispatch Queue有四個優(yōu)先級,應(yīng)根據(jù)需求使用對應(yīng)優(yōu)先級。
/**
high優(yōu)先級的獲取方法
*/
dispatch_queue_t globalHighQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
/**
default優(yōu)先級的獲取方法
*/
dispatch_queue_t globalDefaultQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
/**
low優(yōu)先級的獲取方法
*/
dispatch_queue_t globalLowQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
/**
background優(yōu)先級的獲取方法
*/
dispatch_queue_t globalBackgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
更改隊列優(yōu)先級:dispatch_set_target_queue(dispatch_object_t _Nonnull object, dispatch_queue_t _Nullable queue)
dispatch_queue_t serialQueue = dispatch_queue_create("aa", DISPATCH_QUEUE_SERIAL);//串行隊列
dispatch_queue_t globalBackgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
dispatch_set_target_queue(serialQueue, globalBackgroundQueue);將serialQueue指定為globalBackgroundQueue用。
注:如果這里用函數(shù)dispatch_set_target_queue將多個Serial Dispatch Queue指定為同一個Serial Dispatch Queue,就會將原本并發(fā)的隊列變更為串行隊列。
關(guān)鍵字 Dispatch Group
/**
將原本在serialQueue中要執(zhí)行的放在group里,后面可以加一個結(jié)束的回調(diào)(很多時候會有這種需求)
*/
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, serialQueue, ^{
//代碼
});
dispatch_group_async(group, serialQueue, ^{
//代碼
});
dispatch_group_async(group, serialQueue, ^{
//代碼
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"done");//執(zhí)行完成后回調(diào)
});
關(guān)鍵字 dispatch_barrier_async 柵欄
dispatch_queue_t concurrentQueue = dispatch_queue_create("abcd", DISPATCH_QUEUE_CONCURRENT);//并行隊列
dispatch_async(concurrentQueue, ^{
[self loadImage:@0];
//讀取數(shù)據(jù)0
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@1];
//讀取數(shù)據(jù)1
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@2];
//讀取數(shù)據(jù)2
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@3];
//讀取數(shù)據(jù)3
});
dispatch_barrier_async(concurrentQueue, ^{
[self loadImage:@4];
//異步柵欄寫入數(shù)據(jù)4
});
dispatch_barrier_sync(concurrentQueue, ^{
[self loadImage:@4];
//同步柵欄寫入數(shù)據(jù)4
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@5];
//讀取數(shù)據(jù)5
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@6];
//讀取數(shù)據(jù)6
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@7];
//讀取數(shù)據(jù)7
});
dispatch_async(concurrentQueue, ^{
[self loadImage:@8];
//讀取數(shù)據(jù)8
});
打印log:
2017-06-28 11:11:53.029 多線程GCD[82699:13543714] 執(zhí)行:2 線程信息:<NSThread: 0x600000076d80>{number = 5, name = (null)}
2017-06-28 11:11:53.029 多線程GCD[82699:13543650] 執(zhí)行:0 線程信息:<NSThread: 0x600000076d40>{number = 3, name = (null)}
2017-06-28 11:11:53.029 多線程GCD[82699:13543652] 執(zhí)行:1 線程信息:<NSThread: 0x608000066940>{number = 4, name = (null)}
2017-06-28 11:11:53.029 多線程GCD[82699:13543715] 執(zhí)行:3 線程信息:<NSThread: 0x608000071140>{number = 6, name = (null)}
2017-06-28 11:11:58.763 多線程GCD[82699:13544668] 異步柵欄執(zhí)行:4 線程信息:<NSThread: 0x6080000710c0>{number = 8, name = (null)}
2017-06-28 11:12:01.195 多線程GCD[82699:13542941] 同步柵欄執(zhí)行:4 線程信息:<NSThread: 0x600000066500>{number = 1, name = main}
2017-06-28 11:12:03.605 多線程GCD[82699:13543650] 執(zhí)行:7 線程信息:<NSThread: 0x600000076d40>{number = 3, name = (null)}
2017-06-28 11:12:03.605 多線程GCD[82699:13544677] 執(zhí)行:5 線程信息:<NSThread: 0x608000074bc0>{number = 9, name = (null)}
2017-06-28 11:12:03.605 多線程GCD[82699:13544668] 執(zhí)行:6 線程信息:<NSThread: 0x6080000710c0>{number = 8, name = (null)}
2017-06-28 11:12:03.605 多線程GCD[82699:13543714] 執(zhí)行:8 線程信息:<NSThread: 0x600000076d80>{number = 5, name = (null)}
在讀取數(shù)據(jù)時,有時候會出現(xiàn)數(shù)據(jù)競爭,數(shù)據(jù)安全問題,比如:當(dāng)前的寫入沒有結(jié)束,讀取操作就開始處理,導(dǎo)致數(shù)據(jù)錯亂。或者讀取正在進(jìn)行,開始寫入操作,導(dǎo)致程序崩潰等問題。
蘋果為我們提供了一個便捷的參數(shù):dispatch_barrier_async(柵欄)。
作用:如上述代碼中所表示,只有在1234操作完成時(無序),才會執(zhí)行寫入操作,只有當(dāng)前寫入操作結(jié)束后,才會繼續(xù)向下執(zhí)行。dispatch_barrier_sync也有同樣的效果,但是如果不放入其他線程中的話,會在當(dāng)前線程即主線程執(zhí)行,會阻塞線程。
注意:dispatch_barrier_async中如果使用全局隊列的話,就相當(dāng)于要等待全局隊列里的所有并發(fā)任務(wù)執(zhí)行完后,才會執(zhí)行dispatch_barrier_async中的任務(wù),這樣會受其他在全局隊列里任務(wù)的影響,達(dá)不到我們想要的效果。
總結(jié):
**
并發(fā)隊列(Concurrent Dispatch Queue) 串行隊列(Serial Dispatch Queue) :
負(fù)責(zé)執(zhí)行順序!
異步執(zhí)行 (dispatch_async) 同步執(zhí)行 (dispatch_sync):
負(fù)責(zé)選擇線程執(zhí)行!
**