線程和進(jìn)程
進(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)
通過活動(dòng)監(jiān)視器可以查看mac系統(tǒng)中所有開啟的進(jìn)程
進(jìn)程沒有直接執(zhí)行任務(wù)的能力
線程
即 thread 是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位。被包含在進(jìn)程之中,是進(jìn)程中的實(shí)際運(yùn)作單位。一條線程指的是進(jìn)程中一個(gè)單一順序的控制流,一個(gè)進(jìn)程中可以并發(fā)多個(gè)線程,每條線程并行執(zhí)行不同的任務(wù); 線程是進(jìn)程中的實(shí)際執(zhí)行單元,進(jìn)程中的任務(wù)執(zhí)行依賴線程來實(shí)現(xiàn)
線程和進(jìn)程的關(guān)系:
線程是進(jìn)程中的實(shí)際執(zhí)行單元,進(jìn)程中的任務(wù)執(zhí)行依賴線程來實(shí)現(xiàn), 例如: 生產(chǎn)車間和工人的關(guān)系, 生產(chǎn)車間負(fù)責(zé)工作環(huán)境的準(zhǔn)備 工人來實(shí)際生產(chǎn)產(chǎn)品

線程各個(gè)狀態(tài)下線程所處的位置:
新建狀態(tài):線程被創(chuàng)建 在內(nèi)存中,但不在可調(diào)度池
就緒狀態(tài):線程對(duì)象調(diào)用 start 函數(shù) 將線程加入可調(diào)度線程池, 等待CPU的調(diào)用; 即調(diào)用start函數(shù)并不會(huì)立即執(zhí)行,進(jìn)入就緒狀態(tài)后 需要等待CPU調(diào)度
運(yùn)行狀態(tài):CPU負(fù)責(zé)調(diào)度線程池中線程執(zhí)行任務(wù), 在線程執(zhí)行完成之前 , 其狀態(tài)可能會(huì)在就緒和運(yùn)行之間來回切換,這個(gè)變化是由CPU負(fù)責(zé),開發(fā)人員不能干預(yù)
阻塞狀態(tài):當(dāng)滿足某個(gè)預(yù)定條件時(shí) 阻塞線程執(zhí)行(比如: 同步鎖/sleep()), 當(dāng)進(jìn)入sleep時(shí)線程會(huì)再次進(jìn)入就緒狀態(tài) ;
NSThread的休眠:
sleepUntilDate: 阻塞當(dāng)前線程,直到指定的時(shí)間為止 即休眠到指定時(shí)間
sleepForTimeInterval: 在給定的時(shí)間間隔內(nèi)休眠 , 即指定休眠時(shí)長(zhǎng)
同步鎖: @synchronized(self);
死亡狀態(tài):
正常死亡: 線程執(zhí)行完畢
非正常死亡: 當(dāng)滿足某個(gè)條件后,在線程內(nèi)部(或者主線程)終止執(zhí)行(調(diào)用exit) 或異常崩潰
線程的exit和cancel :
exit: 強(qiáng)行終止線程, 后續(xù)的所有代碼都不會(huì)執(zhí)行; 很有可能引起數(shù)據(jù)錯(cuò)亂
cancel : 取消當(dāng)前線程,但不能取消正在執(zhí)行的線程
線程池原理

第一步: 判斷核心線程池是否都在執(zhí)行任務(wù)
返回NO ,創(chuàng)建新線程去執(zhí)行任務(wù)
返回YES ,進(jìn)入第二步
第二步: 判斷線程池工作隊(duì)列是否已經(jīng)飽和
返回NO, 將任務(wù)存儲(chǔ)到工作隊(duì)列,等待CPU調(diào)度
返回YES,進(jìn)入第三步
第三步:判斷線程池中的線程是否都處于執(zhí)行狀態(tài)
返回NO,安排可調(diào)度線程池中空閑的線程去執(zhí)行任務(wù)
返回YES,進(jìn)入第四步
第四步:交給飽和策略去執(zhí)行,主要有四種(在iOS中并沒有找到以下4種策略)
AbortPolicy: 直接拋出RejectedExcutionExeception異常來阻止系統(tǒng)正常運(yùn)行
CallerRunsPolicy: 將任務(wù)退回到調(diào)度者
DisOldestPolicy: 丟掉等待最久的任務(wù)
DisCardPolicy: 直接丟棄任務(wù)
什么是主線程?
當(dāng)一個(gè)程序啟動(dòng)時(shí),就有一個(gè)進(jìn)程被操作系統(tǒng)(OS)創(chuàng)建,與此同時(shí)一個(gè)線程也立刻運(yùn)行,該線程通常叫做程序的主線程(Main Thread),因?yàn)樗浅绦蜷_始時(shí)就執(zhí)行的。主線程是負(fù)責(zé)執(zhí)行main函數(shù)的線程;主線程中幾乎所有的事情都是交給runloop去做,比如UI界面刷新、點(diǎn)擊事件的處理、performSelector等需要Runloop,但是像簡(jiǎn)單的普通代碼:NSLog輸出、變量定義等是不需要Runloop參與的;
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
線程間通信
在Thread Programming Guide文檔中,提及線程進(jìn)通訊有以下幾種方式

-
直接消息傳遞: 通過performSelector:onThread:的一些列方法可以實(shí)現(xiàn)由某一線程之指定在另外的線程上執(zhí)行任務(wù); 因?yàn)槿蝿?wù)的執(zhí)行上下文是目標(biāo)線程,這種方式發(fā)送消息將會(huì)自動(dòng)的被序列化 -
全局變量/共享內(nèi)存塊和對(duì)象: 在兩個(gè)線程之間傳遞信息的另一種簡(jiǎn)單方法是使用全局變量,共享對(duì)象或共享內(nèi)存塊 ,盡管共享變量即快速又簡(jiǎn)單,但是他們比直接消息傳遞更脆弱, 容易有線程安全問題必須使用鎖或其他同步機(jī)制仔細(xì)保護(hù)共享變量 以確保代碼的正確性否則可能會(huì)導(dǎo)致資源競(jìng)爭(zhēng)引起數(shù)據(jù)錯(cuò)亂或崩潰 -
條件執(zhí)行: 條件是一種同步工具,可用于控制線程何時(shí)執(zhí)行代碼的特定部分, 可以將條件視為關(guān)守 讓線程僅在滿足指定條件時(shí)運(yùn)行 -
Runloop sources: 一個(gè)自定義的Runloop source配置可以讓一個(gè)線程收到的定的應(yīng)用程序消息; 由于Runloop source是事件驅(qū)動(dòng)的,因此在無事可做時(shí) 線程會(huì)自動(dòng)進(jìn)入休眠狀態(tài)從而提高線程的效率 -
Ports add sockets: 基于端口的通信是在兩個(gè)線程之間進(jìn)行通信的一種更為復(fù)雜的方法, 但它也是一種非??煽康募夹g(shù), 更重要的是 端口和套接字可用與外部實(shí)體(例如其他進(jìn)程和服務(wù))進(jìn)行通信; 為了提高效率使用Runloop source來實(shí)現(xiàn)端口, 因此當(dāng)端口上沒有數(shù)據(jù)等待時(shí) 線程將進(jìn)入休眠狀態(tài); 需要注意的是 端口通訊需要將端口加入到主線程的runloop中,否則不會(huì)走到端口回調(diào)方法 -
消息隊(duì)列: 傳統(tǒng)的多處理服務(wù)定義了先進(jìn)先出(FIFO)隊(duì)列抽象,用于管理傳入和傳出數(shù)據(jù) 盡管消息隊(duì)列即簡(jiǎn)單又方便 但是它們不如其他一些通信技術(shù)高效 -
Coca分布式對(duì)象: 分布式對(duì)像是一種Cocoa技術(shù),可提供基于端口的通信的高級(jí)實(shí)現(xiàn),盡管可以將這種技術(shù)用于線程間通信但是強(qiáng)烈建議不要這樣做,因?yàn)樗鼤?huì)產(chǎn)生大量開銷; 分布式對(duì)象更適合與其他進(jìn)程間通信,盡管在這些進(jìn)程之間進(jìn)行事務(wù)的開銷也很高
線程的優(yōu)先級(jí)越高是不是意味著任務(wù)執(zhí)行的越快 ?
并不是的,線程執(zhí)行的快慢受兩方面影響:
- 任務(wù)的復(fù)雜度/CPU調(diào)度情況
- 優(yōu)先級(jí)(priority) 線程的優(yōu)先級(jí)(threadPriority)
-
QoS(quality of service)意指服務(wù)質(zhì)量
QoS
QoS(quality of service) 指線程的服務(wù)質(zhì)量,他影響線程的優(yōu)先級(jí)(priority), 也影響I/O吞吐/CPU吞吐等指標(biāo); 開發(fā)者可以用 qos_class_self獲取當(dāng)前線程/隊(duì)列的QoS
Table 4-5GCD to Foundation QoS mappings 相關(guān)枚舉

蘋果對(duì)于每個(gè)任務(wù)應(yīng)該選用那個(gè)Qos也有一些指導(dǎo)性意見:
Table 4-1Primary QoS classes (shown in order of priority)

QoS 和 priority 是有對(duì)應(yīng)關(guān)系的,參考 xnu 源碼和實(shí)驗(yàn)結(jié)果,對(duì)應(yīng)關(guān)系為:

同時(shí),線程的 priority 會(huì)隨著執(zhí)行動(dòng)態(tài)調(diào)整。測(cè)試中我們會(huì)發(fā)現(xiàn),主線程的 priority 在運(yùn)行開始時(shí)是 QoS User-Interactive 對(duì)應(yīng)的 47,但隨著運(yùn)行會(huì)出現(xiàn)下降的情況。

官方文檔中解釋了線程 priority 變化的原因,priority 由 Mach scheduler控制,為了防止計(jì)算密集的線程壟斷資源,各個(gè)線程的 priority 會(huì)實(shí)時(shí)調(diào)整。
XNU 內(nèi)核的源碼中, 線程 priority 的變化,是由各個(gè) Mach scheduler 實(shí)現(xiàn)的 compute_timeshare_priority 接口控制的。在 iOS 使用的 Mach scheduler 中,compute_timeshare_priority 為同一個(gè)實(shí)現(xiàn) sched_compute_timeshare_priority。線程調(diào)度時(shí)的 priority,會(huì)在線程固有 priority 的基礎(chǔ)上,結(jié)合當(dāng)前線程的 CPU 占用情況和當(dāng)前設(shè)備的整體負(fù)載進(jìn)行調(diào)整
在這個(gè)實(shí)現(xiàn)中,我們能看到 Mach scheduler 對(duì) priority 的調(diào)整會(huì)有一個(gè)極限:對(duì)于原先 priority = 47 的線程來說,向下調(diào)整的極限是 47 - ((BASEPRI_FOREGROUND - BASEPRI_DEFAULT) + 2) = 29。這和我們用多個(gè)設(shè)備測(cè)試到的結(jié)果吻合:主線程執(zhí)行時(shí),priority 的最低值是 29,依然高于 Utility 對(duì)應(yīng)的 priority 20, 這也證明了當(dāng)異步線程的 QoS 是Utility 時(shí),就幾乎無法對(duì)主線程造成搶占 ! 利用此思路可以對(duì)啟動(dòng)時(shí)間進(jìn)行優(yōu)化,尤其是低端機(jī)效果明顯, 核心思路: QoS 是 User-Initiated的時(shí)候,盡管這一 QoS 低于主線程的 User-Interactive,但依然可能對(duì)主線程造成搶占;需要將異步隊(duì)列的 QoS 調(diào)低到 Utility
不改一行業(yè)務(wù)代碼,飛書 iOS 低端機(jī)啟動(dòng)優(yōu)化實(shí)踐
一條線程占多大內(nèi)存空間 ?
在iOS中主線程占1MB 子線程占512KB ????
還是代碼測(cè)試一下吧 ?。?/p>
//子線程內(nèi)存大小
- (void) getstacksize {
size_t stack_size = 0; //堆棧大小變量
pthread_attr_t attr; //線程屬性結(jié)構(gòu)體變量
//初始化線程屬性
int ret = pthread_attr_init(&attr);
if(ret != 0)
{
perror("pthread_attr_init");
return;
}
//獲取當(dāng)前的線程棧大小
ret = pthread_attr_getstacksize(&attr, &stack_size);
if(ret != 0)
{
perror("pthread_attr_getstacksize");
return;
}
//打印堆棧值 stack_size = 524288B, 512k
printf("stack_size = %zuB, %luk\n", stack_size, stack_size/1024);
}
//MARK: 主線程的內(nèi)存大小
- (void)test36 {
NSThread *main = [NSThread currentThread];
NSLog(@"%@",main);// 打印結(jié)果 {number = 1, name = main}
NSUInteger size = main.stackSize;
printf("stack_size = %ldB, %ldk\n", size, size/1024);//stack_size = 524288B, 512k
}
總結(jié):線程默認(rèn)給的是512KB的內(nèi)存, 可以通過pthread_attr_setstacksize 設(shè)置線程的內(nèi)存
pthread_attr_getstacksize 查詢線程內(nèi)存
OC可以通過NSThread的setStackSize來設(shè)置
線程的調(diào)度方式是什么 ?
先說下時(shí)間片
- 時(shí)間片是分時(shí)操作系統(tǒng)分配給每個(gè)正在運(yùn)行的進(jìn)程微觀上的一段CPU時(shí)間,時(shí)間片的大小對(duì)系統(tǒng)的性能影響很大。
如果時(shí)間片足夠大,以至于所有進(jìn)程都能在一個(gè)時(shí)間片內(nèi)執(zhí)行完畢,則時(shí)間片輪轉(zhuǎn)調(diào)度算法就退化為先來先服務(wù)調(diào)度算發(fā)。如果時(shí)間片很小,那么處理機(jī)將在進(jìn)程間過于頻繁切換,使處理機(jī)的開銷增大,而真正用于處理用戶作業(yè)的時(shí)間將減少,因此時(shí)間片的大小應(yīng)選擇適當(dāng)。
線程被放在線程池里等待CPU來調(diào)度, 常見的CPU調(diào)度算法
先來先服務(wù)算法(FCFS)
這個(gè)調(diào)度算法是最簡(jiǎn)單的調(diào)度算法,這個(gè)算法按照每次在就緒隊(duì)列中選擇最先進(jìn)入該隊(duì)列的進(jìn)程進(jìn)行調(diào)度。屬于不可剝奪度算法,有利于CPU繁忙型作業(yè),不利于I/O繁忙性作業(yè)。短作業(yè)優(yōu)先算法(SJF)
此算法是從后背隊(duì)列中選擇一個(gè)或幾個(gè)估計(jì)運(yùn)行時(shí)間短的作業(yè),優(yōu)先調(diào)度到內(nèi)存中運(yùn)行。該做法對(duì)場(chǎng)作業(yè)不利,所以無法保證緊迫性的作業(yè)會(huì)被及時(shí)處理。優(yōu)先級(jí)調(diào)度算法
分為非剝奪和剝奪式的調(diào)度。每個(gè)線程有一個(gè)優(yōu)先級(jí),CPU每次去拿優(yōu)先級(jí)高的運(yùn)行,優(yōu)先級(jí)低的等等,為了避免等太久,每等一定時(shí)間,就給線程提高一個(gè)優(yōu)先級(jí)。高響應(yīng)比優(yōu)先調(diào)度算法
是對(duì)先來先服務(wù)算法(FCFS)和短作業(yè)優(yōu)先算法(SJF)的一種綜合平衡,克服了饑餓狀態(tài)也兼顧了場(chǎng)作業(yè)。時(shí)間輪轉(zhuǎn)片調(diào)度算法
主要適用于分時(shí)系統(tǒng)。根據(jù)先來先服務(wù)的原則,但是僅能運(yùn)行一個(gè)時(shí)間片,用完之后及時(shí)沒有完成運(yùn)行任務(wù),也要將資源釋放給下一個(gè)就緒作業(yè),被剝奪的返回就緒隊(duì)列直至下一次被運(yùn)行。多級(jí)反饋隊(duì)列調(diào)度算法
線程池有幾種 ?
線程池 是一種多線程處理形式(或模式),處理過程中將任務(wù)添加到隊(duì)列,然后在創(chuàng)建線程后自動(dòng)啟動(dòng)這些任務(wù)
一般線程池由部分組成:
-
線程池管理器: 用于創(chuàng)建并管理線程池 -
工作線程:線程池中的線程 -
任務(wù)接口:每個(gè)任務(wù)必須實(shí)現(xiàn)的接口,用于工作線程調(diào)度其運(yùn)行 -
任務(wù)隊(duì)列:用于存放待處理的任務(wù),提供一種緩沖機(jī)制
可以通過java中的六種線程池了解線程池的工作方式
OC中的線程池管理器: NSConnection 是用于管理不同線程中的對(duì)象之間的通信,或者在本地或遠(yuǎn)程系統(tǒng)上運(yùn)行的線程與進(jìn)程之間的通信
如何停止一個(gè)線程 ?
- 首先不能簡(jiǎn)單的停止一個(gè)線程, 因?yàn)橥V挂粋€(gè)線程會(huì)直接把線程干掉,這樣就沒有給線程足夠的時(shí)間來處理想要在停止前保存數(shù)據(jù)的邏輯,任務(wù)戛然而止,可能會(huì)導(dǎo)致數(shù)據(jù)完整性問題
- 雖然線程不能在中間被停止/干掉,但是任務(wù)是可以停止的; 想讓線程結(jié)束的目的是讓任務(wù)結(jié)束,而不是強(qiáng)制線程結(jié)束(exit); 有兩種方式結(jié)束任務(wù),分別是Interrupt和boolean標(biāo)志位
- 1.使用線程中斷機(jī)制-interrupt停止線程, 原生支持sleep、wait等可以讓線程進(jìn)入阻塞狀態(tài)使線程休眠, 而處于休眠中的線程被中斷,那么線程是可以感受到中斷信號(hào)的
- 2.使用volatile boolean標(biāo)志位停止線程:線程中設(shè)置一個(gè)boolean標(biāo)志位值為false,線程里不斷讀取這個(gè)boolean值,其他地方可以修改這個(gè)boolean值;為了保證內(nèi)存可見性,給boolean標(biāo)志位添加volatile保證可見性;當(dāng)某一個(gè)線程修改boolean標(biāo)志位為true,線程中能立刻看到。
如何選擇interrupt和boolean標(biāo)志位去停止線程?interrupt()和boolean標(biāo)志位的原理是一致的。除非是用到了系統(tǒng)方法時(shí)(如:sleep) 或者 使用阻塞隊(duì)列發(fā)生阻塞,使用interrupt();否則,建議使用boolean標(biāo)志位,性能更優(yōu),畢竟interrupt有一定的開銷
這道題想考察什么?
考察要點(diǎn) :
- 是否對(duì)線程的用法有了解;是否對(duì)線程的exit方法有了解(初級(jí))
- 是否對(duì)線程exit過程中存在的問題有認(rèn)識(shí);是否熟悉interrupt中斷的用法(中級(jí))
- 是否能解釋清楚使用boolean標(biāo)志位的好處;是否知道interrupt底層的細(xì)節(jié);通過該題目能夠轉(zhuǎn)移話題到線程安全,并闡述無誤(高級(jí))
exit官方文檔解釋:

- 終止當(dāng)前線程
- 這個(gè)方法使用currentThread類方法來訪問當(dāng)前線程。在退出線程之前,這個(gè)方法將退出線程的NSThreadWillExitNotification發(fā)送到默認(rèn)通知中心。因?yàn)橥ㄖ峭桨l(fā)送的,所以NSThreadWillExitNotification的所有觀察者都保證在線程退出之前接收到通知。
- 應(yīng)該避免調(diào)用此方法,因?yàn)樗粫?huì)給您的線程一個(gè)機(jī)會(huì)來清理它在執(zhí)行期間分配的任何資源。
線程安全方案:
- 對(duì)訪問過程加鎖
- 用線程同步技術(shù)
附: 多線程安全方案