進(jìn)程
- 進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序;
- 每個(gè)進(jìn)程之間是相互獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專用的且受保護(hù)的內(nèi)存空間內(nèi);
線程
- 線程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程的所有任務(wù)都是在線程中執(zhí)行的;
- 進(jìn)程要想執(zhí)行任務(wù),必須的有線程,一個(gè)進(jìn)程進(jìn)程至少要有一條線程;
- APP應(yīng)用程序啟動(dòng)會(huì)默認(rèn)開啟一條線程,這條線程被稱為 主線程 或者 UI線程;
進(jìn)程與線程之間的關(guān)系
- 進(jìn)程之間的地址空間是相互獨(dú)立,不能交叉訪問,同一個(gè)進(jìn)程內(nèi)的線程共享本進(jìn)程的地址空間;
- 進(jìn)程之間的資源是相互獨(dú)立的,同一個(gè)進(jìn)程內(nèi)的線程共享本進(jìn)程的資源;
- 兩個(gè)之間的關(guān)系就相當(dāng)于工廠與流水線的關(guān)系,工廠與工廠之間是相互獨(dú)立的,而工廠中的流水線是共享工廠的資源的,即進(jìn)程相當(dāng)于一個(gè)工廠,線程相當(dāng)于工廠中的一條流水線;
線程與RunLoop之間的關(guān)系
- RunLoop與線程是一一對(duì)應(yīng)的,其保存在一個(gè)全局的字典當(dāng)中;
- RunLoop是來管理線程的,當(dāng)線程的runloop被開啟后,線程會(huì)在執(zhí)行完任務(wù)后進(jìn)入休 眠狀態(tài),有了任務(wù)才會(huì)被喚醒去執(zhí)行任務(wù);
- 對(duì)于主線程來說,RunLoop在程序一啟動(dòng)就默認(rèn)創(chuàng)建好了,在線程結(jié)束時(shí)被銷毀;
- 對(duì)于子線程來說,RunLoop不會(huì)默認(rèn)創(chuàng)建, 其在第一次獲取時(shí)被創(chuàng)建,所以在子線程用定時(shí)器要注意:確保子線程的RunLoop被創(chuàng)建,不然定時(shí)器不會(huì)回調(diào);
多線程
- iOS中的多線程同時(shí)執(zhí)行的本質(zhì)是:
CPU在多個(gè)任務(wù)之間進(jìn)行快速的切換,由于CPU調(diào)度線程的時(shí)間足夠快,就造成了多線程的“同時(shí)”執(zhí)行的假象,讓我們認(rèn)為多個(gè)線程在同時(shí)執(zhí)行任務(wù),其中切換的時(shí)間間隔就是時(shí)間片;
多線程的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
- 能適當(dāng)提高應(yīng)用程序的執(zhí)行效率;
- 能適當(dāng)提高系統(tǒng)資源的利用率,如CPU、內(nèi)存;
- 線程上的任務(wù)執(zhí)行完成后,線程會(huì)自動(dòng)銷毀;
缺點(diǎn):
- 開啟線程需要占用一定的內(nèi)存空間,默認(rèn)情況下,每一個(gè)線程占用512KB,如果開啟大量線程,會(huì)占用大量的內(nèi)存空間,降低程序的性能;
- 線程越多,CPU在調(diào)用線程上的開銷就越大;
- 程序設(shè)計(jì)更加復(fù)雜,比如線程間的通信,多線程的數(shù)據(jù)共享;
多線程的生命周期
線程的生命周期通常分為5個(gè)部分:創(chuàng)建 -- 就緒 -- 運(yùn)行 -- 阻塞 -- 死亡;其狀態(tài)之間的切換如下圖所示:

Snip20210319_208.png
- 創(chuàng)建:即實(shí)例化線程對(duì)象;
- 就緒:線程對(duì)象調(diào)用start方法,將線程對(duì)象加入可調(diào)度線程池,等待CPU的調(diào)用,即調(diào)用start方法,并不會(huì)立即執(zhí)行,而是進(jìn)入到就緒狀態(tài),需要等待一段時(shí)間,經(jīng)CPU調(diào)度后才執(zhí)行;
- 運(yùn)行:CPU負(fù)責(zé)從可調(diào)度線程池中調(diào)度線程然后執(zhí)行,在線程執(zhí)行完成之前,其狀態(tài)可能會(huì)在就緒和運(yùn)行之間來回切換,這個(gè)變化是由CPU負(fù)責(zé),開發(fā)人員不能干預(yù);
- 阻塞:當(dāng)滿足某個(gè)預(yù)定條件時(shí),可以讓線程休眠,即sleep,或者使用同步鎖,阻塞線程執(zhí)行,會(huì)將線程從可調(diào)度線程池中移除,當(dāng)線程解除sleep時(shí)/獲取到鎖,會(huì)重新將線程加入到可調(diào)度線程池中。下面關(guān)于休眠的時(shí)間設(shè)置,都是NSThread的;
- 死亡:分為兩種情況
- 正常死亡,即線程執(zhí)行完畢;
- 非正常死亡,即當(dāng)滿足某個(gè)條件后,在線程內(nèi)部(或者主線程中)終止執(zhí)行(調(diào)用exit方法等退出)
- 簡(jiǎn)要說明,就是處于運(yùn)行中的線程擁有一段可以執(zhí)行的時(shí)間(稱為時(shí)間片),如果時(shí)間片用盡,線程就會(huì)進(jìn)入就緒狀態(tài)隊(duì)列,如果時(shí)間片沒有用盡,且需要開始等待某事件,就會(huì)進(jìn)入阻塞狀態(tài)隊(duì)列;等待事件發(fā)生后,線程又會(huì)重新進(jìn)入就緒狀態(tài)隊(duì)列;
- 每當(dāng)一個(gè)線程離開運(yùn)行,即執(zhí)行完畢或者強(qiáng)制退出后,會(huì)重新從就緒狀態(tài)隊(duì)列中選擇一個(gè)線程繼續(xù)執(zhí)行;
- 線程的exit和cancel說明
- exit:一旦強(qiáng)行終止線程,后續(xù)的所有代碼都不會(huì)執(zhí)行;
- cancel:取消當(dāng)前線程,但是不能取消正在執(zhí)行的線程;
線程池的工作原理
- 其工作原理如下圖所示:

Snip20210319_210.png
- 首先線程池有兩個(gè)重要參數(shù)分別為:corePoolSize和maximumPoolSize;
- corePoolSize表示核心線程池能創(chuàng)建核心線程的最大數(shù)量;
- maximumPoolSize表示線程池能創(chuàng)建線程的最大數(shù)量;核心線程池包含在線程池中。
- 【第一步】:當(dāng)有任務(wù)提交過來,首先判斷核心線程池是否已滿(corePoolSize)
- 未滿,創(chuàng)建核心線程執(zhí)行任務(wù);
- 已滿,進(jìn)入第二步;
- 【第二步】:判斷工作隊(duì)列是否已滿
- 未滿,將任務(wù)添加到工作隊(duì)列中;
- 已滿,進(jìn)入第三步;
- 【第三步】:判斷線程池是否已滿(maximumPoolSize)
- 未滿:創(chuàng)建非核心線程執(zhí)行任務(wù);
- 已滿:進(jìn)入第四步;
- 【第四步】:執(zhí)行飽和策略,
- 通常有以下四種飽和策略:
- AbortPolicy(拋出一個(gè)異常,默認(rèn)的)
- DiscardPolicy(新提交的任務(wù)直接被拋棄)
- DiscardOldestPolicy(丟棄隊(duì)列里最老的任務(wù),將當(dāng)前這個(gè)任務(wù)繼續(xù)提交給線程池)
- CallerRunsPolicy(交給線程池調(diào)用所在的線程進(jìn)行處理,即將某些任務(wù)回退到調(diào)用者)
iOS中多線程的實(shí)現(xiàn)方案
- 主要有四種分別為:
pthread,NSThread,GCD,NSOperation

Snip20210319_211.png
- 下面通過代碼案例分別演示這四種多線程方案的實(shí)現(xiàn):
【pthread】
//線程回調(diào)函數(shù)
void * pthreadTest(){
NSLog(@"===>%@", [NSThread currentThread]);
return NULL;
}
- (void)viewDidLoad {
[super viewDidLoad];
pthread_t threadId = NULL;
//c字符串
char *cString = "HelloCode";
//創(chuàng)建一個(gè)字線程
int result = pthread_create(&threadId,NULL,pthreadTest,cString);
if (result == 0) {
NSLog(@"pthread 創(chuàng)建成功");
}else{
NSLog(@"pthread 創(chuàng)建失敗");
}
}
- 需導(dǎo)入
#import <pthread.h>

Snip20210319_212.png
【NSThread】

Snip20210319_214.png
【GCD】

Snip20210319_215.png
【NSOperation】

Snip20210319_217.png
線程安全問題
- 在
同一時(shí)刻有多條子線程共同訪問共享資源數(shù)據(jù),容易引發(fā)數(shù)據(jù)錯(cuò)亂和數(shù)據(jù)安全問題,有以下兩種解決方案:- 互斥鎖(即同步鎖):@synchronized
- 自旋鎖;
互斥鎖
- 用于保護(hù)臨界區(qū),確保同一時(shí)間,只有一條線程能夠訪問執(zhí)行;
- 加了互斥鎖的代碼,當(dāng)新線程訪問時(shí),如果發(fā)現(xiàn)有其他線程正在訪問共享資源,新線程就會(huì)進(jìn)入休眠狀態(tài);
- 互斥鎖的鎖定范圍,應(yīng)該盡量小,鎖定范圍越大,效率越差;
- 能夠加鎖的任意的NSObject對(duì)象;
- 鎖對(duì)象一定要保證所有的線程都能夠訪問;
自旋鎖
- 自旋鎖與互斥鎖類似,但它不是通過休眠使線程阻塞,而是在獲取鎖之前一直處于忙等(即原地打轉(zhuǎn),稱為自旋)阻塞狀態(tài);
- 鎖持有的時(shí)間短,且線程不希望在重新調(diào)度上花太多成本時(shí),就需要使用自旋鎖,屬性修飾符atomic,本身就有一把自旋鎖;
- 加入了自旋鎖,當(dāng)新線程訪問代碼時(shí),如果發(fā)現(xiàn)有其他線程正在鎖定代碼,新線程會(huì)用死循環(huán)的方法,一直等待鎖定的代碼執(zhí)行完成,即不停的嘗試執(zhí)行代碼,比較消耗性能;
【面試題】:自旋鎖 vs 互斥鎖
- 相同點(diǎn):在同一時(shí)間,保證了只有一條線程訪問共享資源;
- 不同點(diǎn):
- 互斥鎖:發(fā)現(xiàn)其他線程執(zhí)行,當(dāng)前線程 休眠(即就緒狀態(tài)),進(jìn)入等待執(zhí)行,即掛起,一直等其他線程打開之后,然后喚醒執(zhí)行;
- 自旋鎖:發(fā)現(xiàn)其他線程執(zhí)行,當(dāng)前線程一直詢問(即一直訪問),處于忙等狀態(tài),耗費(fèi)的性能比較高;
- 場(chǎng)景:根據(jù)任務(wù)復(fù)雜度區(qū)分,使用不同的鎖,但判斷不全時(shí),更多是使用互斥鎖去處理
當(dāng)前的任務(wù)狀態(tài)比較短小精悍時(shí),用自旋鎖,反之用互斥鎖;