一扶欣、多線程
1.1進(jìn)程與線程
進(jìn)程:進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序;每個(gè)進(jìn)程之間是獨(dú)立的千扶,每個(gè)進(jìn)程均運(yùn)行在其專用的受保護(hù)的內(nèi)存空間內(nèi)宵蛀。
線程:線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程的所有任務(wù)都在線程中執(zhí)行;進(jìn)程要想執(zhí)行任務(wù)县貌,必須要有線程术陶,進(jìn)程至少要有一條線程;程序啟動(dòng)回默認(rèn)開啟一條線程,即主線程煤痕。
多線程原理:同一時(shí)間單核CPU只能處理一個(gè)線程梧宫,即只有一個(gè)線程在執(zhí)行。多線程同時(shí)執(zhí)行是CPU快速的在多個(gè)線程之間切換摆碉,CPU調(diào)度線程的時(shí)間足夠快塘匣,就造成了多線程同時(shí)執(zhí)行的效果。如果線程非常多巷帝,CPU會(huì)在N個(gè)線程之間切換忌卤,消耗大量的CPU資源,每個(gè)線程被調(diào)度的次數(shù)會(huì)降低楞泼,線程的執(zhí)行效率也會(huì)降低驰徊。
多線程技術(shù)方案:
方案 | 說明 | 語言 | 生命周期 | 使用頻率 |
---|---|---|---|---|
pthread | 一套通用的多線程API;適用于Unix/Linux/Windows等平臺(tái);跨平臺(tái),可移植堕阔;適用難度大 | c | 開發(fā)管理 | 很低 |
NSThread | 使用更加面向?qū)ο蠊鞒В缓?jiǎn)單易用,可直接操作線程對(duì)象 | OC | 開發(fā)管理 | 低 |
GCD | 旨在替代NSThread技術(shù)超陆,充分利用設(shè)備的多核 | c | 自動(dòng)管理 | 高 |
NSOperation | 基于GCD(底層是GCD)牺弹;比GCD多了一些更簡(jiǎn)單實(shí)用的功能;實(shí)用更加面向?qū)ο?/td> | OC | 自動(dòng)管理 | 高 |
1.2 任務(wù)
任務(wù)是指執(zhí)行的操作,簡(jiǎn)單說就是在線程中執(zhí)行的那段代碼张漂。在GCD中是放在block中的晶默。
任務(wù)的執(zhí)行有兩種方式:同步執(zhí)行和異步執(zhí)行。兩者的區(qū)別主要在于是否等待隊(duì)列中的任務(wù)執(zhí)行完畢航攒,以及是否具備開啟新線程的能力荤胁。
- 同步執(zhí)行(sync):同步添加任務(wù)到指定的隊(duì)列中,在添加的任務(wù)執(zhí)行結(jié)束前會(huì)一直等待屎债,直到隊(duì)列里的任務(wù)完成后再繼續(xù)執(zhí)行;只能在當(dāng)前線程執(zhí)行任務(wù)垢油,不具備開啟新線程的能力盆驹。加入方式
dispatch_sync
。 - 異步執(zhí)行(async):異步添加任務(wù)到指定隊(duì)列滩愁,不會(huì)做任何等待躯喇,可以繼續(xù)執(zhí)行任務(wù);可以在新的線程執(zhí)行任務(wù)硝枉,具備開啟新線程的能力廉丽,但是并不一定開啟新線程,這跟任務(wù)所在的隊(duì)列有關(guān)妻味。加入方式
dispatch_async
正压。
任務(wù)執(zhí)行速度的影響因素:1.CPU。2.線程狀態(tài)责球。3.任務(wù)的復(fù)雜度焦履。4.任務(wù)的優(yōu)先級(jí)。其中任務(wù)的優(yōu)先級(jí)包括用戶指定的qualityService
(userInteractive
雏逾, userInitiated
嘉裤,utility
,background
栖博,default
)屑宠;等待的頻繁程度(不執(zhí)行)。
二仇让、GCD
隊(duì)列(Dispatch Queue)指的是執(zhí)行任務(wù)的等待隊(duì)列典奉,即用來存放任務(wù)的隊(duì)列。隊(duì)列采用FIFO(先進(jìn)先出)的原則丧叽,新任務(wù)總是被插入到隊(duì)列的末尾秋柄,讀取任務(wù)的時(shí)候總是從隊(duì)列的頭部開始讀取,每讀取一個(gè)任務(wù)蠢正,則從隊(duì)列中釋放一個(gè)任務(wù)骇笔。
GCD中有2種隊(duì)列:串行隊(duì)列和并發(fā)隊(duì)列谆焊,兩者均遵循FIFO的原則脐瑰,不同點(diǎn)在于執(zhí)行的順序以及開啟線程的數(shù)量。
- 串行隊(duì)列(Serial Dispatch Queue):只開啟一個(gè)線程,一個(gè)任務(wù)執(zhí)行完畢才會(huì)執(zhí)行下一個(gè)任務(wù)幻碱。每次只有一個(gè)任務(wù)被執(zhí)行。
- 并發(fā)隊(duì)列(Concurrent Dispatch Queue):可以開啟多個(gè)線程院尔,并且同時(shí)執(zhí)行任務(wù)御板。可以讓多個(gè)任務(wù)同時(shí)執(zhí)行虚吟。
隊(duì)列的創(chuàng)建:可以使用dispatch_queue_create
創(chuàng)建寸认,該方法需要傳入2個(gè)參數(shù):第一個(gè)參數(shù)表示隊(duì)列的標(biāo)識(shí),可為空串慰;第二個(gè)參數(shù)用來識(shí)別是串行隊(duì)列還是并發(fā)隊(duì)列偏塞,DISPATCH_QUEUE_SERIAL
(==NULL)標(biāo)識(shí)串行隊(duì)列,DISPATCH_QUEUE_CONCURRENT
//串行隊(duì)列
dispatch_queue_t s = dispatch_queue_create("com.appex.queue.serial", DISPATCH_QUEUE_SERIAL);
//并發(fā)隊(duì)列
dispatch_queue_t c = dispatch_queue_create("com.appex.queue.concurrent", DISPATCH_QUEUE_CONCURRENT);
主隊(duì)列:主隊(duì)列(Main Dispatch Queue)是一種特殊的串行隊(duì)列邦鲫,說它特殊是因?yàn)槟J(rèn)情況下代碼就在主隊(duì)列中灸叼,主隊(duì)列的代碼又都會(huì)放在主線程中執(zhí)行。獲取方式:
//獲取主隊(duì)列
dispatch_group_t main = dispatch_get_main_queue();
全局并發(fā)隊(duì)列:全局并發(fā)隊(duì)列是(Global Dispatch Queue)是系統(tǒng)提供的并發(fā)隊(duì)列庆捺,獲取方法:
//獲取全局并發(fā)隊(duì)列
dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//第一個(gè)參數(shù)表示優(yōu)先級(jí)的高低古今,一般傳`DISPATCH_QUEUE_PRIORITY_DEFAULT`
DISPATCH_QUEUE_PRIORITY_HIGH
DISPATCH_QUEUE_PRIORITY_DEFAULT
DISPATCH_QUEUE_PRIORITY_LOW
DISPATCH_QUEUE_PRIORITY_BACKGROUND
//第二個(gè)參數(shù)暫時(shí)沒用,傳0即可滔以。
如果當(dāng)前在主線程捉腥,按照隊(duì)列的串行和并發(fā),任務(wù)的同步和異步特性組合你画,我們歸納如下:
區(qū)別 | 并發(fā)隊(duì)列 | 串行隊(duì)列 | 主隊(duì)列 |
---|---|---|---|
同步 | 沒有開啟新線程,串行執(zhí)行任務(wù) | 沒有開啟新線程,串行執(zhí)行任務(wù) | 死鎖 |
異步 | 有開啟新線程,并發(fā)執(zhí)行任務(wù) | 有開啟新線程(1條),串行執(zhí)行任務(wù) | 沒有開啟新線程,串行執(zhí)行任務(wù) |
/*死鎖案例-1*/
dispatch_queue_t queue = dispatch_get_main_queue();
dispatch_async(queue, ^{
/*async沒有開啟新線程但狭,以下代碼在主線程中運(yùn)行*/
NSLog(@"開始:%@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"sync:%@", [NSThread currentThread]);
});
NSLog(@"結(jié)束:%@", [NSThread currentThread]);
});
在主線程中,向主隊(duì)列添加同步任務(wù)會(huì)死鎖撬即。這是因?yàn)樘砑拥娜蝿?wù)和主隊(duì)列自身的任務(wù)相互等待立磁,阻塞了主隊(duì)列,最終造成主隊(duì)列所載的線程(主線程)死鎖剥槐。如果在其他線程向主隊(duì)列添加同步任務(wù)唱歧,則不會(huì)死鎖。
/*死鎖案例-2*/
dispatch_queue_t queue = dispatch_queue_create("com.app.serial", 0);
dispatch_async(queue, ^{
/*async開啟1條新線程粒竖,以下代碼在子線程中運(yùn)行*/
NSLog(@"開始:%@", [NSThread currentThread]);
dispatch_sync(queue, ^{
NSLog(@"sync:%@", [NSThread currentThread]);
});
NSLog(@"結(jié)束:%@", [NSThread currentThread]);
});
在子線程中颅崩,向串行隊(duì)列添加同步任務(wù)會(huì)死鎖。這是因?yàn)樘砑拥娜蝿?wù)和串行隊(duì)列自身的任務(wù)相互等待蕊苗,阻塞了串行隊(duì)列沿后,最終造成串行隊(duì)列所在的線程(子線程)死鎖。如果在其他線程向該隊(duì)列添加同步任務(wù)朽砰,則不會(huì)死鎖尖滚。
以上案例可以概括為在一個(gè)串行隊(duì)列所在的線程喉刘,向該隊(duì)列添加同步任務(wù)會(huì)造成串行隊(duì)列追加的任務(wù)和原有的任務(wù)相互等待而阻塞當(dāng)前線程。
三漆弄、GCD其他常用函數(shù)
3.1 dispatch_after:表示在某個(gè)隊(duì)列中異步延遲執(zhí)行任務(wù)
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t work){
_dispatch_after(when, queue, NULL, work, true);
}
第一個(gè)參數(shù)when表示開始的時(shí)間睦裳,通常在現(xiàn)在的時(shí)間時(shí)間的基礎(chǔ)上加時(shí)間,如dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2)
表示2秒之后撼唾;第二個(gè)參數(shù)queue
傳入隊(duì)列廉邑,第三個(gè)參數(shù)work
傳入任務(wù)的block
代碼。
常規(guī)用法如下:
NSLog(@"開始:%@", [NSThread currentThread]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
NSLog(@"after:%@", [NSThread currentThread]);
});
3.2 dispatch_once:代碼只執(zhí)行一次
void dispatch_once(dispatch_once_t *val, dispatch_block_t block){
dispatch_once_f(val, block, _dispatch_Block_invoke(block));
}
第一個(gè)參數(shù)val
傳入一個(gè)dispatch_once_t
類型的指針地址倒谷,第二個(gè)參數(shù)block
傳入只執(zhí)行一次的block
任務(wù)蛛蒙。
常規(guī)用法:
@interface NXDownloader : NSObject
+ (NXDownloader *)downloader;
@end
@implementation NXDownloader
+ (NXDownloader *)downloader{
static dispatch_once_t t;
static NXDownloader *sharedInstance;
dispatch_once(&t, ^{
sharedInstance = [[NXDownloader alloc] init];
});
return sharedInstance;
}
@end
這樣我們通過NXDownloader *downloader = [NXDownloader downloader];
獲取到的實(shí)例都是同一個(gè),而且是線程安全的渤愁。
3.3dispatch_barrier_async/dispatch_barrier_sync:柵欄函數(shù)
柵欄函數(shù)的作用就是隔離柵欄函數(shù)之前與之后的代碼執(zhí)行牵祟,只有前面的代碼執(zhí)行完畢才會(huì)執(zhí)行后面的代碼,函數(shù)如下:
void dispatch_barrier_async(dispatch_queue_t dq, dispatch_block_t work){
...
}
void dispatch_barrier_sync(dispatch_queue_t dq, dispatch_block_t work){
...
}
柵欄函數(shù)的第一個(gè)參數(shù)dq
接收一個(gè)隊(duì)列猴伶,按照代碼注釋的說明,這里需要傳入一個(gè)并發(fā)隊(duì)列才會(huì)真正的發(fā)揮柵欄函數(shù)的功能塌西,如果傳入的dq是個(gè)串行隊(duì)列他挎,則函數(shù)的表現(xiàn)與dispatch_barrier_async
和dispatch_barrier_sync
表現(xiàn)一樣。
那么這里的async
和sync
的作用有什么不同呢捡需?看如下代碼:
dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"1:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"2:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"3:%@", [NSThread currentThread]);
});
NSLog(@"0-0:%@",[NSThread currentThread]);
//dispatch_barrier_async或dispatch_barrier_sync
dispatch_barrier_async(queue, ^{
sleep(2);
NSLog(@"barrier:%@", [NSThread currentThread]);
});
NSLog(@"0-1:%@",[NSThread currentThread]);
dispatch_async(queue, ^{
sleep(1);
NSLog(@"4:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"5:%@", [NSThread currentThread]);
});
dispatch_async(queue, ^{
sleep(1);
NSLog(@"6:%@", [NSThread currentThread]);
});
多次打印办桨,結(jié)果如下:
執(zhí)行結(jié)果顯示:{1,2,3}
執(zhí)行的順序是不確定,{4,5,6}
執(zhí)行順序也是不確定的站辉,可以確定的是{1,2,3}
執(zhí)行完畢后執(zhí)行barrier
呢撞,再執(zhí)行{4,5,6}
。在async的情況下0-0
饰剥,0-1
最先執(zhí)行殊霞。而sync
的情況下0-0在barrier
之前執(zhí)行,0-1
在barrier
之后執(zhí)行汰蓉。也就是sync
會(huì)如同dispatch_sync
一樣執(zhí)行完畢后再執(zhí)行后面的代碼绷蹲。并且在sync
的情況下barrier
任務(wù)會(huì)在原有的線程(這里是主線程)中執(zhí)行。
還需要注意一點(diǎn)顾孽,柵欄函數(shù)的只在自定義的并發(fā)隊(duì)列才會(huì)生效祝钢,這一點(diǎn)也好理解,因?yàn)椴l(fā)隊(duì)列若厚,系統(tǒng)也會(huì)向里邊添加任務(wù)拦英,我們?cè)O(shè)置一個(gè)柵欄那么后續(xù)加入的系統(tǒng)任務(wù)豈不是要等待柵欄執(zhí)行完畢?這顯然不合理测秸。
3.4dispatch_group疤估,隊(duì)列組
隊(duì)列組簡(jiǎn)言之就是一組任務(wù)執(zhí)行完畢后會(huì)有一個(gè)單獨(dú)的回調(diào)灾常。
//隊(duì)列組的創(chuàng)建
dispatch_group_t group = dispatch_group_create();
//添加任務(wù)
dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_async(group, queue, ^{
NSLog(@"執(zhí)行");
});
//任務(wù)執(zhí)行完畢的回掉
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"結(jié)束:%@");
});
其中dispatch_group_async
會(huì)把任務(wù)放入隊(duì)列,再把隊(duì)列放入隊(duì)列組。也可以用dispatch_group_enter
和dispatch_group_leave
成對(duì)使用做裙。 dispatch_group_notify
會(huì)在任務(wù)執(zhí)行完畢后回調(diào)岗憋,你可以指定一個(gè)隊(duì)列繼續(xù)做其他事情。還有一個(gè)不太常用的dispatch_group_wait
函數(shù)锚贱,這個(gè)函數(shù)第一個(gè)參數(shù)傳入一個(gè)group仔戈,第二個(gè)參數(shù)傳入一個(gè)time
,這個(gè)時(shí)間指定的是dispatch_group_wait
之后的代碼等待的最大時(shí)間,假定這里設(shè)定的時(shí)間是3秒拧廊,如果前面的任務(wù)2秒執(zhí)行完畢监徘,那么wait后面的代碼會(huì)在2秒后執(zhí)行。如果前面的任務(wù)4秒執(zhí)行完畢吧碾,那么wait后面的代碼會(huì)在第3秒的時(shí)候開始執(zhí)行凰盔。
dispatch_group_notify案例
//dispatch_group_notify案例:
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);
for (int i= 0; i < 10; i++){
dispatch_group_async(group, queue, ^{
NSLog(@"%d:%@", i, [NSThread currentThread]);
});
/*以上三行代碼等價(jià)于
dispatch_group_enter(group);
dispatch_async(queue, ^{
NSLog(@"%d:%@", i, [NSThread currentThread]);
dispatch_group_leave(group);
});
*/
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"結(jié)束:%@", [NSThread currentThread]);
});
dispatch_group_wait案例
//dispatch_group_wait案例
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_queue_create("com.app.concurrent", DISPATCH_QUEUE_CONCURRENT);
for (int i= 0; i < 10; i++){
dispatch_group_async(group, queue, ^{
sleep(2);
NSLog(@"%d:%@", i, [NSThread currentThread]);
});
}
//dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC*2)
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
NSLog(@"結(jié)束:%@", [NSThread currentThread]);
這個(gè)場(chǎng)景在實(shí)際開發(fā)中使用較多,比如現(xiàn)在有一組網(wǎng)絡(luò)圖片需要分享到某個(gè)平臺(tái)倦春,我們需要等待多張網(wǎng)絡(luò)圖片全部下載完畢后才能開始分享户敬。