前言
iOS 底層第22天的學(xué)習(xí)。今天又要迎來一個新的篇章的學(xué)習(xí)。這個篇章就是多線程。
多線程引入
- 說到線程,肯定要說到另一個詞 — 進(jìn)程
進(jìn)程的基本概念
進(jìn)程: 是指在系統(tǒng)中正在運行的一個程序
每個進(jìn)程之間是獨?的,每個進(jìn)程均運?在其專?的且受保護(hù)的內(nèi)存空間內(nèi)
通過“活動監(jiān)視器”可以查看Mac系統(tǒng)中所開啟的進(jìn)程
線程的基本概念
線程是進(jìn)程的基本執(zhí)?單元,?個進(jìn)程的所有任務(wù)都在線程中執(zhí)?
進(jìn)程要想執(zhí)?任務(wù),必須得有線程,進(jìn)程?少要有?條線程
程序啟動會默認(rèn)開啟?條線程,這條線程被稱為主線程或UI線程
- 再來看一下蘋果官方文檔對
線程的解釋
翻譯??
線程 是應(yīng)用程序內(nèi)實現(xiàn)多個執(zhí)行路徑的一種相對輕量級的方式。在系統(tǒng)層面,程序并排運行,系統(tǒng)根據(jù)其需求和其他程序的需求為每個程序提供執(zhí)行時間。然而,在每個程序中,存在一個或多個執(zhí)行線程,這些線程可用于同時或幾乎同時執(zhí)行不同的任務(wù)。系統(tǒng)本身實際上管理這些執(zhí)行線程,安排它們在可用核心上運行,并根據(jù)需要先發(fā)制人地中斷它們,以允許其他線程運行。
線程與進(jìn)程的關(guān)系
地址空間:同?進(jìn)程的線程共享本進(jìn)程的地址空間,?進(jìn)程之間則是獨?的地址空間。資源擁有:同?進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu 等,但是進(jìn)程之間的資源是獨?的。
資源擁有:同?進(jìn)程內(nèi)的線程共享本進(jìn)程的資源如內(nèi)存、I/O、cpu 等,但是進(jìn)程之間的資源是獨?的。
1: ?個進(jìn)程崩潰后,在保護(hù)模式下不會對其他進(jìn)程產(chǎn)?影響,但是?個線程崩潰整個進(jìn)程都死掉。所以多進(jìn)程要?多線程健壯。
2: 進(jìn)程切換時,消耗的資源?,效率?。所以涉及到頻繁的切換時,使?線程要好于進(jìn)程。同樣如果要求同時進(jìn)?并且?要共享某些變量的并發(fā)操作,只能?線程不能?進(jìn)程
3: 執(zhí)?過程:每個獨?的進(jìn)程有?個程序運?的??、順序執(zhí)?序列和程序??。但是線程不能獨?執(zhí)?,必須依存在應(yīng)?程序中,由應(yīng)?程序提供多個線程執(zhí)?控制。
4: 線程是處理器調(diào)度的基本單位,但是進(jìn)程不是。
5: 線程沒有地址空間,線程包含在進(jìn)程地址空間中
線程成本
- 看下官方的這幅圖??

| 項目 | 大約成本 | 備注 |
|---|---|---|
| 內(nèi)核數(shù)據(jù)結(jié)構(gòu) | 大約1KB | 此內(nèi)存用于存儲線程數(shù)據(jù)結(jié)構(gòu)和屬性,其中大部分被分配為有線內(nèi)存,因此無法分頁到磁盤 |
| 堆??臻g | 512 KB(二次線程)8 MB(OS X 主線程)1 MB(iOS主線程) | 輔助線程允許的最小堆棧大小為16KB,堆棧大小必須是4KB的倍數(shù)。在線程創(chuàng)建時,此內(nèi)存的空間將放在您的進(jìn)程空間中,但在需要之前,才會創(chuàng)建與該內(nèi)存關(guān)聯(lián)的實際頁面。 |
| 創(chuàng)建時間 | 大約90微秒 | 此值反映了從創(chuàng)建線程的初始調(diào)用到線程的入口點例程開始執(zhí)行的時間。這些數(shù)字是通過分析基于英特爾的iMac上線程創(chuàng)建時生成的平均值和中位數(shù)確定的,iMac具有2GHz Core Duo處理器和運行OS X v10.5的1GB內(nèi)存。 |
多線程意義
- 看一下官方文檔對多線程的解釋

翻譯??
在應(yīng)用程序中具有多個線程提供了兩個非常重要的潛在優(yōu)勢:
- 多個線程可以提高應(yīng)用程序的感知響應(yīng)能力。
- 多線程可以提高應(yīng)用程序在多核系統(tǒng)上的實時性能。
如果您的應(yīng)用程序只有一個線程,則該線程必須完成所有工作。它必須響應(yīng)事件,更新應(yīng)用程序的窗口,并執(zhí)行實現(xiàn)應(yīng)用程序行為所需的所有計算。只有一個線程的問題在于它一次只能做一件事。那么,當(dāng)您的計算需要很長時間才能完成時會發(fā)生什么?當(dāng)您的代碼忙于計算所需的值時,應(yīng)用程序會停止響應(yīng)用戶事件并更新其窗口。如果這種行為持續(xù)足夠長的時間,用戶可能會認(rèn)為您的應(yīng)用程序被掛起,并試圖強制退出它。但是,如果您將自定義計算移到一個單獨的線程上,應(yīng)用程序的主線程將可以更及時地響應(yīng)用戶交互。
- 優(yōu)點
- 能適當(dāng)提?程序的執(zhí)?效率
- 能適當(dāng)提?資源的利?率(CPU,內(nèi)存)
- 線程上的任務(wù)執(zhí)?完成后,線程會?動銷毀
- 缺點
- 開啟線程需要占??定的內(nèi)存空間(默認(rèn)情況下,每?個線程都占512KB) 如果開啟?量的線程,會占??量的內(nèi)存空間,降低程序的性能
- 線程越多,CPU在調(diào)?線程上的開銷就越?
- 程序設(shè)計更加復(fù)雜,?如線程間的通信、多線程的數(shù)據(jù)共享
多線程原理

- 多線程并非真正意義上的并發(fā),只是因為
CPU在同一個時間點上調(diào)度線程的速度非常快,所以宏觀上就近似并行多線程了。 - 時間?的概念:
CPU在多個任務(wù)直接進(jìn)?快速的切換,這個時間間隔就是時間? - 單核
CPU- 同?時間,
CPU只能處理1個線程, 換?之,同?時間只有1個線程在執(zhí)?
- 同?時間,
- 多線程同時執(zhí)?:
- 是
CPU快速的在多個線程之間的切換,CPU調(diào)度線程的時間?夠快,就造成了多線程的“同時”執(zhí)?的效果
- 是
- 如果線程數(shù)?常多
-
CPU會在N個線程之間切換,消耗?量的CPU資源,每個線程被調(diào)度的次數(shù)會降低,線程的執(zhí)?效率降低
-
多線程技術(shù)方案
| 方案 | 簡介 | 語言 | 線程生命周期 | 使用頻率 |
|---|---|---|---|---|
| pthread | 一套通用 API ,使用難度大,跨平臺,可移值 | C | 手動管理 | 幾乎不用 |
| NSThread | 使用更加面向?qū)ο?,簡單易用,可直接操作對?/td> | OC | 手動管理 | 偶爾使用 |
| GCD | 旨在替代 NSThread 等技術(shù),充分利用設(shè)備多核 | C | 自動管理 | 經(jīng)常使用 |
| NSOperation | 底層基于 GCD,使用更加面向?qū)ο?/td> | OC | 自動管理 | 經(jīng)常使用 |
線程生命周期

- 代碼??
// 創(chuàng)建一個線程 New
NSThread *t = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// start
[t start]; // 進(jìn)入 runable
// 對 run 的實現(xiàn)
- (void)run{
NSLog(@"開始");
//下面的代碼的作用是判斷線程狀態(tài),可能因為下面的延時,阻塞會帶來當(dāng)前線程的一些影響
// sleep 模擬線程堵塞 Blocked
[NSThread sleepForTimeInterval:3];
for (NSInteger i = 0; i < 10; i++) {
// 判斷線程是否被取消
if ([NSThread currentThread].isCancelled) {
NSLog(@"%@被取消",self.t.name);
return;
}
// 開始 running
NSLog(@"%@ %zd", [NSThread currentThread], i);
//內(nèi)部取消線程
// 強制退出 - 當(dāng)某一個條件滿足,不希望線程繼續(xù)工作,直接殺死線程,退出
// 線程 Dead
if (i == 8) {
// 強制退出當(dāng)前所在線程!后續(xù)的所有代碼都不會執(zhí)行
[NSThread exit];
}
}
}
- 在線程生命周期里有個東西叫 線程池
- 那線程池是用來干什么的呢? 顧名思義 就是存放線程的嘛
-
那線程池調(diào)度線程的具體流程是什么樣的呢? 請看??
- 飽和策略如何處理
? AbortPolicy: 直接拋出RejectedExecutionExeception異常來阻?系統(tǒng)正常運?
? CallerRunsPolicy: 將任務(wù)回退到調(diào)?者
? DisOldestPolicy: 丟掉等待最久的任務(wù)
? DisCardPolicy: 直接丟棄任務(wù)
線程鎖
自旋鎖 vs 互斥鎖
- 自旋鎖 : 發(fā)現(xiàn)其他線程執(zhí)行 當(dāng)前線程 詢問 - 忙等 耗費性能比較高,一般用在大端
- 互斥鎖: 發(fā)現(xiàn)其他線程執(zhí)行 當(dāng)前線程 休眠 (就緒狀態(tài)) 一直在等打開 喚醒執(zhí)行.下面會重點講解
互斥鎖
- 保證鎖內(nèi)的代碼,同?時間,只有?條線程能夠執(zhí)?!
- 互斥鎖的鎖定范圍,應(yīng)該盡量?,鎖定范圍越?,效率越差!
- 互斥鎖參數(shù)
- 能夠加鎖的任意
NSObject對象 - 注意:鎖對象?定要保證所有的線程都能夠訪問
- 如果代碼中只有?個地?需要加鎖,?多都使?
self,這樣可以避免單獨再創(chuàng)建?個鎖對象
- 能夠加鎖的任意
多線程知識點補充
- 任務(wù)執(zhí)行速度的影響因素有哪些?
- 1.
CPU(單核 or 多核)- 2.任務(wù)的復(fù)雜度
- 3.優(yōu)先級的設(shè)定
- 4.線程狀態(tài)
- 優(yōu)先級翻轉(zhuǎn)
優(yōu)先級翻轉(zhuǎn)是當(dāng)一個高優(yōu)先級任務(wù)通過信號量機(jī)制訪問共享資源時,該信號量已被一低優(yōu)先級任務(wù)占有,因此造成高優(yōu)先級任務(wù)被許多具有較低優(yōu)先級任務(wù)阻塞,實時性難以得到保證。
- 那為什么高優(yōu)先級任務(wù)會被較低優(yōu)先級任務(wù)阻塞?
我們來解釋這下面這2個東西IO 密集型
IO密集型指的是系統(tǒng)的CPU性能相對硬盤、內(nèi)存要好很多,此時,系統(tǒng)運作,大部分的狀況是CPU在等I/O(硬盤/內(nèi)存) 的讀/寫操作,此時CPU Loading并不高。CPU 密集型
CPU密集型也叫計算密集型,指的是系統(tǒng)的硬盤、內(nèi)存性能相對CPU要好很多,此時,系統(tǒng)運作大部分的狀況是CPU Loading 100%,CPU要讀/寫I/O(硬盤/內(nèi)存),I/O在很短的時間就可以完成,而CPU還有許多運算要處理,CPU Loading 很高。
具體概念可參考這篇文章小結(jié)一下:
IO 密集型 -> 線程任務(wù)頻繁等待
CPU 密集型 -> 線程任務(wù)很少等待
因此就會導(dǎo)致IO密集型的線程 被餓死,這時CPU開始進(jìn)行調(diào)度把IO密集型的任務(wù)的優(yōu)先級臨時的給提高上去 -> 線程被執(zhí)行的可能性提高, 就導(dǎo)致了優(yōu)先級翻轉(zhuǎn)優(yōu)先級因素
- 用戶指定
- 等待的頻繁的
- 任務(wù)長時間不執(zhí)行
- atomic 與 nonatomic 的區(qū)別
atomic
是原子屬性,是為多線程開發(fā)準(zhǔn)備的,是默認(rèn)屬性!
僅僅在屬性的setter方法中,增加了鎖(自旋鎖),能夠保證同一時間,只有一條線程對屬性進(jìn)行寫操作 , 同一時間 單(線程)寫多(線程)讀的線程處理技術(shù)。線程安全,需要消耗?量的資源
- 底層源碼??
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
// .....
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
// 添加一個自旋鎖
spinlock_t& slotlock = PropertyLocks[slot]; 、
slotlock.lock();
oldValue = *slot;
*slot = newValue;
slotlock.unlock();
}
- 從源碼可知
atomic只是一個判斷的標(biāo)識
nonatomic
是非原子屬性
沒有鎖!性能高!?線程安全,適合內(nèi)存?的移動設(shè)
- 小結(jié):
iOS開發(fā)建議
所有屬性都聲明為nonatomic
盡量避免多線程搶奪同?塊資源
盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減?移動客戶端的壓力
總結(jié)
- 今天分享的多線程篇章概念性的東西比較多:
- 什么線程,線程和進(jìn)程區(qū)別
- 多線程的原理:它并非真正意義上的并發(fā),只是因為
CPU在同一個時間點上調(diào)度線程的速度非常快,所以宏觀上就近似并行多線程了 - 線程生命周期 從
new -> start -> runable -> running -> blocked -> Dead - 最后也補充幾個問題,擴(kuò)展了知識面。
- 預(yù)告:下次要學(xué)習(xí)的就是
iOS多線程里用的最多的GCD底層的探索


