iOS必經(jīng)之路:第一篇 多線程

一摹芙、進(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)程。

多線程

  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ù)共享

三议惰、任務(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市骂远,隨后出現(xiàn)的幾起案子囚霸,更是在濱河造成了極大的恐慌,老刑警劉巖激才,帶你破解...
    沈念sama閱讀 221,430評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拓型,死亡現(xiàn)場離奇詭異,居然都是意外死亡瘸恼,警方通過查閱死者的電腦和手機(jī)劣挫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,406評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來东帅,“玉大人压固,你說我怎么就攤上這事】勘眨” “怎么了帐我?”我有些...
    開封第一講書人閱讀 167,834評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長愧膀。 經(jīng)常有香客問我拦键,道長,這世上最難降的妖魔是什么檩淋? 我笑而不...
    開封第一講書人閱讀 59,543評論 1 296
  • 正文 為了忘掉前任矿咕,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘碳柱。我一直安慰自己捡絮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,547評論 6 397
  • 文/花漫 我一把揭開白布莲镣。 她就那樣靜靜地躺著福稳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪瑞侮。 梳的紋絲不亂的頭發(fā)上的圆,一...
    開封第一講書人閱讀 52,196評論 1 308
  • 那天,我揣著相機(jī)與錄音半火,去河邊找鬼越妈。 笑死,一個胖子當(dāng)著我的面吹牛钮糖,可吹牛的內(nèi)容都是我干的梅掠。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼店归,長吁一口氣:“原來是場噩夢啊……” “哼阎抒!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起消痛,我...
    開封第一講書人閱讀 39,671評論 0 276
  • 序言:老撾萬榮一對情侶失蹤且叁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后秩伞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體逞带,經(jīng)...
    沈念sama閱讀 46,221評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,303評論 3 340
  • 正文 我和宋清朗相戀三年纱新,在試婚紗的時候發(fā)現(xiàn)自己被綠了掰担。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,444評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡怒炸,死狀恐怖带饱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情阅羹,我是刑警寧澤勺疼,帶...
    沈念sama閱讀 36,134評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站捏鱼,受9級特大地震影響执庐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜导梆,卻給世界環(huán)境...
    茶點故事閱讀 41,810評論 3 333
  • 文/蒙蒙 一轨淌、第九天 我趴在偏房一處隱蔽的房頂上張望迂烁。 院中可真熱鬧,春花似錦递鹉、人聲如沸盟步。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,285評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽却盘。三九已至,卻和暖如春媳拴,著一層夾襖步出監(jiān)牢的瞬間黄橘,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,399評論 1 272
  • 我被黑心中介騙來泰國打工屈溉, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留塞关,地道東北人。 一個月前我還...
    沈念sama閱讀 48,837評論 3 376
  • 正文 我出身青樓子巾,卻偏偏與公主長得像帆赢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子砰左,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,455評論 2 359

推薦閱讀更多精彩內(nèi)容

  • 本文用來介紹 iOS 多線程中 GCD 的相關(guān)知識以及使用方法匿醒。這大概是史上最詳細(xì)场航、清晰的關(guān)于 GCD 的詳細(xì)講...
    花花世界的孤獨行者閱讀 505評論 0 1
  • 多線程的四種解決方案:pthread缠导,NSThread,GCD溉痢,NSOperation 一僻造、多線程的基本概念進(jìn)程:...
    陽明先生_X自主閱讀 473評論 0 3
  • ? 前言:這可能是史上最全面的一篇iOS 多線程博客了(王婆賣瓜一番??),從多線程的基本概念孩饼,進(jìn)程的概念髓削,引出i...
    阿餅six閱讀 850評論 2 11
  • 程序中同步和異步是什么意思?有什么區(qū)別镀娶? 解釋一:異步調(diào)用是通過使用單獨的線程執(zhí)行的立膛。原始線程啟動異步調(diào)用,異步調(diào)...
    風(fēng)繼續(xù)吹0閱讀 1,035評論 1 2
  • 1.NSTimer不準(zhǔn)時的原因:(1).RunLoop循環(huán)處理時間梯码,每次循環(huán)是固定時間宝泵,只有在這段時間才會去查看N...
    稻春閱讀 1,245評論 0 3