[TOC]
一或粮、 線程和進程
1.1 線程的定義
- 線程是進程的
基本執(zhí)行單元
塔淤,一個進程的所有任務倒在線程中執(zhí)行 - 進程要想執(zhí)行任務,必須得有線程针史,進程至少要有一條線程
- 程序啟動會默認開啟一條線程贡定,這條線程被稱為主線程或 UI 線程
1.2 進程的定義
- 進程是指在系統(tǒng)中正在運行的一個應用程序
- 每個進程之間是獨立的赋访,每個進程均運行在其專用的且受保護的內存
1.3 iOS開發(fā)為什么是單進程的
- 沙盒-單進程數據更加隱私、安全
- 進程切換缓待,消耗資源大
1.4 線程和隊列的關系
- 線程和隊列是兩個完全獨立的概念蚓耽,線程是由系統(tǒng)分配、系統(tǒng)調度旋炒,隊列是用來管理任務步悠,開發(fā)者可以管理任務的串行、并行瘫镇、優(yōu)先級鼎兽、先后順序;
- 開發(fā)者管理任務隊列铣除,系統(tǒng)根據隊列的屬性谚咬,來給隊列中的任務分配線程;
- 隊列是提供給開發(fā)者使用的抽象概念通孽,線程是由系統(tǒng)調度的真實對象序宦,開發(fā)者可以通過管理隊列來指定系統(tǒng)分配、調度線程的數量背苦、順序等互捌。
- 任務執(zhí)行速度:CPU、線程行剂、隊列秕噪、任務的復雜度、任務的優(yōu)先級
- 大量的臨時變量:使用 autorealease 處理
1.5 線程和進程的關系和區(qū)別
- 地址空間:同一進程的線程共享本進程的地址空間厚宰,而進程之間則是獨立的地址空間
- 資源擁有:同一進程內的線程共享進程的資源腌巾,如內存遂填、I/O、cpu等澈蝙,但是進程之間的資源是獨立的
- 健壯性:一個進程崩潰后吓坚,在保護模式下不會對其他進程產生影響,但是一個線程崩潰整個進程都會死掉灯荧。多以多進程要比多線程健壯
- 并發(fā)操作:進程切換時礁击,消耗的資源大,效率高逗载。所以涉及到頻繁的切換時哆窿,使用線程要好于進程。同樣如果要求同時進行并且又要共享某些變量的并發(fā)操作厉斟,只能使用線程不能用進程
- 執(zhí)行過程:每個獨立的進程有一個程序運行的入口挚躯、順序執(zhí)行序列和程序入口。但是線程不能獨立執(zhí)行擦秽,必須依存于應用程序中码荔,由應用程序提供多個線程執(zhí)行控制
- 調度單位:線程是處理器調度的基本單位,但進程不是
1.6 進程和線程與堆和棧的關系
- 進程的地址空間
每個進程的地址空間是獨立的
号涯,是操作系統(tǒng)把物理內存映射到進程的虛擬地址上目胡。
進程只能訪問自己的地址空間锯七,不能訪問別的進程的地址空間链快。
通俗的理解:如果有a.exe和b.exe同時在系統(tǒng)中運行,那么a和b都可以有0x00000000-0xffffffff
的虛擬地址空間(不考慮操作系統(tǒng)占用等因素)眉尸。
假如a b都有1個變量在地址0x12345678
處域蜗,看起來地址一樣,實際上在物理內存中不是一個地方噪猾。 -
進程是線程的容器
霉祸,windows調度運行的單位是線程,一個進程至少有1個主線程袱蜡。一個進程內所有的線程都處于同一個虛擬地址空間丝蹭。 -
進程初始化的時候,系統(tǒng)會在進程的地址空間中創(chuàng)建一個堆坪蚁,叫進程默認堆
奔穿。進程中所有的線程共用這一個堆。當然敏晤,可以增加1個或幾個堆贱田,給不同的線程共同使用或單獨使用。 - 創(chuàng)建線程的時候嘴脾,系統(tǒng)會在進程的地址空間中分配1塊內存給線程棧男摧,通常是1MB。
線程棧是獨立的,不共享
耗拓。iOS中是512KB
1.7 補充內存相關知識
內存五大分區(qū)
1拇颅、棧區(qū)(stack
):由編譯器自動分配釋放,存放函數的參數值乔询,局部變量的值等蔬蕊。其操作方式類似于數據結構中的棧。
2哥谷、堆區(qū)(heap)
:一般由程序員分配釋放岸夯,若程序員不釋放,程序結束時可能由OS回收们妥。注意它與數據結構中的堆是兩回事猜扮,分配方式類似于鏈表。new出來的放在這里监婶。
3旅赢、全局區(qū)(靜態(tài)區(qū))
:(static)全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域惑惶,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域煮盼。程序結束后由系統(tǒng)釋放。
4带污、文字常量區(qū)
:常量字符串就是放在這里的僵控。程序結束后由系統(tǒng)釋放
5、程序代碼區(qū)
:存放函數體的二進制代碼鱼冀。-
棧和堆的區(qū)別
-
管理方式不同
报破。棧由操作系統(tǒng)自動分配釋放,無需我們手動控制千绪;堆的申請和釋放工作由程序員控制充易,容易產生內存泄漏; -
空間大小不同
荸型。每個進程擁有的棧的大小要遠遠小于堆的大小盹靴。理論上,程序員可申請的堆大小為虛擬內存的大小瑞妇,進程棧的大小64bits的Windows默認1MB稿静,64bits的Linux默認10MB; -
生長方向不同
踪宠。堆的生長方向向上自赔,內存地址由低到高;棧的生長方向向下柳琢,內存地址由高到低绍妨。 -
分配方式不同
润脸。
堆都是動態(tài)分配的,沒有靜態(tài)分配的堆他去。
棧有2種分配方式:靜態(tài)分配和動態(tài)分配毙驯。靜態(tài)分配是由操作系統(tǒng)完成的,比如局部變量的分配灾测。
棧的動態(tài)分配由alloca函數進行分配爆价,但是棧的動態(tài)分配和堆是不同的,棧的動態(tài)分配是由操作系統(tǒng)進行釋放媳搪,無需我們手工實現铭段。 -
分配效率不同
。
棧由操作系統(tǒng)自動分配秦爆,會在硬件層級對棧提供支持:分配專門的寄存器存放棧的地址序愚,壓棧出棧都有專門的指令執(zhí)行,這就決定了棧的效率比較高等限。
堆則是由C/C++提供的庫函數或運算符來完成申請與管理爸吮,實現機制較為復雜,頻繁的內存申請容易產生內存碎片望门。顯然形娇,堆的效率比棧要低得多。
-
二筹误、 多線程
2.1 多線程的意義
- 優(yōu)點
- 能適當提高程序的執(zhí)行效率
- 能適當提高資源的利用率(CPU桐早、內存)
- 線程上的任務執(zhí)行完成后,線程會自動銷毀
- 缺點
- 開啟線程需要占用一定的內存空阿金(默認情況下纫事,每一個線程都占 512 KB)
- 如果開啟大量的線程勘畔,會占用大量的內存空間所灸,降低程序的性能
- 線程越多丽惶,CPU 在調用線程上的開銷就越大
- 程序設計更加復雜,比如線程間的通信爬立、多線程的數據共享
2.2 多線程的原理
CPU 在單位時間片里快速在各個線程之間切換
-
單線程
單核 CPU單線程 任務1 任務2 任務3
- 其本質也是單線程钾唬,一次只能執(zhí)行一個任務
- 單核 CPU的多線程是通過 任務1執(zhí)行一部分再執(zhí)行任務2再切換任務3,這種方式達到多線程的效果
- 不停切換侠驯、時間片抡秆、共贏、達到利益最大化
-
多線程
多核 CPU線程1 任務1 線程2 任務2 線程3 任務3
2.3 多線程技術方案
-
pthread
- 一套通用的多線程API
- 適用于 Unix/Linux/Windows 等系統(tǒng)
- 跨平臺/可移植
- 使用難度大
-
NSThread
- 使用更加面向對象
- 簡單易用吟策,可直接操作線程對象
-
GCD
- 旨在替代 NSTread 等線程技術
- 充分利用設備的多核
-
NSOperation
- 基于GCD(底層是GCD)
- 比 GCD 多了一些更簡單使用的功能
- 使用更加面向對象
三儒士、線程、線程池檩坚、生命周期着撩、安全诅福、通訊、runloop
3.1 線程的生命周期
新建:Start拖叙,創(chuàng)建線程氓润,調用start進入就緒狀態(tài)
就緒:Runnable,CPU調度當前線程薯鳍,進入運行狀態(tài)
運行:Running咖气,任務執(zhí)行完畢,退出線程并銷毀挖滤,CPU調度當前線程
阻塞:Blocked崩溪,調用 sleep方法/等待同步鎖/從可調度線程池移出
死亡:Dead,任務執(zhí)行完成斩松、強制退出
-
可調度線程池
- 當前線程
- 其他線程
- CPU調度的線程是從線程池中獲取
-
正常流程和堵塞流程
- 正常流程:
新建->就緒->運行->死亡
- 堵塞流程:
新建->就緒->運行->堵塞->就緒->...->堵塞
- 正常流程:
3.2 線程池的原理
- 參數解釋
- corePoolSize:線程池的基本大忻踔邸(核心線程池大小)
- maxinumPoolSize:線程池的最大大小
- keepAliveTime:線程池中超過corePoolSize數目的空閑線程的最大存活時間
- unit:keepAliveTime參數的時間單位
- workQueue:任務阻塞隊列
- threadFactory:新建線程的工廠
- handler:當提交的任務數超過maxnumPoolSize與workQueue之和時砸民,任務會交給RejectedExecutionHandler來處理
- 【原理】線程池大小小于核心線程池大小
- 創(chuàng)建線程執(zhí)行任務抵怎,流程和線程的生命周期一樣
- 【原理】線程池大小不小于核心線程池大小
- 線程池判斷工作隊列未滿
將任務push進隊列 - 線程池判斷工作隊列已滿
- 且
maxinumPoolSize>corePoolSize
,將創(chuàng)建新的線程來執(zhí)行任務 - 交給報策略去處理
- Abort策略:默認策略岭参,新任務提交時直接拋出未檢查的異常 RejectedExecution反惕,該異常可由調用者捕獲
- CallerRuns策略:為調節(jié)機制演侯,既不拋棄任務也不拋出異常姿染,而是將某些任務會退到調用者。不會在線程池的線程中執(zhí)行新的任務秒际,而是在調用exector的線程中運行新的任務
- Discard策略:新提交的任務被拋棄悬赏。
- DiscardOldest策略:隊列的是“對頭”的任務,然后嘗試提交新的任務娄徊。(不適合工作隊列為優(yōu)先隊列場景)
- 且
- 線程池判斷工作隊列未滿
3.3 線程和runloop的關系
- runloop與線程的對應關系
- runloop與線程是一一對應的闽颇,一個runloop對應一個核心的線程;
- 為什么說是核心的寄锐?因為runloop是可以嵌套的兵多,但是核心的只能有一個,他們的關系保存在一個全局的字典里橄仆;
- runloop是來管理線程的剩膘,當線程的runloop被開啟后,線程會在執(zhí)行完成后進入休眠狀態(tài)盆顾,有了任務就會被喚醒去執(zhí)行任務怠褐。
- runloop在第一次或失去時被創(chuàng)建,在線程結束時被銷毀您宪。
- 對于主線程來說奈懒,runloop在程序一啟動就默認創(chuàng)建好了具温。
- 對于子線程來說,runloop是懶加載的筐赔,只有當我們使用的時候才會創(chuàng)建铣猩,所以在子線程用定時器要注意:確保子線程的runloop被創(chuàng)建,不然定時器不會回調茴丰。
3.4 線程安全和線程通訊
-
atomic
與nonatomic
的區(qū)別- 屬性定義
-
nonatomic
:非原子屬性达皿,適合內存小的移動設備 -
atomic
:原子屬性(線程安全),針對多線程設計贿肩,默認值峦椰,需要消耗大量的資源。atomic只保證了寫安全汰规,但對于可變數組這類容器中的元素的讀寫安全并不能保證汤功。
-
- 線程安全解釋
- 保證同一時間只有一個線程能夠寫入
- atomic本身就有一把鎖(自旋鎖)
- 單寫多讀:單個線程寫入,多個線程可以讀取
- iOS開發(fā)的建議
- 所有屬性都聲明為 nonatomic
- 盡量避免多線程搶奪一塊資源
- 盡量將加鎖溜哮、資源搶奪的業(yè)務邏輯交給服務器端處理滔金,減小移動客戶端的壓力
- 屬性定義
-
互斥鎖小結
- 保證鎖內的代碼,同一時間茂嗓,只有一條線程能夠執(zhí)行
- 互斥鎖的鎖定范圍餐茵,應該盡量小,鎖定范圍越大述吸,效率越差
- 互斥鎖參數
- 能夠加鎖的任意 NSObject 對象
- 注意:鎖對象一定要保證所有的線程都能夠訪問
- 如果代碼中只有一個地方需要加鎖忿族,大多都使用 self,這樣可以避免單獨再創(chuàng)建一個鎖對象
四蝌矛、iOS中常用多線程技術
4.1 pthread
pthread_create 創(chuàng)建線程
-
參數:
- pthread_t:要創(chuàng)建線程的結構體指針道批,通常開發(fā)的時候,如果遇到 C 語言的結構體入撒,類型后綴
_t / Ref
結尾
同時不需要*
- 線程的屬性隆豹,nil(空對象 - OC 使用的) / NULL(空地址,C 使用的)
- 線程要執(zhí)行的
函數地址
void *
: 返回類型衅金,表示指向任意對象的指針噪伊,和 OC 中的 id 類似
(*)
: 函數名
(void *)
: 參數類型,void *
- 傳遞給第三個參數(函數)的
參數
- 返回值:C 語言框架中非常常見
int
0 創(chuàng)建線程成功氮唯!成功只有一種可能
非 0 創(chuàng)建線程失敗的錯誤碼,失敗有多種可能姨伟!
- 返回值:C 語言框架中非常常見
// 1: pthread pthread_t threadId = NULL; //c字符串 char *cString = "HelloCode"; int result = pthread_create(&threadId, NULL, pthreadTest, cString); if (result == 0) { NSLog(@"成功"); } else { NSLog(@"失敗"); }
void *pthreadTest(void *para){ // 接 C 語言的字符串 // NSLog(@"===> %@ %s", [NSThread currentThread], para); // __bridge 將 C 語言的類型橋接到 OC 的類型 NSString *name = (__bridge NSString *)(para); NSLog(@"===>%@ %@", [NSThread currentThread], name); return NULL; }
- pthread_t:要創(chuàng)建線程的結構體指針道批,通常開發(fā)的時候,如果遇到 C 語言的結構體入撒,類型后綴
4.2 NSThread
[NSThread detachNewThreadSelector:@selector(threadTest) toTarget:self withObject:nil];
/**
1. 循環(huán)的執(zhí)行速度很快
2. 棧區(qū)/常量區(qū)的內存操作也挺快
3. 堆區(qū)的內存操作有點慢
4. I(Input輸入) / O(Output 輸出) 操作的速度是最慢的惩琉!
* 會嚴重的造成界面的卡頓,影響用戶體驗夺荒!
* 多線程:開啟一條線程瞒渠,將耗時的操作放在新的線程中執(zhí)行
*/
- (void)threadTest{
NSLog(@"begin");
NSInteger count = 1000 * 100;
for (NSInteger i = 0; i < count; i++) {
// 棧區(qū)
NSInteger num = I;
// 常量區(qū)
NSString *name = @"zhang";
// 堆區(qū)
NSString *myName = [NSString stringWithFormat:@"%@ - %zd", name, num];
NSLog(@"%@", myName);
}
NSLog(@"over");
}
4.3 GCD
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self threadTest];
});
4.4 NSOperation
[[[NSOperationQueue alloc] init] addOperationWithBlock:^{
[self threadTest];
}];