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

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

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

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