一摹芙、進(jìn)程、線程
一应狱、 進(jìn)程:
多線程
- 1.進(jìn)程是一個具有一定獨立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運行活動绑嘹,它是操作系統(tǒng)分配資 源的基本單元.
- 2.進(jìn)程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,就是一段程序的執(zhí)行過程,我們可以理解為手機(jī)上 的一個 app.
- 3.每個進(jìn)程之間是獨立的年碘,每個進(jìn)程均運行在其專用且受保護(hù)的內(nèi)存空間內(nèi)澈歉,擁有獨立運行所需 的全部資源
二、 線程
- 1.程序執(zhí)行流的最小單元屿衅,線程是進(jìn)程中的一個實體.
- 2.一個進(jìn)程要想執(zhí)行任務(wù),必須至少有一條線程.應(yīng)用程序啟動的時候埃难,系統(tǒng)會默認(rèn)開啟一條線程,
也就是主線程
三、 進(jìn)程和線程的關(guān)系
- 1.線程是進(jìn)程的執(zhí)行單元,進(jìn)程的所有任務(wù)都在線程中執(zhí)行
- 2.線程是 CPU 分配資源和調(diào)度的最小單位
- 3.一個程序可以對應(yīng)多個進(jìn)程(多進(jìn)程),一個進(jìn)程中可有多個線程,但至少要有一條線程
- 4.同一個進(jìn)程內(nèi)的線程共享進(jìn)程資源
二涡尘、多進(jìn)程忍弛、多線程 多進(jìn)程
打開 mac 的活動監(jiān)視器,可以看到很多個進(jìn)程同時運行
- 進(jìn)程是程序在計算機(jī)上的一次執(zhí)行活動考抄。當(dāng)你運行一個程序细疚,你就啟動了一個進(jìn)程。顯然川梅,程序 是死的(靜態(tài)的)疯兼,進(jìn)程是活的(動態(tài)的)。
- 進(jìn)程可以分為系統(tǒng)進(jìn)程和用戶進(jìn)程贫途。凡是用于完成操作系統(tǒng)的各種功能的進(jìn)程就是系統(tǒng)進(jìn)程吧彪,它 們就是處于運行狀態(tài)下的操作系統(tǒng)本身;所有由用戶啟動的進(jìn)程都是用戶進(jìn)程。進(jìn)程是操作系統(tǒng)進(jìn) 行資源分配的單位丢早。
- 進(jìn)程又被細(xì)化為線程姨裸,也就是一個進(jìn)程下有多個能獨立運行的更小的單位。在同一個時間里怨酝,同 一個計算機(jī)系統(tǒng)中如果允許兩個或兩個以上的進(jìn)程處于運行狀態(tài)傀缩,這便是多進(jìn)程。
多線程
同一時間农猬,CPU 只能處理 1 條線程扑毡,只有 1 條線程在執(zhí)行。多線程并發(fā)執(zhí)行盛险,其實是 CPU 快速地在多 條線程之間調(diào)度(切換)。如果 CPU 調(diào)度線程的時間足夠快勋又,就造成了多線程并發(fā)執(zhí)行的假象
如果線程非常非常多苦掘,CPU 會在 N 多線程之間調(diào)度,消耗大量的 CPU 資源楔壤,每條線程被調(diào)度執(zhí)行的頻 次會降低(線程的執(zhí)行效率降低)
多線程的優(yōu)點: 能適當(dāng)提高程序的執(zhí)行效率 能適當(dāng)提高資源利用率(CPU鹤啡、內(nèi)存利用率)
多線程的缺點:
開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用 1M蹲嚣,子線程占用 512KB)递瑰,如果開啟大量的 線程,會占用大量的內(nèi)存空間隙畜,降低程序的性能
線程越多抖部,CPU 在調(diào)度線程上的開銷就越大 程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享
三议惰、任務(wù)慎颗、隊列
任務(wù)
就是執(zhí)行操作的意思,也就是在線程中執(zhí)行的那段代碼。在 GCD 中是放在 block 中的俯萎。執(zhí)行任務(wù)有兩種 方式:同步執(zhí)行(sync)和異步執(zhí)行(async)
同步(Sync):同步添加任務(wù)到指定的隊列中傲宜,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待夫啊,直到隊列里面的任 務(wù)完成之后再繼續(xù)執(zhí)行函卒,即會阻塞線程。只能在當(dāng)前線程中執(zhí)行任務(wù)(是當(dāng)前線程撇眯,不一定是主線程)报嵌,不具 備開啟新線程的能力。
異步(Async):線程會立即返回叛本,無需等待就會繼續(xù)執(zhí)行下面的任務(wù)沪蓬,不阻塞當(dāng)前線程±春颍可以在新的線程中 執(zhí)行任務(wù)跷叉,具備開啟新線程的能力(并不一定開啟新線程)。如果不是添加到主隊列上营搅,異步會在子線程中執(zhí) 行任務(wù)
隊列
隊列(Dispatch Queue):這里的隊列指執(zhí)行任務(wù)的等待隊列云挟,即用來存放任務(wù)的隊列。隊列是一種特殊的 線性表转质,采用 FIFO(先進(jìn)先出)的原則园欣,即新任務(wù)總是被插入到隊列的末尾,而讀取任務(wù)的時候總是從隊 列的頭部開始讀取休蟹。每讀取一個任務(wù)沸枯,則從隊列中釋放一個任務(wù)
在 GCD 中有兩種隊列:串行隊列和并發(fā)隊列。兩者都符合 FIFO(先進(jìn)先出)的原則赂弓。兩者的主要區(qū)別是: 執(zhí)行順序不同绑榴,以及開啟線程數(shù)不同。
串行隊列(SerialDispatchQueue): 同一時間內(nèi)盈魁,隊列中只能執(zhí)行一個任務(wù)翔怎,只有當(dāng)前的任務(wù)執(zhí)行完成之后,才能執(zhí)行下一個任務(wù)杨耙。(只 開啟一個線程赤套,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))珊膜。主隊列是主線程上的一個串行隊列,是系 統(tǒng)自動為我們創(chuàng)建的
并發(fā)隊列(ConcurrentDispatchQueue): 同時允許多個任務(wù)并發(fā)執(zhí)行容握。(可以開啟多個線程,并且同時執(zhí)行任務(wù))车柠。并發(fā)隊列的并發(fā)功能只有 在異步(dispatch_async)函數(shù)下才有效
四唯沮、iOS 中的多線程
主要有三種:NSThread脖旱、NSoperationQueue、GCD
- 1. NSThread:輕量級別的多線程技術(shù)
是我們自己手動開辟的子線程介蛉,如果使用的是初始化方式就需要我們自己啟動萌庆,如果使用的是構(gòu)造器方式 它就會自動啟動。只要是我們手動開辟的線程币旧,都需要我們自己管理該線程践险,不只是啟動,還有該線程使 用完畢后的資源回收
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(testTread:) object:@"我是參數(shù)"];
// 當(dāng)使用初始化方法出來的主線程需要start啟動
[thread start];
// 可以為開辟的子線程起名字
thread.name = @"我是名字";
// 調(diào)整thread的權(quán)限吹菱,線程權(quán)限的范圍值為0~1巍虫,越大權(quán)限越高,先執(zhí)行的概率越高鳍刷,由于是概率占遥,并不能很準(zhǔn)確的實現(xiàn)我們先要的執(zhí)行順序
thread.threadPriority = 1;
// 取消當(dāng)前已經(jīng)啟動的線程
[thread cancel];
// 通過遍歷構(gòu)造器開辟子線程
[NSThread detachNewThreadSelector:@selector(testTread:) toTarget:self withObject:@"構(gòu)造器方式"];
performSelector...只要是 NSObject 的子類或者對象都可以通過調(diào)用方法進(jìn)入子線程和主線程,其實這些方法 所開辟的子線程也是 NSThread 的另一種體現(xiàn)方式输瓜。 在編譯階段并不會去檢查方法是否有效存在瓦胎,如果不存在只會給出警告
// 在當(dāng)前線程,延遲1秒執(zhí)行尤揣,響應(yīng)了OC語言的動態(tài)性:延遲到運行時才綁定方法
[self performSelector:@selector(testTread:) withObject:nil afterDelay:1];
// 回到主線程搔啊,waitUntilDone:是否將回調(diào)方法執(zhí)行完再執(zhí)行后面的代碼,如果為YES北戏,就必須等回調(diào)方法執(zhí)行完才能執(zhí)行后面的代碼
[self performSelectorOnMainThread:@selector(testTread:) withObject:nil waitUntilDone:YES];
// 開辟子線程
[self performSelectorInBackground:@selector(testTread:) withObject:nil];
// 在指定線程執(zhí)行
[self performSelector:@selector(testTread:) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];
需要注意的是:如果是帶 afterDelay 的延時函數(shù)负芋,會在內(nèi)部創(chuàng)建一個 NSTimer,然后添加到當(dāng)前線程的 Runloop 中嗜愈。也就是如果當(dāng)前線程沒有開啟 runloop旧蛾,該方法會失效。在子線程中蠕嫁,需要啟動 runloop(注意調(diào) 用順序)
[self performSelector:@selector(testTread:) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop]run];
而 performSelector:withObject:只是一個單純的消息發(fā)送锨天,和時間沒有一點關(guān)系。所以不需要添加到子線程的 Runloop 中也能執(zhí)行
- 2拌阴、GCD 對比 NSOprationQueue
我們要明確 NSOperationQueue 與 GCD 之間的關(guān)系
GCD 是面向底層的 C 語言的 API,NSOpertaionQueue 用 GCD 構(gòu)建封裝的奶镶,是 GCD 的高級抽象迟赃。
1、GCD 執(zhí)行效率更高厂镇,而且由于隊列中執(zhí)行的是由 block 構(gòu)成的任務(wù)纤壁,這是一個輕量級的數(shù)據(jù)結(jié)構(gòu),寫起 來更方便
2捺信、GCD 只支持 FIFO 的隊列酌媒,而 NSOperationQueue 可以通過設(shè)置最大并發(fā)數(shù)欠痴,設(shè)置優(yōu)先級,添加依賴關(guān)系 等調(diào)整執(zhí)行順序
3秒咨、NSOperationQueue 甚至可以跨隊列設(shè)置依賴關(guān)系喇辽,但是 GCD 只能通過設(shè)置串行隊列,或者在隊列內(nèi)添 加 barrier(dispatch_barrier_async)任務(wù)雨席,才能控制執(zhí)行順序,較為復(fù)雜
4菩咨、NSOperationQueue 因為面向?qū)ο螅灾С?KVO陡厘,可以監(jiān)測 operation 是否正在執(zhí)行(isExecuted)抽米、是 否結(jié)束(isFinished)、是否取消(isCanceld)
- 實際項目開發(fā)中糙置,很多時候只是會用到異步操作云茸,不會有特別復(fù)雜的線程關(guān)系管理,所以蘋果推崇的 且優(yōu)化完善谤饭、運行快速的 GCD 是首選
- 如果考慮異步操作之間的事務(wù)性标捺,順序行,依賴關(guān)系网持,比如多線程并發(fā)下載宜岛,GCD需要自己寫更多 的代碼來實現(xiàn),而 NSOperationQueue 已經(jīng)內(nèi)建了這些支持
- 不論是GCD還是NSOperationQueue功舀,我們接觸的都是任務(wù)和隊列萍倡,都沒有直接接觸到線程,事實 上線程管理也的確不需要我們操心辟汰,系統(tǒng)對于線程的創(chuàng)建列敲,調(diào)度管理和釋放都做得很好。而 NSThread 需要我們自己去管理線程的生命周期帖汞,還要考慮線程同步戴而、加鎖問題,造成一些性能上的開銷
五翩蘸、GCD---隊列
iOS 中所意,有 GCD、NSOperation催首、NSThread 等幾種多線程技術(shù)方案扶踊。
而 GCD 共有三種隊列類型:
main queue:通過 dispatch_get_main_queue()獲得,這是一個與主線程相關(guān)的串行隊列郎任。
global queue:全局隊列是并發(fā)隊列秧耗,由整個進(jìn)程共享。存在著高舶治、中分井、低三種優(yōu)先級的全局隊列车猬。調(diào)用 dispath_get_global_queue 并傳入優(yōu)先級來訪問隊列。
自定義隊列:通過函數(shù) dispatch_queue_create 創(chuàng)建的隊列
六尺锚、死鎖 死鎖就是隊列引起的循環(huán)等待
1珠闰、一個比較常見的死鎖例子:主隊列同步
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deallock");
});
}
在主線程中運用主隊列同步,也就是把任務(wù)放到了主線程的隊列中缩麸。 同步對于任務(wù)是立刻執(zhí)行的铸磅,那么當(dāng)把任務(wù)放進(jìn)主隊列時惭聂,它就會立馬執(zhí)行,只有執(zhí)行完這個任務(wù)幔妨, viewDidLoad 才會繼續(xù)向下執(zhí)行。
而 viewDidLoad 和任務(wù)都是在主隊列上的台谊,由于隊列的先進(jìn)先出原則弧械,任務(wù)又需等待 viewDidLoad 執(zhí)行完畢 后才能繼續(xù)執(zhí)行八酒,viewDidLoad 和這個任務(wù)就形成了相互循環(huán)等待,就造成了死鎖刃唐。 想避免這種死鎖羞迷,可以將同步改成異步 dispatch_async,或者將 dispatch_get_main_queue 換成其他串行或并行 隊列,都可以解決画饥。
2衔瓮、同樣,下邊的代碼也會造成死鎖:
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue, ^{
NSLog(@"deallock");
});
});
外面的函數(shù)無論是同步還是異步都會造成死鎖抖甘。
這是因為里面的任務(wù)和外面的任務(wù)都在同一個 serialQueue 隊列內(nèi)热鞍,又是同步,這就和上邊主隊列同步的例 子一樣造成了死鎖
解決方法也和上邊一樣衔彻,將里面的同步改成異步 dispatch_async,或者將 serialQueue 換成其他串行或并行隊 列薇宠,都可以解決
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
dispatch_sync(serialQueue2, ^{
NSLog(@"deallock");
});
});
這樣是不會死鎖的,并且 serialQueue 和 serialQueue2 是在同一個線程中的。
七艰额、GCD 任務(wù)執(zhí)行順序
1澄港、串行隊列先異步后同步
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue, ^{
NSLog(@"2");
});
NSLog(@"3");
dispatch_sync(serialQueue2, ^{
NSLog(@"4");
});
NSLog(@"5");
打印順序是 13245
原因是:
首先先打印 1
接下來將任務(wù) 2 其添加至串行隊列上,由于任務(wù) 2 是異步柄沮,不會阻塞線程回梧,繼續(xù)向下執(zhí)行,打印 3 然后是任務(wù) 4,將任務(wù) 4 添加至串行隊列上祖搓,因為任務(wù) 4 和任務(wù) 2 在同一串行隊列狱意,根據(jù)隊列先進(jìn)先出原則, 任務(wù) 4 必須等任務(wù) 2 執(zhí)行后才能執(zhí)行棕硫,又因為任務(wù) 4 是同步任務(wù)髓涯,會阻塞線程袒啼,只有執(zhí)行完任務(wù) 4 才能繼 續(xù)向下執(zhí)行打印 5
所以最終順序就是 13245哈扮。
這里的任務(wù) 4 在主線程中執(zhí)行纬纪,而任務(wù) 2 在子線程中執(zhí)行。
如果任務(wù) 4 是添加到另一個串行隊列或者并行隊列滑肉,則任務(wù) 2 和任務(wù) 4 無序執(zhí)行(可以添加多個任務(wù)看效果)
2包各、performSelector
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[serialQueue performSelector:@selector(testTread:) withObject:nil afterDelay:0];
});
這里的 test 方法是不會去執(zhí)行的,原因在于
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
這個方法要創(chuàng)建提交任務(wù)到 runloop 上的靶庙,而 gcd 底層創(chuàng)建的線程是默認(rèn)沒有開啟對應(yīng) runloop 的问畅,所有這 個方法就會失效。
而如果將 dispatch_get_global_queue 改成主隊列六荒,由于主隊列所在的主線程是默認(rèn)開啟了 runloop 的护姆,就會 去執(zhí)行(將 dispatch_async 改成同步,因為同步是在當(dāng)前線程執(zhí)行掏击,那么如果當(dāng)前線程是主線程卵皂,test 方法也 是會去執(zhí)行的)。
八砚亭、dispatch_barrier_async
1灯变、問:怎么用 GCD 實現(xiàn)多讀單寫?
多讀單寫的意思就是:可以多個讀者同時讀取數(shù)據(jù),而在讀的時候捅膘,不能取寫入數(shù)據(jù)添祸。并且,在寫的過程 中寻仗,不能有其他寫者去寫刃泌。即讀者之間是并發(fā)的,寫者與讀者或其他寫者是互斥的愧沟。
這里的寫處理就是通過柵欄的形式去寫蔬咬。
就可以用 dispatch_barrier_sync(柵欄函數(shù))去實現(xiàn)
2、dispatch_barrier_sync 的用法:
dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
dispatch_barrier_sync(concurrentQueue, ^{
NSLog(@"barrier");
});
for (NSInteger i = 10; i < 20; i++) {
dispatch_sync(concurrentQueue, ^{
NSLog(@"%zd",i);
});
}
這里的 dispatch_barrier_sync 上的隊列要和需要阻塞的任務(wù)在同一隊列上沐寺,否則是無效的林艘。 從打印上看,任務(wù) 0-9 和任務(wù)任務(wù) 10-19 因為是異步并發(fā)的原因混坞,彼此是無序的狐援。而由于柵欄函數(shù)的存在, 導(dǎo)致順序必然是先執(zhí)行任務(wù) 0-9究孕,再執(zhí)行柵欄函數(shù)啥酱,再去執(zhí)行任務(wù) 10-19。
- dispatch_barrier_sync:Submitsabarrierblockobjectforexecutionandwaitsuntilthatblockcompletes.(提交 一個柵欄函數(shù)在執(zhí)行中,它會等待柵欄函數(shù)執(zhí)行完)
-dispatch_barrier_async:Submitsabarrierblockforasynchronousexecutionandreturnsimmediately.(提交一 個柵欄函數(shù)在異步執(zhí)行中,它會立馬返回)
而 dispatch_barrier_sync 和 dispatch_barrier_async 的區(qū)別也就在于會不會阻塞當(dāng)前線程 比如厨诸,上述代碼如果在 dispatch_barrier_async 后隨便加一條打印镶殷,則會先去執(zhí)行該打印,再去執(zhí)行任 務(wù) 0-9 和柵欄函數(shù);而如果是 dispatch_barrier_sync微酬,則會在任務(wù) 0-9 和柵欄函數(shù)后去執(zhí)行這條打印绘趋。
3颤陶、則可以這樣設(shè)計多讀單寫:
- (void)readDataForKey:(NSString *)key {
__block id result;
dispatch_sync(_concurrentQueue, ^{
result = [self valueForKey:key];
});
return result;
}
- (void)writeData:(id)data forKey:(NSString *)key {
dispatch_barrier_async(_concurrentQueue, ^{
[self setValue:data forKey:key];
});
}
九、dispatch_group_async
場景:在 n 個耗時并發(fā)任務(wù)都完成后陷遮,再去執(zhí)行接下來的任務(wù)滓走。比如,在 n 個網(wǎng)絡(luò)請求完成后去刷新 UI 頁面帽馋。
dispatch_queue_t concurrentQueue = dispatch_queue_create("test1", DISPATCH_QUEUE_CONCURRENT);
dispatch_group_t group = dispatch_group_create();
for (NSInteger i = 0; i < 10; i++) {
dispatch_group_async(group, concurrentQueue, ^{
NSLog(@"%zd:網(wǎng)絡(luò)請求" ,i);
});
}
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
NSLog(@"刷新頁面" );
});
十搅方、Dispatch Semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計數(shù)的信號绽族。 Dispatch Semaphore 提供了三個函數(shù)
1.dispatch_semaphore_create:創(chuàng)建一個 Semaphore 并初始化信號的總量 2.dispatch_semaphore_signal:發(fā)送一個信號姨涡,讓信號總量加 1 3.dispatch_semaphore_wait:可以使總信號量減 1,當(dāng)信號總量為 0 時就會一直等待(阻塞所在線程)吧慢, 否則就可以正常執(zhí)行绣溜。
Dispatch Semaphore 在實際開發(fā)中主要用于:
- 保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù) ? 保證線程安全娄蔼,為線程加鎖
1怖喻、保持線程同步:
dispatch_semaphore_wait 加鎖阻塞了當(dāng)前線程,dispatch_semaphore_signal 解鎖后當(dāng)前線程繼續(xù)執(zhí)行
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSInteger number = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
number = 100;
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
NSLog(@" semaphore---end, number = %Zd", number);
2岁诉、保證線程安全锚沸,為線程加鎖:
在線程安全中可以將 dispatch_semaphore_wait 看作加鎖,而 dispatch_semaphore_signal 看作解鎖 首先創(chuàng)建全局變量
_semaphore = dispatch_semaphore_create(1);
注意到這里的初始化信號量是 1涕癣。
-(void)asyncTask {
dispatch_semaphore_wait(_semaphore, DISPATCH_TIME_FOREVER);
count++;
sleep(1);
NSLog(@"執(zhí)行任務(wù):%zd" ,count);
dispatch_semaphore_signal(_semaphore);
}
異步并發(fā)調(diào)用 asyncTask
for (NSInteger i = 0; i < 100; i++) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self asyncTask];
});
}
然后發(fā)現(xiàn)打印是從任務(wù) 1 順序執(zhí)行到 100哗蜈,沒有發(fā)生兩個任務(wù)同時執(zhí)行的情況。
原因如下:
在子線程中并發(fā)執(zhí)行 asyncTask坠韩,那么第一個添加到并發(fā)隊列里的距潘,會將信號量減 1,此時信號量等于 0只搁, 可以執(zhí)行接下來的任務(wù)音比。而并發(fā)隊列中其他任務(wù),由于此時信號量不等于 0氢惋,必須等當(dāng)前正在執(zhí)行的任務(wù) 執(zhí)行完畢后調(diào)用 dispatch_semaphore_signal 將信號量加 1洞翩,才可以繼續(xù)執(zhí)行接下來的任務(wù),以此類推焰望, 從而達(dá)到線程加鎖的目的骚亿。
十一、延時函數(shù)(dispatch_after)
dispatch_after 能讓我們添加進(jìn)隊列的任務(wù)延時執(zhí)行熊赖,該函數(shù)并不是在指定時間后執(zhí)行處理来屠,而只是在指定 時間追加處理到 dispatch_queue
// 第一個參數(shù)時time 第二個參數(shù)是dispatch_queue 第三個參數(shù)時要執(zhí)行的block
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after");
});
由于其內(nèi)部使用的是 dispatch_time_t 管理時間,而不是 NSTimer。 所以如果在子線程中調(diào)用俱笛,相比 performSelector:afterDelay,不用關(guān)心 runloop 是否開啟
十二绣檬、使用 dispatch_once 實現(xiàn)單例
- (void)shareInstance {
static dispatch_once_t onceToken;
static id instance = nil;
dispatch_once(&onceToken, ^{
instance = [[self alloc]init];
});
return instance;
}
十三、NSOperationQueue 的優(yōu)點
NSOperation嫂粟、NSOperationQueue 是蘋果提供給我們的一套多線程解決方案。實際上 NSOperation墨缘、 NSOperationQueue 是基于 GCD 更高一層的封裝星虹,完全面向?qū)ο蟆5潜?GCD 更簡單易用镊讼、代碼可讀 性也更高宽涌。
1、可以添加任務(wù)依賴蝶棋,方便控制執(zhí)行順序
2卸亮、可以設(shè)定操作執(zhí)行的優(yōu)先級
3、任務(wù)執(zhí)行狀態(tài)控制:isReady,isExecuting,isFinished,isCancelled
如果只是重寫 NSOperation 的 main 方法玩裙,由底層控制變更任務(wù)執(zhí)行及完成狀態(tài)兼贸,以及任務(wù)退出 如果重寫了 NSOperation 的 start 方法,自行控制任務(wù)狀態(tài)
系統(tǒng)通過 KVO 的方式移除 isFinished==YES 的 NSOperation
3吃溅、可以設(shè)置最大并發(fā)量
十四溶诞、NSOperation 和 NSOperationQueue
- 操作(Operation):
執(zhí)行操作的意思,換句話說就是你在線程中執(zhí)行的那段代碼决侈。
在 GCD 中是放在 block 中的螺垢。在 NSOperation 中,使用 NSOperation 子類 NSInvocationOperation赖歌、 NSBlockOperation枉圃,或者自定義子類來封裝操作。
- 操作隊列(OperationQueues):
這里的隊列指操作隊列庐冯,即用來存放操作的隊列孽亲。不同于 GCD 中的調(diào)度隊列 FIFO(先進(jìn)先出)的原則。 NSOperationQueue 對于添加到隊列中的操作展父,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依 賴關(guān)系)墨林,然后進(jìn)入就緒狀態(tài)的操作的開始執(zhí)行順序(非結(jié)束執(zhí)行順序)由操作之間相對的優(yōu)先級決定(優(yōu) 先級是操作對象自身的屬性)。
操作隊列通過設(shè)置最大并發(fā)操作數(shù)(maxConcurrentOperationCount)來控制并發(fā)犯祠、串行旭等。 NSOperationQueue 為我們提供了兩種不同類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上衡载,
而自定義隊列在后臺執(zhí)行搔耕。
十五、NSThread+runloop 實現(xiàn)常駐線程
NSThread 在實際開發(fā)中比較常用到的場景就是去實現(xiàn)常駐線程。
- 由于每次開辟子線程都會消耗cpu弃榨,在需要頻繁使用子線程的情況下菩收,頻繁開辟子線程會消耗大量的 cpu,而且創(chuàng)建線程都是任務(wù)執(zhí)行完成之后也就釋放了鲸睛,不能再次利用娜饵,那么如何創(chuàng)建一個線程可以 讓它可以再次工作呢?也就是創(chuàng)建一個常駐線程。
首先常駐線程既然是常駐官辈,那么我們可以用 GCD 實現(xiàn)一個單例來保存NSThread
- (NSThread *)shareThread {
static NSThread *shareThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
[shareThread setName:@"threadTest"];
[shareThread start];
});
return shareThread;
}
這樣創(chuàng)建的 thread 就不會銷毀了嗎?
[self performSelector:@selector(test) onThread:[viewController shareThread] withObject:nil waitUntilDone:NO];
- (void)test {
NSLog(@"test:%@",[NSThread currentThread]);
}
并沒有打印箱舞,說明 test 方法沒有被調(diào)用。 那么可以用 runloop 來讓線程常駐
- (NSThread * )shareThread {
static NSThread *shareThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest2) object:nil];
[shareThread setName :@"threadTest"];
[shareThread start];
});
return shareThread;
}
+ (void)threadTest {
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode: NSDefaultRunLoopMode];
[runLoop run];
}
}
這時候再去調(diào)用 performSelector 就有打印了拳亿。
十六晴股、自旋鎖與互斥鎖
自旋鎖:
是一種用于保護(hù)多線程共享資源的鎖,與一般互斥鎖(mutex)不同之處在于當(dāng)自旋鎖嘗試獲取鎖時以忙 等待(busy waiting)的形式不斷地循環(huán)檢查鎖是否可用肺魁。當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖 住)电湘,那么下一個線程會一直等待(不會睡眠),當(dāng)上一個線程的任務(wù)執(zhí)行完畢鹅经,下一個線程會立即執(zhí)行寂呛。 在多 CPU 的環(huán)境中,對持有鎖較短的程序來說瘾晃,使用自旋鎖代替一般的互斥鎖往往能夠提高程序的性能昧谊。
互斥鎖:
當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖住),那么下一個線程會進(jìn)入睡眠狀態(tài)等待任務(wù)執(zhí)行完畢酗捌, 當(dāng)上一個線程的任務(wù)執(zhí)行完畢呢诬,下一個線程會自動喚醒然后執(zhí)行任務(wù)。
總結(jié):
自旋鎖會忙等: 所謂忙等胖缤,即在訪問被鎖資源時尚镰,調(diào)用者線程不會休眠,而是不停循環(huán)在那里哪廓,直到被鎖 資源釋放鎖狗唉。
互斥鎖會休眠: 所謂休眠,即在訪問被鎖資源時涡真,調(diào)用者線程會休眠分俯,此時 cpu 可以調(diào)度其他線程工 作。直到被鎖資源釋放鎖哆料。此時會喚醒休眠線程缸剪。
優(yōu)缺點:
自旋鎖的優(yōu)點在于,因為自旋鎖不會引起調(diào)用者睡眠东亦,所以不會進(jìn)行線程調(diào)度杏节,CPU 時間片輪轉(zhuǎn)等耗時操 作。所有如果能在很短的時間內(nèi)獲得鎖,自旋鎖的效率遠(yuǎn)高于互斥鎖奋渔。
缺點在于镊逝,自旋鎖一直占用 CPU,他在未獲得鎖的情況下嫉鲸,一直運行--自旋撑蒜,所以占用著 CPU,如果 不能在很短的時 間內(nèi)獲得鎖玄渗,這無疑會使 CPU 效率降低座菠。自旋鎖不能實現(xiàn)遞歸調(diào)用。
自旋鎖:atomic捻爷、OSSpinLock、dispatch_semaphore_t
互 斥 鎖 : pthread_mutex 份企、 @ synchronized 也榄、 NSLock 、 NSConditionLock 司志、 NSCondition 甜紫、 NSRecursiveLock