一、進(jìn)程、線程
1、進(jìn)程
- 進(jìn)程是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行行動,它是操作系統(tǒng)分配資源的基本單位
- 進(jìn)程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,就是一段程序的執(zhí)行過程,可以理解為手機(jī)上的一個 app
- 每個進(jìn)程之間是獨立的,每個進(jìn)程均運行在其專用且受保護(hù)的內(nèi)存空間內(nèi),擁有獨立運行所需的全部資源
2、線程
- 程序執(zhí)行流的最小單元,線程是進(jìn)程中的一個實體
- 一個進(jìn)程要想執(zhí)行任務(wù),必須至少有一條線程。應(yīng)用程序啟動的時候,系統(tǒng)默認(rèn)會開啟一條線程
3、進(jìn)程和線程的關(guān)系
- 線程是進(jìn)程的執(zhí)行單元,進(jìn)程的所有任務(wù)都在線程中執(zhí)行
- 線程是 CPU 分配資源和調(diào)度的最小單位
- 一個程序可以對應(yīng)多個進(jìn)程(多進(jìn)程),一個進(jìn)程可有多個線程,但至少要有一條線程
- 同一個進(jìn)程內(nèi)線程共享進(jìn)程資源
二、多進(jìn)程、多線程
1、多進(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)程。
2、多線程
- 同一時間,CPU 只能處理一條線程,只有一條在執(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ù)、隊列
1、任務(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)前線程,不一定是主線程),不具備開啟新線程的能力。
異步(sync):線程會立即返回,無需等待就會繼續(xù)執(zhí)行下面的任務(wù),不阻塞當(dāng)前線程。可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力(并不一定開啟新線程)。如果不是添加到主隊列上,異步會在子線程中執(zhí)行任務(wù)
2、隊列
隊列(Dispatch queue):這里的隊列指的是執(zhí)行任務(wù)的等待隊列,即用來存放任務(wù)的隊列。隊列是一種特殊的線性表,采用 FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊列的末尾,而讀取任務(wù)的時候總是從隊列的頭部開始讀取。每讀取一個任務(wù),則從隊列中釋放一個任務(wù)。
在 GCD 中有兩種隊列,串行隊列和并發(fā)隊列。兩者都符合先進(jìn)先出的原則,區(qū)別主要是執(zhí)行順序不同,開啟的線程數(shù)不同。
串行隊列(Serial Dispatch Queue):同一時間內(nèi),隊列中只能執(zhí)行一個任務(wù),只有當(dāng)前的任務(wù)執(zhí)行完成之后,才能執(zhí)行下一個任務(wù)。(只開啟一個線程,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))。主隊列是主線程上的一個串行隊列,是系統(tǒng)自動為我們創(chuàng)建的
并發(fā)隊列(Concurrent Dispatch Queue):同時允許多個任務(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(testThread:) object:@"我是參數(shù)"];
// 當(dāng)使用初始化方法出來的主線程需要 start 啟動
[thread start];
// 可以為開辟的線程起名字
thread.name = @"NSThread線程";
// 調(diào)整 thread 的權(quán)限(范圍是0-1,越大權(quán)限越高,先執(zhí)行的概率越高)
thread.threadPriority = 1;
// 取消當(dāng)前已經(jīng)啟動的線程
[thread cancle];
// 通過遍歷構(gòu)造器開辟子線程
[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"構(gòu)造器方式"];
performSelector...只要是 NSObject 的子類或者對象都可以通過調(diào)用方法進(jìn)入子線程和主線程,其實這些方法所開辟的子線程也是 NSThread 的另一種體現(xiàn)方式。
在編譯階段并不會去檢查方法是否有效存在,如果不存在只會給出警告
// 在當(dāng)前線程。延設(shè)1s執(zhí)行。胸應(yīng)70c語言的動態(tài)性:延遲到運行時才綁定方法
[self performSelector :@selector(aaa) withobject:nil afterDelay:1];
//回到主線程。wO itUntilDone:是否將該回調(diào)方法執(zhí)行完在執(zhí)行后面的代碼,如果為YES:就必須等回調(diào)方法執(zhí)行完成之后才能執(zhí)行后面的代碼
[self performSelectorOnMainThre ad:@selector(aaa) withobject :ni1 waitUntilDone:YES];
// 開辟子線程
[self performSelector InBackground :@selector(aaa) withobject:ni1];
// 在指定線程執(zhí)行
[self performSelector :@selector(aaa) 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(aaa) withobject:nil afterDelay:1];
[[NSRunloop currentRunloop] run];
而 performSelector:withObject: 只是一個單純的消息發(fā)送,和時間沒有一點關(guān)系。所以不需要添加到子線程的 Runloop 中也能執(zhí)行。
2、GCD 對比 NSOprationQueue
我們要明確 NSOperationQueue 與 GCD 之間的關(guān)系
GCD 是面向底層的 C語言的 API,NSOprationQueue 是用 GCD 封裝構(gòu)建的,是 GCD 的高級抽象。
- GCD 的執(zhí)行效率更高,而且由于隊列中執(zhí)行的是由 block 構(gòu)成的任務(wù),這是一個輕量級的數(shù)據(jù)結(jié)構(gòu),寫起來更方便
- GCD 只支持 FIFO 的隊列,而 NSOperationQueue 可以通過設(shè)置最大并發(fā)數(shù),設(shè)置優(yōu)先級,添加依賴關(guān)系等調(diào)整執(zhí)行順序
- NSOperationQueue 甚至支持跨隊列設(shè)置依賴關(guān)系,,但是 GCD 只能通過設(shè)置串行隊列,或者在隊列內(nèi)添加 barrier(dispatch_barrier_async)任務(wù),才能控制執(zhí)行順序,較為復(fù)雜
- 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)用 dispatch_get_global_queue 并傳入優(yōu)先級來訪問隊列。
- 自定義隊列:通過函數(shù) dispatch_queue_create 創(chuàng)建的隊列
六、死鎖
死鎖就是隊列引起的循環(huán)等待
1、一個比較常見的死鎖例子:主隊列同步
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"deadlock");
});
}
在主線程中運用主隊列同步,也就是把任務(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(@"deadlock");
});
});
外面的函數(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(@"deadlock");
});
});
這樣是不會死鎖的,并且 serialQueue 和 serialQueue2 是在同一個線程中的。
七、GCD 任務(wù)執(zhí)行順序
1、串行隊列先異步后同步
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue2, ^{
NSLog(@"deadlock");
});
NSLog(@"3");
dispatch_sync(serialQueue, ^{
NSLog(@"3");
});
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), ^{
[self.performSelector:@selector(test:) withObject:nil afterDelay:0];
});
這里的 test 方法是不會去執(zhí)行的,原因在于
- (void)performSelector:(SEL)aSelector withObject:(nullable id)aArgument afterDelay:(NSTimerIntrval)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",DISPATCHQUEUE_CONCURRENT);
for (NSInteger i = 0; i < 10; i++) {
dispatch_sync(currrentQueue, ^{
NSLog(@"%zd",i);
});
}
dispatch_barrier_sync (concurrentQueue, ^{
NSLog(@"barrier");
});
for (NSInteger i = 10; i < 20; i++) {
dispatch_sync(concurrrentQueue, ^{
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: Submits a barrier block object for execution and waits until that block completes.(提交一個柵欄函數(shù)在執(zhí)行中,它會等待柵欄函數(shù)執(zhí)行完)
- dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一個柵欄函數(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è)計多讀單寫:
- (id)readDataForKey:(NSString *)key{
__block id result;
dispatch_sync(concurrentQueue, ^{
result = [self vaueforKeykey];
));
return result;
}
- (void)writeDeta:(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, ^{
sleep(1);
NSLog(@"%zd:網(wǎng)絡(luò)請求" ,i);
});
}
dispatch_group_notify(group, dispatch_get_main queue(), ^{
NSLog(@"刷新頁面");
});
十、Dispatch Semaphore
GCD 中的信號量是指 Dispatch Semaphore,是持有計數(shù)的信號。
DispatchSemaphore 提供了三個函數(shù)
- dispatch_semaphore_create:創(chuàng)建一個 Semaphore 并初始化信號的總量
- dispatch_semaphore_signal:發(fā)送一個信號,讓信號總量加 1
- dispatch_semaphore_wait:可以使總信號量減 1,當(dāng)信號總量為 0 時就會一直等待(阻塞所在線程),否則就可以正常執(zhí)行。
Dispatch Semaphore 在實際開發(fā)中主要用于:
- 保持線程同步,將異步執(zhí)行任務(wù)轉(zhuǎn)換為同步執(zhí)行任務(wù)
- 保證線程安全,為線程加鎖
1、保持線程同步:
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(semaphare, DISPATCH_TIME_FOREVER);
NSLog( @" semaphare---end number = %zd" ,number);
dispatch_semaphore_wait 加鎖阻塞了當(dāng)前線程,dispatch_semaphore_signal 解鎖后當(dāng)前線程繼續(xù)執(zhí)行
2、保證線程安全,為線程加鎖:
在線程安全中可以將 dispatch_semaphore_wait 看作加鎖,而 dispatch_semaphore_signal 看作解鎖.
首先創(chuàng)建全局變量
_semaphore = dispatch_semaphore_create();
注意到這里的初始化信號量是 1。
- (void)asyncTask{
dispatch_semaphore_wait(_semaphore, DISPATCH TTME 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ù)是dispotch queue,第三 個參是要 執(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)單例
- (instancetype)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ū)ο?。但是?GCD 更簡單易用、代碼可讀性 也更高。
十四、NSOperation 和 NSOperationQueue
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
4、可以設(shè)置最大并發(fā)量
十五、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 :ni1];
[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 current Thread]);
}
并沒有打印,說明 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 {
NSRunLooprunLoop = [NSRunLoop currentRunLoop];
[runLoop addPort: [NSMachPort port] forMode : NSDefaultRunLoopMode] ;
[runLoop run];
}
}
這時候再去調(diào)用 performSelector 就有打印了。
十六、自旋鎖與互斥鎖
自旋鎖:
是一種用于保護(hù)多線程共享資源的鎖,與一般互斥鎖(mutex)不同之處在于當(dāng)自旋鎖嘗試獲取鎖時以忙等待(busywaiting)的形式不斷地循環(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ù)。
優(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