在開始之前,先理解以下幾點(diǎn)
一、進(jìn)程和線程的區(qū)別和聯(lián)系:
1、地址資源:進(jìn)程有自己的內(nèi)存地址,進(jìn)程內(nèi)的線程可以共享進(jìn)程的內(nèi)存地址
2、資源分配和調(diào)度:進(jìn)程是系統(tǒng)進(jìn)行資源分配和擁有的基本單位,同一個進(jìn)程內(nèi)的線程可共享進(jìn)程的資源
3、線程是CPU調(diào)度的基本單位。
4、二者都可以并發(fā)執(zhí)行
二、多線程的意義
優(yōu)點(diǎn):
1、提高資源(CPU和內(nèi)存)利用率
2、提高程序的執(zhí)行效率
3、線程上的任務(wù)執(zhí)行完后,線程會自動銷毀
缺點(diǎn):
1、開辟線程需要占用一定的內(nèi)存空間(默認(rèn)每個線程占用512kb)
2、線程越多,CPU調(diào)度的開銷越大
三、多線程原理
多線程的并發(fā)執(zhí)行其實并不是同時執(zhí)行,而是CPU在不同的線程間頻繁切換,達(dá)到的“偽同時”效果。這是由于每一個分得CPU的任務(wù)都會有一個時間片,它執(zhí)行完時間片的時間,CPU就不屬于它們了,要等待再次分配。
拋出問題
問題1、主線程是做什么的?
答:主線程是iOS程序運(yùn)行后開辟的第一個線程,也叫UI線程,用來顯示/刷新UI界面和處理UI事件。
問題2、UI為什么要在主線程更新?
答:蘋果為了性能考慮,UIKit不是線程安全的,試想如果UI可以在子線程更新,那么如果有多個線程同時修改某個資源時將會出現(xiàn)很多莫名其妙的錯誤。
問題3、能不能把渲染放到子線程?怎么渲染
遺留問題。
線程和Runloop的關(guān)系
1、Runloop和線程是一一對應(yīng)的關(guān)系
2、Runloop是來管理線程的,當(dāng)線程的runloop被開啟后,線程執(zhí)行完任務(wù)會休眠,等下次有任務(wù)時再執(zhí)行任務(wù)。
3、線程在第一次創(chuàng)建是被開啟,在線程結(jié)束時銷毀
3、Runloop在子線程中默認(rèn)不開啟,需要手動操作才能開啟。注意NSTImer
線程池工作原理
系統(tǒng)中的線程屬于寶貴資源,合理運(yùn)用線程池,可以減少創(chuàng)建和銷毀線程的次數(shù),使得每個線程可以重復(fù)利用。
- corePoolSize:線程池基本大?。ê诵木€程數(shù)量)
- maximumPoolSize:線程池允許的最大線程數(shù)
- keepAliveTime:當(dāng)線程池的線程數(shù)量大于corePoolSize時,空閑線程的最大存活時間
- workQueue:存放任務(wù)的工作隊列
- handler:超出線程范圍和隊列容量的任務(wù)的處理程序
線程池的工作流程:當(dāng)提交一個任務(wù)時,線程池會做出如下判斷
1、如果正在運(yùn)行的線程數(shù)量小于corePoolSize,馬上創(chuàng)建線程執(zhí)行任務(wù)。
2、如果正在運(yùn)行的線程數(shù)量大于等于corePoolSize,則放入工作隊列中。
3、如果不巧,工作隊列也滿了,而且正在運(yùn)行的線程數(shù)量小于maximumPoolSize,那么還是要創(chuàng)建線程立刻運(yùn)行這個任務(wù)。
4、如果正在運(yùn)行的線程數(shù)量大于等于maximumPoolSize,則交給飽和策略處理。
如圖所示

當(dāng)線程池的線程執(zhí)行完任務(wù)以后,會從工作隊列取下一個任務(wù)執(zhí)行。
當(dāng)一個線程空閑,且正在運(yùn)行的線程數(shù)大于corePoolSize,則計時超過keepAliveTime線程就會被kill掉,所以當(dāng)線程池中任務(wù)都完成后,它最終會縮小到corePoolSize大小。
飽和策略
當(dāng)任務(wù)提交線程池失敗時,即當(dāng)前提交的任務(wù)數(shù)超過maxmumPoolSize與workQueue之和時,就會交給飽和策略處理。
飽和策略有四種,AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。
| 策略 | Description |
|---|---|
| AbortPolicy | 中止策略,屬于默認(rèn)的飽和策略,該策略將拋出未檢查的RejectedExecutionException。調(diào)用者可以捕獲這個異常,然后根據(jù)需要編寫自己的處理代碼。 |
| DiscardPolicy | 拋棄策略,當(dāng)新提交的任務(wù)無法保存到隊列中等待執(zhí)行時,“拋棄(Discard)”策略會悄悄拋棄該任務(wù)。 |
| DiscardOldestPolicy | 拋棄下一個將被執(zhí)行的任務(wù),然后嘗試重新提交新的任務(wù)。(如果工作隊列是一個優(yōu)先隊列,那么“拋棄最舊的”策略將導(dǎo)致拋棄優(yōu)先級最高的任務(wù),因此最好不要將“拋棄最舊的”飽和策略和優(yōu)先隊列放在一起使用。) |
| CallerRunsPolicy | 調(diào)用者策略,策略實現(xiàn)了一種調(diào)度機(jī)制,該策略既不會拋棄任務(wù),也不會拋出異常,而是將某些任務(wù)回退到調(diào)用者,從而降低新任務(wù)的流量。它不會在線程池的某個線程中執(zhí)行新提交的任務(wù),而是在一個調(diào)用了execute的線程中執(zhí)行該任務(wù)。如果采用有界隊列和“調(diào)用者運(yùn)行”飽和策略,當(dāng)線程池中的所有線程都被占用,并且工作隊列被填滿后,下一個任務(wù)會在調(diào)用execute時在主線程中執(zhí)行。由于執(zhí)行任務(wù)需要一定的時間,因此主線程至少在一段時間內(nèi)不能提交任何任務(wù),從而使得工作者線程有時間來處理完正在執(zhí)行的任務(wù)。在此期間,主線程不會調(diào)用accept,因此到達(dá)的請求將被保存在TCP層的隊列中而不是在應(yīng)用程序的隊列中。如果持續(xù)過載,那么TCP層將最終發(fā)現(xiàn)它的請求隊列被填滿,因此同樣會開始拋棄請求。當(dāng)服務(wù)器過載時,這種過載情況會逐漸向外蔓延開來——從線程池到工作隊列到應(yīng)用程序再到TCP層,最終達(dá)到客戶端,導(dǎo)致服務(wù)器在高負(fù)載下實現(xiàn)一種平緩的性能降低。 |
線程的生命周期
線程生命周期的每一步如下所示:
- 1、新建:實例化線程對象
- 2、就緒:調(diào)用start將線程加入可調(diào)度線程池,等待CPU調(diào)度(分配時間片)。
- 3、運(yùn)行:CPU從可調(diào)度線程池中分配時間片給線程,線程在未執(zhí)行完畢情況下可能會在就緒和運(yùn)行之間不斷切換,程序員無法干預(yù)。
- 4、阻塞:線程有時會因為同步、鎖、sleep等方式阻塞。
- 5、死亡:分為正常死亡(線程結(jié)束)和非正常死亡(線程終止)。

多線程的四種技術(shù)方案
多線程有四種技術(shù)方案分別是pthread、NSThread、GCD和NSOperation,如下圖所示

我們一般使用比較多的是GCD,因為開發(fā)者只需要告訴 GCD 想要執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼,但這也是GCD的不夠靈活的地方,我們無法監(jiān)控線程的各個狀態(tài),這也是很多大框架中使用NSOperation的原因,NSOperation相比GCD更加靈活,開發(fā)者可以通過KVO監(jiān)測Operation的狀態(tài),自定義NSOperation等。