1、進(jìn)程
? ? ? ? 1)進(jìn)程是一個具有一定獨立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運行活動,它是操作系統(tǒng)分配資源的基本單元。
? ? ? ? 2)進(jìn)程是指在系統(tǒng)中正在運行的一個應(yīng)用程序,就是一段程序的執(zhí)行過程,可以理解為手機上的一個app。
? ? ? ? 3)每個進(jìn)程之間是獨立的,每個進(jìn)程均運行在某專用且受保護(hù)的內(nèi)存空間內(nèi),擁有獨立運行所需的全部資源。
2、線程
? ? ? ? 1)程序執(zhí)行流的最小單元,線程是進(jìn)程中的一個實體。
? ? ? ? 2)一個進(jìn)程要想執(zhí)行任務(wù),必須至少有一條線程,應(yīng)用程序啟動的時候,系統(tǒng)會默認(rèn)開啟一條進(jìn)程,也就是主線程。
3、進(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)程資源。
4、多進(jìn)程
? ? ? ? 打開Mac的活動監(jiān)視器,可以卡到很多個進(jìn)程同時運行。
? ? ? ? 1)進(jìn)程是程序在計算機上的一次執(zhí)行活動。當(dāng)你運行一個程序,你就啟動了一個進(jìn)程。顯然,程序是死的(靜態(tài)的),進(jìn)程是活的(動態(tài)的)。
? ? ? ? 2)進(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)行資源分配的單位。
? ? ? ? 3)進(jìn)程又被細(xì)化為線程,也就是一個進(jìn)程下有多個能獨立運行的更小的單位。在同一個時間里,同一臺計算機系統(tǒng)中如果允許兩個或兩個以上的進(jìn)程處于運行狀態(tài),這便是多進(jìn)程。
5、多線程
? ? ? ? 1)同一時間,CPU只能處理1條線程,只有1條線程在執(zhí)行。多線程并發(fā)執(zhí)行,其實就是CPU快速地在多條線程之間調(diào)度(切換)。如果CPU調(diào)度線程的實踐足夠快,就造成了多線程并發(fā)執(zhí)行的假象。
? ? ? ? 2)如果線程非常非常多,CPU會在N多線程之間調(diào)度,消耗大量CPU資源,每條線程被調(diào)度執(zhí)行的頻次會降低(線程的執(zhí)行效率降低)。
? ? ? ? 3)多線程的優(yōu)點:
? ? ? ? ? ? ? ? 能適當(dāng)提高程序的執(zhí)行效率。
? ? ? ? ? ? ? ? 能適當(dāng)提高資源利用率(CPU、內(nèi)存利用率)。
? ? ? ? 4)多線程的缺點:
? ? ? ? ? ? ? ? 開啟線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占用1M,子線程占用512KB),如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能。線程越多,CPU在調(diào)度線程上的開銷就越大。
? ? ? ? ? ? ? ? 程序設(shè)計更加復(fù)雜:比如線程之間的通信、多線程的數(shù)據(jù)共享等。
6、任務(wù)
? ? ????就是執(zhí)行操作的意思,也就是在線程中執(zhí)行的那段代碼。在GCD中是放在block中的。執(zhí)行任務(wù)有兩種方式:同步執(zhí)行(sync)和異步執(zhí)行(async)。
? ? ? ? 1)同步(Sync):同步添加任務(wù)到指定的隊列中,在添加的任務(wù)執(zhí)行結(jié)束之前,會一直等待,直到隊列里面的任務(wù)完成之后再繼續(xù)執(zhí)行,即會阻塞線程。只能在當(dāng)前線程中執(zhí)行任務(wù)(是當(dāng)前線程,不一定是主線程),不具備開啟新線程的能力。 ? ?
? ? ? ? 2)異步(Async):線程會立即返回,無需等待就會繼續(xù)執(zhí)行下面的任務(wù),不阻塞當(dāng)前線程??梢栽谛碌木€程中執(zhí)行任務(wù),具備開啟新線程的negligence(并不一定開啟新線程)。如果不是添加到主隊列上,異步會在子線程中執(zhí)行任務(wù)。
7、隊列
? ? ? ? 隊列(Dispatch Queue):這里的隊列指執(zhí)行任務(wù)的等待隊列,即用來存放任務(wù)的隊列。隊列是一種特殊的線性表,采用FIFO(先進(jìn)先出)的原則,即新任務(wù)總是被插入到隊列的末尾,而讀取任務(wù)的時候總是從隊列的頭部開始讀取。每讀取一個任務(wù),則從隊列中釋放一個任務(wù)。
? ? ? ? 在GCD中有兩個隊列:串行隊列和并發(fā)隊列。兩者都符合FIFO(先進(jìn)先出)的原則。
? ? ? ? 兩者的主要區(qū)別是:執(zhí)行順序不同,以及開啟線程數(shù)不同。
? ? ? ? 1)串行隊列:(Serial Despatch Queue):
? ? ? ? ? ? ? ? 同一時間,隊列中只能執(zhí)行一個任務(wù),只有當(dāng)前的任務(wù)執(zhí)行完成之后,才能執(zhí)行下一個任務(wù)。(只開啟一個線程,一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù))。主隊列是主線程上的一個串行隊列,是系統(tǒng)自動為我們創(chuàng)建的。
? ? ? ? 2)并發(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@"手動創(chuàng)建"];
// 當(dāng)使用初始化方法出來的線程需要start啟動
[thread start];
// 可以為開辟的線程起名字
thread.name = @"自己的線程";
// 調(diào)整Thread的權(quán)限,線程權(quán)限的范圍是0~1。越大權(quán)限越高,先執(zhí)行的概率就會越高,由于概率高,所以并不能很準(zhǔn)確的實現(xiàn)我們想要的執(zhí)行順序,默認(rèn)值時0.5
thread.threadPriority = 1;
// 取消當(dāng)前已經(jīng)啟動的線程
[thread cancel];
// 通過遍歷構(gòu)造器開辟線程
[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"構(gòu)造器方法"];
performSelector...只要是NSObject的子類或者對象都可以通過調(diào)用此方法進(jìn)入子線程和主線程,其實這些方法所開辟的子線程也是NSThread的另一種體現(xiàn)方式。
在編譯階段并不會去檢查方法是否有效存在,如果不存在,只會給出警告。
// 在當(dāng)前線程,延遲1s執(zhí)行。響應(yīng)了OC語言的動態(tài)性:延遲到運行時才綁定方法
[self performSelector:@selector(test) withObject:nil afterDelay:1];
// 回到主線程。waitUntilDone:是否將該回調(diào)方法執(zhí)行完再執(zhí)行后面的代碼。如果是YES:就必須等回調(diào)方法執(zhí)行完成之后才能執(zhí)行后面的代碼,會阻塞當(dāng)前的線程;如果是NO:就是不等回調(diào)方法結(jié)束,不會阻塞當(dāng)前線程
[self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
// 開辟子線程
[self performSelectorInBackground:@selector(test) withObject:nil];
// 在指定線程執(zhí)行
[self performSelector:@selector(test) onThread:[NSThread currentThread] withObject:nil waitUntilDone:YES];
需要注意的是:如果是帶有afterDelay的延時函數(shù),會在內(nèi)部創(chuàng)建一個Timer,然后添加到當(dāng)前線程的RunLoop中,也就是如果當(dāng)前線程沒有開啟runloop,該方法會失效。在子線程中,需要啟動runloop(注意調(diào)用順序)。
[self performSelector:@selector(test) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];
而performSelector: withObject: 只是一個單純的消息發(fā)送,和時間沒有一點關(guān)系。所以不需要添加到子線程的RunLoop中也能執(zhí)行。
2)GCD和NSOperationQueue
GCD是面向底層的C語言的API,NSOperationQueue是用GCD構(gòu)建封裝的,是GCD的高級抽象。
1、GCD執(zhí)行效率更高,而卻由于隊列中執(zhí)行的是block構(gòu)成的任務(wù),這是一個輕量級的數(shù)據(jù)結(jié)構(gòu),寫起來更方便。
2、GCD只支持FIFI的隊列,而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)、是否取消(isCancelled)。
? ? ? ? 1)實際項目開發(fā)中,很多時候只會用到異步操作,不會有特別復(fù)雜的線程關(guān)系管理,所以蘋果推崇的且優(yōu)化完善、運行快速的GCD是首選。
? ? ? ? 2)如果考慮異步操作之間的事務(wù)性,順序性,依賴關(guān)系,比如多線程并發(fā)下載,GCD需要自己寫更多的代碼來實現(xiàn),而NSOperationQueue已經(jīng)內(nèi)建了這些支持
? ? ? ? 3)不論是GCD還是NSOperationQueue,我么接觸的都是任務(wù)和隊列,都沒有直接接觸到線程,事實上線程管理也的確不需要我么操心,系統(tǒng)對于線程的創(chuàng)建,調(diào)度管理和釋放都做得很好。而NSThread需要我們自己去管理線程的生命周期,還要考慮線程頭部、加鎖問題,造成一些性能上的開銷。
3)死鎖
死鎖就是隊列引起的循環(huán)等待
(1)一個比較常見的死鎖:主隊列同步? ?
?- (void)viewDidLoad {
????????[super viewDidLoad];
? ? ? ? dispatch_sync(dispatch_get_main_queue(), ^{ ?
? ? ? ? ? ? ? ? NSLog(@"1");
????????});
? ? ? ? NSLog(@"2");
}
在主線程中運用主隊列同步,也就是把任務(wù)放到了主線程的隊列中。
同步對于任務(wù)是立刻執(zhí)行的,那么當(dāng)吧任務(wù)放到主隊列時,它就會立馬執(zhí)行,只有執(zhí)行完這個任務(wù),viewDidLoad才會繼續(xù)向下執(zhí)行。
而viewDidLoad和任務(wù)都是在主隊列上的,由于隊列的FIFO(先進(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(@"111");
????????});
? ? ????NSLog(@"222");
});
外面的函數(shù)無論是同步還是異步都會造成死鎖。
這是因為里面的任務(wù)和外面的任務(wù)都是在同一個serialQueue隊列內(nèi),又是同步,這就和上面主隊列同步的例子一樣造成了死鎖。
解決方案和上面的一樣,將里面的同步改成異步dispatch_async,或者將serialQueue換成其他串行或者并行隊列,都可以解決。
dispatch_queue_t serialQueue1 = dispatch_queue_create("test", ?DISPATCH_QUEUE_SERIAL);
dispatch_queue_t serialQueue2 = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
dispatch_async(serialQueue1, ^{
? ? ? ? dispatch_sync(serialQueue2, ^{
? ? ? ? ? ? ? ? NSLog(@"111");
????????}) ;
? ? ? ? NSLog(@"222");
});
這樣是不會死鎖的,并且serialQueue1和serialQueue2是在同一個線程中。
4)GCD —— 隊列
GCD有三種隊列類型:
1、main queue:通過dispatch_get_main_queue()獲得,這是一個與主線程相關(guān)的串行隊列。
2、global queue:全局隊列,是并發(fā)隊列,由整個進(jìn)程共享。存放著高、中、低三種優(yōu)先級的全局隊列。調(diào)用dispatch_get_global_queue并傳入優(yōu)先級來訪問隊列。
3、自定義隊列:通過函數(shù)dispatch_queue_create創(chuàng)建的隊列。
(1)串行隊列先異步后同步
dispatch_queue_t serialQueue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
NSLog(@"1");
dispatch_async(serialQueue, ^{
? ? ? ? NSLog(@"2");
});
NSLog(@"3");
dispatch_sync(serialQueue, ^{
? ? ? ? NSLog(@"4");
});
NSLog(@"5");
打印順序:13245。
原因:首先先打印1;接下來將任務(wù)2添加到串行隊列上,由于任務(wù)2是異步,不會阻塞線程,繼續(xù)向下執(zhí)行,打印3;然后是任務(wù)4,將任務(wù)4添加到串行隊列上,因為任務(wù)4和任務(wù)2在同一個串行隊列,根據(jù)隊列FIFI原則,任務(wù)4必須等待任務(wù)2執(zhí)行完后才能執(zhí)行,有因為任務(wù)4是同步任務(wù),會阻塞線程,只有執(zhí)行完任務(wù)4才能繼續(xù)向下執(zhí)行打印5。
這里的任務(wù)4在主線程中執(zhí)行,而任務(wù)2在子線程中執(zhí)行。如果任務(wù)4是添加到另一個串行隊列或者并行隊列,則任務(wù)2和任務(wù)4無序執(zhí)行。
(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)anArgument afterDelay:(NSTimeInterval)delay;
這個方法要創(chuàng)建任務(wù)提交到runloop上,而gcd框架繼承的線程默認(rèn)沒有開啟對應(yīng)runloop的,所以這個方法會失效。
而如果將dispatch_get_global_queue改成主隊列,由于主隊列所在的主線程是默認(rèn)開啟了runloop的,就會去執(zhí)行。
將dispatch_async改成同步,因為同步是在當(dāng)前線程執(zhí)行,那么如果當(dāng)前線程是主線程,test方法也會去執(zhí)行。
在performSelector 下面添加[[NSRunLoop currentRunLoop] run]; 開啟當(dāng)前線程,test也會去執(zhí)行
(3)dispatch_barrier_async
1> 怎么用GCD實現(xiàn)多讀單寫?
多度單寫的意思就是:可以多個讀者同時讀取數(shù)據(jù),而在讀的時候,不能取寫入數(shù)據(jù)。并且,在寫的過程中,不能有其他寫者去寫,即讀者之間是并發(fā)的,寫者與讀者或者其他者時互斥的。

?處理
這里的寫處理可以用 dispatch_barrier_sync(柵欄函數(shù))去實現(xiàn)
2> dispatch_barrier_async
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和熱舞10-19因為是異步并發(fā)的原因,彼此是無序的。而由于柵欄函數(shù)的存在,導(dǎo)致順序必然是先執(zhí)行任務(wù)0-9,再執(zhí)行柵欄函數(shù),再執(zhí)行任務(wù)10-19.
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í)行完后再去執(zhí)行這條打印。
3> 設(shè)計多讀單寫
- (id)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];
????????});
}
(4)dispatch_group_async
場景:在n個耗時并發(fā)任務(wù)都完成后再去執(zhí)行接下來的任務(wù)。比如,在n個網(wǎng)絡(luò)請求完成后刷新UI頁面。
dispatch_queue_t concurrentQueue = dispatch_queue_create("test", DISPATCH_QUQUE_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(@"刷新頁面");
});
(5)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ā)中主要用于:
1> 保證線程同步,將異步執(zhí)行的任務(wù)轉(zhuǎn)換成同步執(zhí)行的任務(wù)。
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);
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);
__block NSInteger count = 0;
// 這里信號量是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];?????????
????????});
}
打印的是任務(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á)到線程加鎖的目的。
(6)延時函數(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是否開啟。
(7)單例(dispatch_once)
static id _instance;
+ (instancetype)shareInstance {
static dispatch_once_t ?onceToken;
? ? ? ? dispatch_once(&onceToken, ^{
? ? ? ? ? ? ? ? _instance = [[self alloc] init];
????????});
? ? ? ? return _instance;
}
+ (id)allocWithZone:(struct _NSZone *)zone {
static dispatch_one_t onceToken;
? ? ? ? dispatch_once(&onceToken, ^{
? ? ? ? ? ? ? ? _instance = [super allocWithZone:zone];
????????});
? ? ? ? return _instance;
}
// 如果遵守了NSCopying協(xié)議
- (id)copyWithZone:(nullable NSZone *)zone {
????????return _instance;
}
5)NSOperationQueue
NSOperation、NSOperationQueue是蘋果提供給我們的一套多線程解決方案,實際上NSOperation、NSOperationQueue是基于GCD更高一層的封裝,完全面向?qū)ο蟆5潜菺CD更簡單易用、代碼可讀性也更高。
1、可添加完成的代碼塊,在操作完成后執(zhí)行。
2、可以添加操作之間的依賴,方便控制執(zhí)行順序。
3、可以設(shè)置操作執(zhí)行的優(yōu)先級。
4、可以很方便的取消一個操作的執(zhí)行。
5、使用KVO觀察對操作執(zhí)行狀態(tài)的更改:isExecuting、isFinished、isCancelled。
????1)如果只是重寫NSOperation的main方法,由底層控制變更任務(wù)執(zhí)行及完成狀態(tài),以及任務(wù)退出。
????2)如果重寫了NSOperation的start方法,自行控制任務(wù)狀態(tài)。
????3)系統(tǒng)通過KVO的方式移出isFinished == NSOperation。
6、可以設(shè)置最大并發(fā)數(shù)量
既然是基于GCD的更高一層的封裝。那么,GCD中的一些概念同樣適用于NSOperation、NSOperationQueue。在NSOperation、NSOperationQueue中也有類似的任務(wù)和隊列。
操作(Operation):
1、執(zhí)行操作的意思,換句話說就是你在線程中執(zhí)行的那段代碼。
2、在GCD中是放在block里面的。在NSOperation中,我們使用NSOperation子類NSInvocationOperation、NSBlockOperation,或者自定義子類來封裝操作。
操作隊列(OperationQueue)
1、用來存放操作的隊列。不同于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)先級是操作對象自身的屬性)。
2、操作隊列通過設(shè)置最大并發(fā)數(shù)(maxConcurrentOperationCount)來控制并發(fā)、串行數(shù)量。
3、NSOperationQueue為我們提供了兩種不同類型的隊列:主隊列和自定義隊列。主隊列運行在主線程上,自定義隊列在后臺執(zhí)行。
NSOperation、NSOperationQueue使用步驟:
NSOperation需要配合NSOperationQueue來實現(xiàn)多線程。因為默認(rèn)情況下,NSOperation單獨使用時,系統(tǒng)同步執(zhí)行操作,配合NSOperationQueue才能更好實現(xiàn)異步執(zhí)行。
1、創(chuàng)建操作:先將需要執(zhí)行的操作封裝到一個NSOperation對象中。
2、創(chuàng)建隊列:創(chuàng)建NSOperationQueue對象
3、將操作加入到隊列中:將NSOperation對象添加到NSOperationQueue對象中。
創(chuàng)建操作
NSOperation是個抽象類,不能用來封裝操作。我們只能使用它的子類來封裝操作。
1、NSInvocationOperation。
2、NSBlockOperation。
3、自定義繼承自NSOperation的子類,通過實現(xiàn)內(nèi)部相應(yīng)的方法來封裝操作。
-- NSInvocationOperation
- (void)useInvocationOperation {
? ? ? ? // 1、創(chuàng)建 NSInvocationOperation對象
? ? ? ? NSInvocationOperation * op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task) object:nil];
? ? ? ? // 2、調(diào)用 start 方法開始執(zhí)行操作
? ? ? ? [op start];
}
- (void)task {
? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? ? ? [NSThread sleepForTimeInterval:0.5] // 模擬耗時操作
? ? ? ? ? ? ? ? NSLog(@"111 ------ %@", [NSThread currentThread]); / / 打印當(dāng)前線程
????????}
}
在沒有使用NSOperationQueue,在主線程中單獨使用子類NSInvocationOperation執(zhí)行一個操作時,操作是在當(dāng)前線程執(zhí)行的,并沒有開啟新線程。
切換到其他線程
// 在其他線程使用子類NSInvocationOperation
[NSThread detachNewThreadSelector:@selector(useInvocationOperation) toTarget:self withObject:nil];
在其他線程中單獨使用子類NSInvocationOperation,操作是在當(dāng)前調(diào)用的其他線程上執(zhí)行的,并沒有開啟新線程。
-- NSBlockOperation
- (void)useBlockOperation {
// 1、創(chuàng)建 NSBlockOperation對象
? ? ? ? NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? for (int i = 0; i < 10; i ++ ) {
? ? ? ? ? ? ? ? ? ? ? ? [NSThread sleepForTimeInterval:0.5];
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"111 --- %@", [NSThread currentThread]);
????????????????}
????????}];
? ? ? ? // 2、調(diào)用 start 方法開始執(zhí)行操作
? ? ? ? [op start];
}
和上面的NSInvocationOperation使用一樣。因為代碼是在主線程中調(diào)用的,所有打印結(jié)果為主線程。如果在其他線程中執(zhí)行操作,則打印結(jié)果為其他線程。
但是,NSBlockOperation還提供了一個方法 addExecutionBlock:,通過 addExecutionBlock:就可以為NSBlockOperation添加額外的操作。這些操作(包括blockOperationWithBlock中的操作)可以在不同的線程中同時(并發(fā))執(zhí)行。只有當(dāng)所有相關(guān)的操作已經(jīng)完全執(zhí)行時,才視為完成。
如果添加的操作過多的話,blockOperationWithBlock:中的操作也可能會在其他線程(非當(dāng)前線程)中執(zhí)行,這是由系統(tǒng)決定的,并不是說添加到 blockOperationWithBlock:中的操作一定會在當(dāng)前線程中執(zhí)行。
- (void)useBlockOperationAndExecutionBlock {
? ? ? ? NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? ? ? ? ? ? ? [NSThread sleepForTimeInterval:0.2];
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"1 --- %@", [NSThread currentThread]);
????????????????}
}];
? ? ? ? [op addExecutionBlock:^{
? ? ? ? ? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? ? ? ? ? ? ? [NSThread sleepForTimeInterval:0.2];
? ? ? ? ? ? ? ? ? ? ? ? [NSLog(@"2 ---- %@", [NSThread currentThread])];
????????????????}
????????}];
? ? ? ? // ......
}
使用子類NSBlockOperation,并調(diào)用方法 AddExecutionBlock:的情況下,blockOperationWithBlock:方法中的操作和 addExecutionBlock:中的操作是在不同的線程中異步執(zhí)行的。
一般情況,如果一個NSBlockOperation對象封裝了多個操作。NSBlockOperation是否開啟新線程,取決于操作的個數(shù)。如果操作個數(shù)多,就可能會自動開啟新線程。開啟的線程數(shù)是由系統(tǒng)決定的。
-- 自定義繼承自NSOperation 的子類
通過重寫main或者start方法來定義自己的NSOperation對象。
// KKOperation.h
#import <Foundation/Foundation.h>
@interface KKOperation : NSOperation
@end
#import "KKOperation.h"
@implementation KKOperation
- (void)main {
? ? ? ? if (self.isCancelled) {
? ? ? ? ? ? ? ? if (int i = 0 ; i < 10; i++) {
? ? ? ? ? ? ? ? ? ? ? ? [NSThread sleepForTimeInterval:0.2];
? ? ? ? ? ? ? ? ? ? ? ?NSLog(@"1 ---- %@", [NSThread currentThread]);
????????????????}
????????}
}
@end
#import "KKOperation.h"
- (void)useCustomOperation {
KKOperation *op = [[KKOperation alloc] init];
? ? ? ? [op start];
}
在沒有使用NSOperationQueue,在主線程單獨使用自定義繼承自NSOperation的子類的去年高考下,是在主線程執(zhí)行的操作,并沒有開啟新線程
創(chuàng)建隊列
NSOperationQueue一共有兩種隊列:主隊列、自定義隊列。其中自定義隊列公司包含了串行、并發(fā)功能。
1、主隊列
凡是添加到主隊列中的操作,都會放到主線程中執(zhí)行(注:不包括操作使用addExecutionBlock:添加的額外操作,額外操作可能在其他線程執(zhí)行)。
NSOperationQueue *queue = [NSOperationQueue mainQueue];
2、自定義隊列
????1)添加到這種隊列中的操作,會自動放到子線程中執(zhí)行
? ? 2)同時包含了:串行、并發(fā)功能
? ? ? ? ? ? NSOperationQueue *queue = [[NSOperationQueue alloc] init]; ? ?
-- 將操作放入隊列中
(1) - (void)addOperation:(NSOperation *)op;
需要先創(chuàng)建操作。再將創(chuàng)建好的操作放入到建好的隊列中去
- (void)addOperationToQueue {
NSOperationQueue *queue = [[NSOperation alloc] init];
? ? ? ? NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(task1) object:nil];
? ? ? ? NSInvocationOperation *op2 = [[NSInvocation alloc] initWithTarget:self selector:@selector(task2) withObject:nil];
? ? ? ? NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? for (int i = 0; i < 10; i++) ?{
? ? ? ? ? ? ? ? ? ? ? ? [NSThread sleepForTimeInterval:0.2];
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"3 --- %@", [NSThread currentThread]);
????????????????}
????????}];
? ? ? ? [op3 addExecutionBlock:^{
? ? ? ? ? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? ? ? ? ? ? ? [NSThread sleepTimeInterval:0.2];
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"4 --- %@", [NSThread currentThread]);
????????????????}
????????}];
? ? ? ? [queue addOperation:op1];
? ? ? ? [queue addOperation:op2];
? ? ? ? [queue addOperation:op3];
}
使用NSOperation子類創(chuàng)建的操作,并使用addOperation:將操作加入到操作隊列后能夠開啟新線程,進(jìn)行并發(fā)執(zhí)行。
(2)- (void)addOperationWithBlock:(void (^)(void))block;
無需先創(chuàng)建操作,在block中直接添加操作,直接將包含操作的block加入到隊列中。
- (void)addOperationWithBlockToQueue {
NSOperationQueue *queue = [[NSOperation alloc] init];
? ? ? ? [queue addOperationWithBlock:^{
? ? ? ? ? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? ? ? ? ? ? ? [NSThread sleepForTimeInterval:0.2];
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"1 --- %@", [NSThread currentThread]);
????????????????}
????????}];
? ? ? ? // ....
}
使用addOperationWithBlock:將操作加操作隊列后能夠開啟新線程,進(jìn)行并發(fā)執(zhí)行
-- NSOperationQueue控制串行,并發(fā)執(zhí)行
maxConcurrentOperationCount:最大并發(fā)操作數(shù),用來控制一個特定隊列中可以有多少個操作同時參與并發(fā)執(zhí)行。(不是指并發(fā)線程的數(shù)量,而是一個隊列中同時能并發(fā)執(zhí)行的最大操作數(shù)。而且一個操作也并非只能在一個線程中運行)。
maxConcurrentOperationCount:默認(rèn)情況為-1,表示不進(jìn)行限制,可進(jìn)行并發(fā)執(zhí)行。為1時,隊列為串行隊列,只能串行執(zhí)行。大于1是,隊列為并發(fā)隊列,操作并發(fā)執(zhí)行,當(dāng)然這個值不能超過系統(tǒng)限制,即使自己設(shè)置一個很大的值,系統(tǒng)也會自動調(diào)整為min(自己設(shè)定的值,系統(tǒng)設(shè)定的最大值)。
-- NSOperation操作依賴
NSOperation、NSOperationQueue最吸引人的地方是它能添加操作之間的依賴關(guān)系。通過操作依賴,我們可以很方便的控制操作之間的執(zhí)行順序。
// 添加依賴,使當(dāng)前操作依賴于操作op的完成
1、-(void)addDependency:(NSOperation *)op;?
// 移出依賴,取消當(dāng)前操作對操作op的依賴
2、- (void)removeDependency:(NSOperation *)op;
// 在當(dāng)前操作開始執(zhí)行之前,完成執(zhí)行的所有操作對象數(shù)組
3、@property (readonly, copy) NSArray<NSOperation *> *dependencies;
A執(zhí)行完操作,B才能執(zhí)行操作
- (void)addDependency {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
? ? ? ? NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? // .....
????????}];
? ? ? ? NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? // .....
????????}];
? ? ? ? [op2 addDependency:op1];
? ? ? ? [queue addOperation:op1];
? ? ? ? [queue addOperation:op2];
}
-- NSOperation優(yōu)先級
queuePriority屬性適用于同一操作隊列中的操作,不適用與不同操作隊列中的操作。默認(rèn)所有新創(chuàng)建的操作對象優(yōu)先級都是 NSOperationQueuePriorityNormal;
typedef NS_ENUM(NSInteger, NSOperationQueuePriority) {
? ? ? ? NSOperationQueuePriorityVerLow = -8L,
? ? ? ? NSOperationQueuePriorityLow = -4L,
? ? ? ? NSOperationQueuePriorityNormal = 0,
? ? ? ? NSOperationQueuePriorityHigh = 4,
? ? ? ? NSOperationQueuePriorityVeryHigh = 8,
}
對于添加到隊列中的操作,首先進(jìn)入準(zhǔn)備就緒的狀態(tài)(就緒狀態(tài)取決于操作之間的依賴關(guān)系),然后進(jìn)入就緒狀態(tài)的操作開始執(zhí)行,順序(非結(jié)束執(zhí)行順序)由操作之間相對的優(yōu)先級決定(優(yōu)先級是操作對象自身的屬性)。
當(dāng)一個操作的所有依賴都已經(jīng)完成時,操作對象通常會進(jìn)入準(zhǔn)備就緒狀態(tài),等待執(zhí)行。
例如:
現(xiàn)在有4個優(yōu)先級都是 NSOperationQueuePriorityNormal(默認(rèn)級別)的操作:op1、op2、op3、op4.其中op3依賴op2,op2依賴op1?,F(xiàn)在講將者4個操作添加到隊列中并發(fā)執(zhí)行。
1、因為op1和op4都沒有需要依賴的操作,所以在op1、op4執(zhí)行之前,就處于就緒狀態(tài)的操作、
2、op3和op2都有依賴的操作,所以op3和op2都不是準(zhǔn)備就緒狀態(tài)下的操作。
queuePriority的作用:
1、queuePriority屬性決定了進(jìn)入準(zhǔn)備就緒狀態(tài)下的操作之間的開始執(zhí)行順序,并且,優(yōu)先級不能取代依賴關(guān)系。
2、如果一個隊列中既包含高優(yōu)先級操作,又包含低優(yōu)先級操作,并且兩個操作都已經(jīng)準(zhǔn)備就緒,那么列表先執(zhí)行高優(yōu)先級操作。比如上例,如果op1和op4是不同優(yōu)先級操作,那么就會先執(zhí)行優(yōu)先級高的操作。
3、如果一個隊列中既包含了準(zhǔn)備就緒狀態(tài)的操作,又包含了未準(zhǔn)備就緒的操作,未準(zhǔn)備就緒的操作優(yōu)先級比準(zhǔn)備就緒的操作優(yōu)先級高。那么,雖然準(zhǔn)備就緒的操作優(yōu)先級低,也會優(yōu)先執(zhí)行。優(yōu)先級不能取代依賴關(guān)系。如果要控制操作間的啟動順序,則必須使用依賴關(guān)系。
-- NSOperation、NSOperationQueue線程間的通信
在iOS開發(fā)過程中,我們一般在主線程進(jìn)行UI刷新,例如:點擊、滾動、拖拽等事件。我們常把一些耗時的操作放在其他線程,如網(wǎng)絡(luò)圖片加載、文件上傳等耗時操作。當(dāng)我們在其他線程完成了耗時操作時,需要回到主線程,那么就用到了線程之間的通信。
- (void)communication {
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
? ? ? ? [queue addOperationWithBlock:^{
? ? ? ? ? ? ? ? // 耗時操作
????????}];
? ? ? ? [[NSOperationQueue mainQueue] addOperationWithBlock:^{
? ? ? ? ? ? ? ? // 更新UI顯示
????????}];
}
-- NSOperation、NSOperationQueue線程同步和線程安全
1、線程安全:如果代碼所在的進(jìn)程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。
????1)如果每次運行結(jié)果和單線程運行的結(jié)果是一樣的,而且其他的變量的值也和預(yù)期的是一樣,就是線程安全的。
????2)若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,一般來說,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作(更改變量),一般都需要考慮線程同步,否則的話可能影響線程安全。
2、線程同步:
? ? 1)例如:線程A和線程B一塊合作,A執(zhí)行到一定程度時要依靠線程B的某個結(jié)果,于是停下來,示意B運行;B以言執(zhí)行,再講結(jié)果給A;A再繼續(xù)操作。
? ? 2)例如:兩個人在一起聊天,兩個人不能同時說話,避免聽不清(操作沖突)。等一個人說完(一個線程結(jié)束操作),另一個再說(另一個線程開始操作。)
## 模擬火車票售賣:總共有100張火車票,有兩個售賣窗口,一個在北京,一個再廣州。兩窗口同時售賣,直到賣完為止。
非線程安全:
/*
? ? 非線程安全:
? ? 初始化火車票數(shù)量,賣票窗口(非線程安全),并開始賣票
*/
- (void)initTicketStatusNotSave {
????????NSLog(@"currentThread --- %@", [NSThread currentThread]);
? ? ? ? self.ticketSurplusCount = 100;
? ? ? ? // 北京窗口
? ? ? ? NSOperationQueue *queue1?= [[NSOperationQueue alloc] init];
? ? ? ? queue1.maxConcurrentOperationCount = 1;
? ? ? ? // 廣州窗口
? ? ? ? NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
? ? ? ? queue2.maxConcurrentOperationCount = 1;
? ? ? ? // 售賣操作 op
? ? ? ? NSBlockOperation *op1 ?= [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? [self saleTicketNotSafe];
????????}];
? ? ? ? NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? [self saleTicketNotSafe];
????????}];
? ? ? ? // 添加操作,開始買票
? ? ? ? [queue1 addOperation:op1];
? ? ? ? [queue2 addOperation:op2];
}
// 售賣火車票(非線程安全)
- (void)saleTicketNotSafe{
? ? ? ? while(1) {
? ? ? ? ? ? ? ? if (self.ticketSurplusCount > 0 ) {
? ? ? ? ? ? ? ? ? ? ? ? self.ticketSurplusCount--;
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"剩余票數(shù):%zd,窗口:%@", self.ticketSurplusCount, [NSThread currentThread]);
????????????????}else {
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"火車票已售完!");
? ? ? ? ? ? ? ? ? ? ? ? break;
????????????????}
????????}
}
結(jié)果:非線程安全下,即不使用NSLock情況下,得到的票數(shù)錯亂無章的。
線程安全:給線程加鎖,在一個線程執(zhí)行高操作的時候,不允許其他線程進(jìn)行操作
iOS線程加鎖方式:
@synchronized、NSLock、NSRecursiveLock、NSCondition、NSConditionLock、pthread_mutex、dispatch_semaphore、OSSpinLock、atomic(property) set/get的等等
/*
線程安全:使用NSLocj加鎖
? ? 初始化火車票數(shù)量,賣票窗口(線程安全),并開始賣票
*/
-(void)initTicketStatusSave {
NSLog(@"currentThread -- %@", [NSThread currentThread]);
? ? ? ? self.ticketSurplusCount = 100; // 總票數(shù)
? ? ? ? self.luck = [[NSLock alloc] init]; // 初始化鎖
? ? ? ? // 初始化售賣窗口
? ? ? ? NSOperationQueue *queue1 = [[NSOperationQueue alloc] init];
? ? ? ? queue1.maxConcurrentOperationCount = 1;
? ? ? ? NSOperationQueue *queue2 = [[NSOperationQueue alloc] init];
? ? ? ? queue2.maxConcurrentOperationCount = 1;
? ? ? ? // 初始化售賣操作
? ? ? ? NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? [self saleTicketSafe];
????????}];
? ? ? ? NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
? ? ? ? ? ? ? ? [self saleTicketSafe];
????????}];
? ? ? ? // 添加操作,開始賣票
? ? ? ? [queue1 addOperation:op1];
? ? ? ? [queue2 addOperation:op2];
}
// 售賣火車票(線程安全)
- (void)saleTicketSafe {
? ? ? ? while(1) {
????????????????[self.lock lock]; // 加鎖
? ? ? ? ? ? ? ?if (self.ticketSurplusCount > 0) {
? ? ? ? ? ? ? ? ? ? ? ? self.ticketSurplusCount--;
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"剩余票數(shù):%zd,窗口:%@", self.ticketSurplusCount, [NSThread currentThread]);
????????????????}
?????????????????[self.lock unlock]; ?// 解鎖
????????????????if (self.ticketSurplusCount <= 0) {
????????????????????????NSLog(@"所有火車票已售完!"); ? ?
????????????????????????bread;
????????????????}????
????????}
}
在考慮了線程安全,使用了NSLock加載、解鎖機制下,得到了票數(shù)是正確的,沒有出現(xiàn)混亂的情況。解決了多線程同步問題。
-- NSOperation常用屬性及方法
1、取消操作:-(void)cancel;
2、判斷操作狀態(tài)的方法
? ? ? ? 1)-(BOOL)isFinished; // 判斷操作是否已經(jīng)結(jié)束
? ? ? ? 2)-(BOOL)isCancelled; // 判斷操作是否已經(jīng)標(biāo)記為取消
? ? ? ? 3)-(BOOL)isExecuting; // 判斷操作是否正在運行
? ? ? ? 4)-(BOOL)isReady; // 判斷操作是否處于準(zhǔn)備就緒狀態(tài),這個值和操作的依賴關(guān)系相關(guān)
3、操作同步
? ? ? ? 1)-(void)waitUntilFinished;阻塞當(dāng)前線程,直到該操作結(jié)束??捎糜诰€程執(zhí)行順序的同步。
? ? ? ? 2)-(void)setCompletionBlock:(void (^)(void))block;completionBlock會在當(dāng)前操作執(zhí)行完畢時執(zhí)行completionBlock。
? ? ? ? 3)-(void)addDependency:(NSOperation *)op;添加依賴,使當(dāng)前操作依賴于操作op的完成。
? ? ? ? 4)-(void)removeDependency:(NSOperation *)op;移出依賴,取消當(dāng)前操作對操作ap的依賴。
? ? ? ? 5)@property (readonly, copy) NSArray<NSOperation *> *dependencies;在當(dāng)前操作開始執(zhí)行之前完成執(zhí)行的所有操作的數(shù)組。
-- NSOperationQueue的常用屬性和方法
1、取消、暫停、恢復(fù)操作
? ? ? ? 1)-(void)cancelAllOperation;可以取消隊列的所有操作。
? ? ? ? 2)-(BOOL)isSuspended;判斷隊列是否處于暫停狀態(tài)。YES:暫停,NO為恢復(fù)。
? ? ? ? 3)-(void)setSuspended:(BOOL)b;可設(shè)置操作的暫停和恢復(fù),YES代表暫停,NO代表恢復(fù)。
2、操作同步
-(void)waitUntilAllOperationsAreFinished;阻塞當(dāng)前線程,知道隊列中的操作全部執(zhí)行完畢。
3、添加、獲取操作
? ? ? ? 1)-(void)addOperationWithBlock:(void (^)(void))block;向隊列中添加一個NSBlockOperation類型的操作對象。
? ? ? ? 2)-(void)addOperations:(NSArray *)ops waitUntilFinished:(BOOL)wait;向隊列中添加數(shù)組,wait標(biāo)志是否阻塞當(dāng)前線程直到所有操作結(jié)束。
? ? ? ? 3)-(NSArray *)operations;當(dāng)前在隊列中的操作數(shù)組(某個操作執(zhí)行結(jié)束后會自動從數(shù)組清除)。
? ? ? ? 4)-(NSInteger)operationCount;當(dāng)前隊列中的操作數(shù)。
4、獲取隊列
? ? ? ? 1)+(id)currentQueue;獲取當(dāng)前隊列,如果當(dāng)前線程不是在NSOperationQueue上運行則返回bil。
? ? ? ? 2)+(id)mianQueue:獲取主隊列。
warning:
1、這里的暫停和取消(包括操作的取消和隊列的取消)并不代表可以將當(dāng)前的操作立即取消,而是當(dāng)當(dāng)前的操作執(zhí)行完畢忠厚不再執(zhí)行新的操作。
2、暫停和取消的區(qū)別就在于:暫停操作之后還可以恢復(fù)操作,繼續(xù)向下執(zhí)行;而取消操作之后,所有的操作就情況了,無法再接著執(zhí)行剩下的操作。
-- NSThread + runloop實現(xiàn)常駐線程
NSThread在實際開發(fā)中比較常用到的場景就是去實現(xiàn)常駐線程。
由于每次開辟子線程都會消耗cpu,在需要頻繁使用子線程的情況下,頻繁開辟子線程會消耗大量的cpu資源,而且創(chuàng)建線程都是任務(wù)執(zhí)行完之后就釋放了,不能再次利用。此時就需要創(chuàng)建一個常駐線程。
GCD實現(xiàn)以單例來保存NSThread
+ (NSThread *)shareThread {
static id shareThread;
? ? ? ? static dispatch_once_t onceToken;
? ? ? ? dispatch_once(&onceToken, ^{
? ? ? ? ? ? ? ? shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
? ? ? ? ? ? ? ? [shareThread setName:@"shareThread"];
? ? ? ? ? ? ? ? [shareThread start];
????????});
? ? ? ? return shareThread;
}
+ (void)threadTest {
? ? ? ? @autoreleasepool {
? ? ? ? ? ? ? ? NSRunLoop *runloop = [NSRunLoop currentRunLoop];
? ? ? ? ? ? ? ? [runloop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
? ? ? ? ? ? ? ? [runloop run];
????????}
}
[self performSelector:@selector(test) onThread:[[self class] shareThread] withObject:nil waitUntilDone:NO];
- (void)test {
? ? ? ? NSLog(@"test thread:%@", [NSThread currentThread]);
}
27、鎖

1、自旋鎖:
是一種用于保護(hù)多線程共享資源的鎖,與一般互斥鎖(mutex)不同之處在于當(dāng)自旋鎖嘗試獲取鎖時,以忙等待(busy waiting)的形式不斷地循環(huán)檢查鎖是否可用。當(dāng)上一個線程的任務(wù)沒有執(zhí)行完畢的時候(被鎖住),那么下一個線程會一直等待(不會睡眠),當(dāng)上一個線程的任務(wù)執(zhí)行完畢,下一個線程會立即執(zhí)行。在多CPU的環(huán)境中,對持有鎖較短的程序來說,使用自旋鎖代替一般的互斥鎖往往能夠提高程序性能。
2、互斥鎖:
讓上一個線程的任務(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,它在未獲得鎖的情況下,一直自旋,如果不能在很短的實踐內(nèi)獲得鎖,無疑會浪費CPU資源,是CPU效率降低。自旋鎖不能實現(xiàn)遞歸調(diào)用。 ? ? ? ?
自旋鎖:atomic、CSSPinLock、dispatch_semaphore_t
互斥鎖:pthread_mutex、@synchronized、NSLock、NSConditionLock、NSCondition、NSRecursiveLock。
自旋鎖實現(xiàn)思路:
bool lock = false; // 一開始沒上鎖,任何線程都可以申請解鎖
do {
? ? ? ? while (lock); // 如果lock為true就一直死循環(huán),相當(dāng)于申請鎖
? ? ? ? lock = true; ? ?// 上鎖,這樣別的線程就無法獲得鎖
? ? ? ? // 臨界區(qū)
? ? ? ? lock = false; ? ? // 相當(dāng)于釋放鎖,這樣別的線程可以進(jìn)入臨界區(qū)
????????// 不需要鎖保護(hù)的代碼
}
@synchronized
NSObject *objc = [[NSObject alloc] ?init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? @synchronized(obj) {
? ? ? ? ? ? ? ? NSLog(@"線程1 開始");
? ? ? ? ? ? ? ? sleep(2);
? ? ? ? ? ? ? ? NSLog(@"線程1 結(jié)束");
????????}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(2);
? ? ? ? @synchronized(obj){
? ? ? ? ? ? ? ? NSLog(@"線程 2");
????????}
});
// 線程1 開始 ——》線程1 結(jié)束 ——》 線程2
@synchronized(obj)指令是用obj為該鎖的唯一標(biāo)識,只有當(dāng)標(biāo)識相同時,才能滿足互斥,如果線程2中的obj為其他值,則線程2就不會被阻塞。
@synchronized指令實現(xiàn)鎖的優(yōu)點:我們不需要在代碼中顯示的創(chuàng)建鎖對象,便可以實現(xiàn)鎖的機制,但作為一種防御措施,@synchronized塊會隱士的添加一個異常處理來保護(hù)代碼,該處理例程會在異常拋出的時候自動的釋放互斥鎖。如果不想讓隱士的異常處理例程帶來額外的開銷,可以考慮使用鎖對象。
dispatch_semaphore
dispatch_semaphore_t signal ?= dispatch_semaphore_create(1);
dispatch_time_t waitTime = dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
dispatch_semaphore_wait(signal, waitTime);
? ? ? ? NSLog(@"線程 1 開始");
? ? ? ? sleep(2);
? ? ? ? NSLog(@"線程 1 結(jié)束");
? ? ? ? dispatch_semaphore_signal(signal);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
? ? ? ? dispatch_semaphore_wait(signal, waitTime);
? ? ? ? NSLog(@"線程 2");
? ? ? ? dispatch_semaphore_signal(signal);
});
// waitTime > 2:同步操作。 線程1 開始 ? ?——》 線程1 結(jié)束 ? ?——》線程2
//waitTime < 2:線程1 開始 ? ?——》線程2 ? ?——》線程1 結(jié)束
dispatch_semaphore是GCD用來同步的一種方式。
1、dispatch_semaphore_create的聲明:
dispatch_semaphore_t dispatch_semaphore_create(long value);傳入的參數(shù)為long,輸出是一個dispatch_semaphore_t類型且值為value的信號量。
參數(shù)value必須 大于或等于0,否認(rèn)在dispatch_semaphore_create會返回NULL。
2、dispatch_semaphore_signal聲明:
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);這個函數(shù)會使傳入的信號量dsema的值加1。
3、dispatch_semaphore_wait聲明:
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);設(shè)個函數(shù)會使傳入的信號量dsema的值減1。
函數(shù)作用:如果dsema信號量的值大于0,該函數(shù)所處的線程就繼續(xù)執(zhí)行下面的語句,并且將信號量的值減1;如果dsema的值為0;那么這個函數(shù)就阻塞當(dāng)前線程等待timeout(注意timeout的類型為dispatch_time_t,不能直接傳入整型或float型數(shù)),如果等待期dsema的值被dispatch_semaphore)signal函數(shù)加1了,且該函數(shù)(dispatch_semaphore_wait)所處線程獲得了信號量,那么就繼續(xù)向下執(zhí)行并將信號量減1.如果等待期沒有獲得信號量或者信號量的值一直未0,那么等到timeout時,其所處線程自動執(zhí)行其后語句。
dispatch_semaphore是信號量,但當(dāng)信號總量為1時,也可以當(dāng)作鎖來用。在沒有等待情況出現(xiàn)時,它的性能比 pthread_mutext還要高,但一旦有等待情況出現(xiàn),性能就會下降很多。相對于OSSpinLock來說,它的優(yōu)勢在于等待時不會消耗CPU資源。
NSLock
NSLock *lock = [[NSLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// [lock lock];
? ? ? ? [lock lockBeforeDate:[NSDate date]];
? ? ? ? NSLog(@"線程 1 開始");
? ? ? ? sleep(2);
? ? ? ? NSLog(@"線程1 結(jié)束");
? ? ? ? [lock unlock];
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
sleep(1);
? ? ? ? if ([lock tryLock]) { ?// 嘗試獲取鎖,如果獲取不到返回NO,不會阻塞該線程
? ? ? ? ? ? ? ? NSLog(@"鎖可以操作");
? ? ? ? ? ? ? ? [lock unlock];????
????????}else {????
? ? ? ? ? ? ? ? NSLog(@"鎖不可操作");
? ? ? ? }
? ? ? ? NSDate *date = [[NSDate alloc] initWithTimeIntervalSinceNow:3];
? ? ? ? if ([lock lockBeforeDate:date]) { // 嘗試在未來的3s內(nèi)獲取鎖,并阻塞該線程,如果3s內(nèi)獲取不到,恢復(fù)線程,返回NO,不會阻塞該線程
? ? ? ? ? ? ? ? NSLog(@"沒有超時,獲得鎖");
? ? ? ? ? ? ? ? [lock unlock];
????????}else {
? ? ? ? ? ? ? ? NSLog(@"超時,沒有獲得鎖");
????????}
});
// 線程1 開始 ——》 鎖不可操作 ——》線程1 結(jié)束 ——》沒有超時,獲得鎖
NSLock 是Cocoa提供給我們最基本的鎖對象,也是最經(jīng)常使用的
源碼:
@protocol NSLocking
- (void)lock;
- (void)unlock;
@end
@interface NSLock: NSObject<NSLocking> {
@private
? ? ? ? void *_priv;
}
- (BOOL)tryLock; // 嘗試加鎖,如果鎖不可用(已經(jīng)被鎖住),則不會阻塞線程,并返回NO
-(BOOL)lockBeforeDate:(NSDate *)limit; // 在指定Date之前嘗試加鎖,如果在指定時間之前都不能加鎖,則返回NO
@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);
@end
NSRecursiveLock遞歸鎖:
NSRecursiveLock實際上定義的是一個遞歸鎖,這個鎖可以被同一個線程多次請求,而不會引起死鎖。主要是用在循環(huán)或遞歸操作中。
NSLock *lock = [[NSLock alloc] init];
// NSRecursiveLock *lock = [[NSRecursiveLock alloc] init];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
static void (^RecursiveMethod)(int);
? ? ? ? RecursiveMethod = ^(int value) {
? ? ? ? ? ? ? ? [lock lock];
? ? ? ? ? ? ? ? if (value > 0) {
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"value = %d", value);
? ? ? ? ? ? ? ? ? ? ? ? sleep(1);
? ? ? ? ? ? ? ? ? ? ? ? RecursiveMethod(value-1);
????????????????}
? ? ? ? ? ? ? ? [lock unlock];
????????}
? ? ? ? RecursiveMethod(5);
});
這段代碼是一個典型的死鎖情況。在我們的線程中,RecursiveMethod是遞歸調(diào)用的。所以每次進(jìn)入這個block時,都會去加一次鎖,而從第二次開始,有鎖已經(jīng)被使用了且沒有解鎖,所以它需要等待鎖被解除,這樣就導(dǎo)致了死鎖,線程被阻塞了。
在這種情況下,我們就可以使用NSRecursiveLock。它允許同一個線程多次加鎖,而不會造成死鎖。遞歸鎖會跟蹤它被lock的次數(shù)。每次成功的lock都必須平衡調(diào)用unlock操作。只有所有達(dá)到這種平衡,鎖最后才能被釋放。
將NSLock替換為NSRecursiveLock,代碼才能正常工作
@interface NSRecursiveLock : NSObject <NSLocking> {
@private
? ? ? ? void *_priv;
}
- (BOOL)tryLock;
- (BOOL)lockBeforDate:(Date *)limit;
@property (nullable, copt) NSString *name NS_AVAILABEL(10_5,, 2_0);
@end
NSConditionLock條件鎖NSMutableArray *books = [NSMutableArray array];
NSInteger HAS_BOOK = 1;
NSInteger NO_BOOK = 0;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? while (1) {
? ? ? ? ? ? ? ? [lock lockWhenCondition:NO_BOOK];
? ? ? ? ? ? ? ? [books addObject:[[Book alloc] initWithName:@"圍城"]];
? ? ? ? ? ? ? ? NSLog(@"1 total books:%zd", books.count);
? ? ? ? ? ? ? ? [lock unlockWithCondition:HAS_BOOK];
? ? ? ? ? ? ? ? sleep(1);
????????}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? while(1) {
? ? ? ? ? ? ? ? NSLog(@"2 wait for lend a book");
? ? ? ? ? ? ? ?[lock lockWhenCondition:HAS_BOOK];
? ? ? ? ? ? ? ? [books removeObjectAtIndex:0];
? ? ? ? ? ? ? ? NSLog(@"3 lend a book");
? ? ? ? ? ? ? ? [lock unlockWithCondition:NO_BOOK];
????????}
});
// 2 -> 1 -> 3 -> 2 -> 1 -> 3 -> 2 -> 1 -> 3
在線程1中的加鎖使用了lock,是不需要條件的,所以順利的就鎖住了,但在unlock時,使用了一個整型條件,它可以開啟其他線程中正在等待這把鑰匙的鎖。而線程2則只需要一把標(biāo)識為2的鑰匙,所以當(dāng)線程1循環(huán)到最后一次的時候,才最終打開可2中的阻塞。NSConditionLock跟其他鎖一樣,是需要lock與unlock對應(yīng)的,只是lock。lockWhenCondition與unlock、unlockWithCondition是可以隨意組合的。
當(dāng)使用多線程的時候,有時一把只會lock和unlock的鎖,不能完全滿足我們的需求。因為普通的鎖只關(guān)心鎖與不鎖,而不在乎用什么鑰匙才能開鎖,而我們在處理資源共享的時候,多數(shù)請求是只滿足一定的條件下才能打開這把鎖。
NSCondition
NSCondition *condition = [[NSCondition alloc] init];
NSMutableArray *books = [NSMutableArray array];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? while(1) {
? ? ? ? ? ? ? ? [condition lock];
? ? ? ? ? ? ? ? if [books count] == 0) {
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"1 ?have no book ");
? ? ? ? ? ? ? ? ? ? ? ? [condition wait];
????????????????}
? ? ? ? ? ? ? ? [books removeObjectAtIndex:0];
? ? ? ? ? ? ? ? NSLog(@"2 ?lend a book ");
? ? ? ? ? ? ? ? [condition unlock];
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? while(1) {
? ? ? ? ? ? ? ? [condition lock];
? ? ? ? ? ? ? ? [books addObject:[[Books alloc] initWithName:@"四季"] ];
? ? ? ? ? ? ? ? NSLog(@"3 ?borrow a book, total books :%zd ", books.count);
? ? ? ? ? ? ? ? [condition signal];
? ? ? ? ? ? ? ? [condition unlock];
? ? ? ? ? ? ? ?sleep(1);
}
});
// 1 -> 3 -> 2 -> 1 -> 3 -> 2 -> 1 -> 3 -> 2
一種最基本的條件鎖,受控控制線程wait和signal
1、[condition lock]:一般用于多線程同時訪問、修改同一個數(shù)據(jù)源時,保證在同一時間內(nèi)數(shù)據(jù)只被訪問、修改一次,其他線程的命令需要lock外等待,直到unlock,才可訪問。
2、[condition unlock]:與lock同理。
3、[condition wait]:讓當(dāng)前線程處于等待狀態(tài)。
4、[condition signal]:CPU發(fā)信號告訴線程不用等待,可以繼續(xù)執(zhí)行
pthread_mutex
__block pthread_mutex_t pLock;
pthread_mutex_init(&pLock, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? pthread_mutex_lock(&pLock);
? ? ? ? NSLog(@"線程 1 開始");
? ? ? ? sleep(2);
? ? ? ? NSLog(@"線程 1 結(jié)束");
? ? ? ? pthread_mutex_unlock(&pLock);
}),
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? sleep(1);
? ? ? ? pthread_mutex_lock(&pLock);
? ? ? ? NSLog(@"線程 2");
? ? ? ? pthread_mutex_unlock(&pLock);
});
// 線程1 開始 ——》 線程 1 結(jié)束 ? ?——》線程 2
c語言下多線程加鎖方式。
1、pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);初始化鎖變量mutex,attr為鎖屬性,NULL值為默認(rèn)屬性。
2、pthread_mutex_look(pthread_mutex_t *mutex):加鎖
3、pthread_mutex_tylock(pthread_mutex_t *mutex);加鎖,但與2不同的是:當(dāng)鎖已經(jīng)在使用的使用,返回EBUSY,而不是掛機等待。
4、pthread_mutex_unlock(pthread_mutex_t *mutex);釋放鎖
5、pthread_mutex_destroy(pthread_mutex_t ** mutex);使用完后釋放
pthread_mutex(recursive)
__block pthread_mutex_t pLock;
// pthread_mutex_init(&pLock, NULL); 初始化會出現(xiàn)死鎖
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&pLock, &attr);
pthread_mutexattr_destroy(&attr);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? static void (^RecursiveMethod)(int);
? ? ? ? RecursiveMethod = ^(int value) {
? ? ? ? ? ? ? ? pthread_mutex_lock(&pLock);
? ? ? ? ? ? ? ? if (value > 0 ) {
? ? ? ? ? ? ? ? ? ? ? ? NSLog(@"value = %zd", value);
? ? ? ? ? ? ? ? ? ? ? ? sleep(1) ? ?;
? ? ? ? ? ? ? ? ? ? ? ? RecursiveMethod(value - 1);
????????????????}
? ? ? ? ? ? ? ? pthread_mutex_unlock(&pLock);
????????};
? ? ? ? RecursiveMethod(5);
});
這是pthread_mutex為了防止在遞歸的情況下出現(xiàn)死鎖而出現(xiàn)的遞歸鎖。作用和NSRecursiveLock遞歸鎖類似。
OSSpinLock
__block OSSpinLock spLock = OS_SPINLOCK_INIT;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? OSSpinLockLock(&spLock);
? ? ? ? NSLog(@"線程 1 開始");
? ? ? ? sleep(1);
? ? ? ? NSLog(@"線程 1 結(jié)束");
? ? ? ? OSSpinLockUnlock(&spLock);
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
? ? ? ? OSSpinLockLock(&spLock);
? ? ? ? sleep(1);
? ? ? ? NSLog("線程 2");
? ? ? ? QSSpinLockUnlock(&spLock);
});
OSSpinLock:自旋鎖,性能最高的鎖。原理很簡單,就是一直do while 忙等。它的缺點是當(dāng)?shù)却龝r會消耗大量的CPU資源,所以它不適合較長時間的事務(wù)。而且OSSpinLock目前已經(jīng)不安全,慎用。
鎖之間性能對比:
OSSpinLock和dispatch_semaphore的效率遠(yuǎn)遠(yuǎn)高于其他。
@synchronized和NSCondition效率較差
OSSpinLock現(xiàn)在已經(jīng)不安全。