線(xiàn)程
- 線(xiàn)程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程的所有任務(wù)都在線(xiàn)程中執(zhí)行,一個(gè)進(jìn)程可以有多個(gè)線(xiàn)程。
- 進(jìn)程想要執(zhí)行任務(wù),必須得有線(xiàn)程,進(jìn)程至少需要一條線(xiàn)程。
- 程序啟動(dòng)默認(rèn)開(kāi)啟一條線(xiàn)程,這條線(xiàn)程被稱(chēng)為主線(xiàn)程或UI線(xiàn)程。
進(jìn)程
- 進(jìn)程是在系統(tǒng)中正在執(zhí)行的一個(gè)應(yīng)用程序。
- 每個(gè)進(jìn)程之間相互獨(dú)立,每個(gè)進(jìn)程均運(yùn)行在其專(zhuān)用的且受保護(hù)的內(nèi)存空間內(nèi)。
譬如Mac可以通過(guò)“活動(dòng)監(jiān)視器”查看系統(tǒng)中所開(kāi)啟的進(jìn)程。
線(xiàn)程與進(jìn)程的關(guān)系
- 地址空間:同一進(jìn)程的線(xiàn)程共享本進(jìn)程的地址空間,而進(jìn)程之間則是獨(dú)立的地址空間。
- 資源擁有:同一進(jìn)程內(nèi)的線(xiàn)程共享本進(jìn)程的資源如內(nèi)存、I/O、CPU等,但是進(jìn)程之間資源是相互獨(dú)立的
- 一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其他進(jìn)程產(chǎn)生影響,但是一個(gè)線(xiàn)程奔潰會(huì)導(dǎo)致整個(gè)進(jìn)程都死掉。所以多進(jìn)程要比多線(xiàn)程健壯。
- 進(jìn)程切換時(shí),效率高,但消耗的資源大。所以涉及到頻繁切換時(shí),使用線(xiàn)程要比進(jìn)程好。同樣如果要同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線(xiàn)程而不能用進(jìn)程。
- 執(zhí)行過(guò)程:每個(gè)獨(dú)立的進(jìn)程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序入口。但是線(xiàn)程不能獨(dú)立運(yùn)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線(xiàn)程執(zhí)行控制。
- 線(xiàn)程是處理器調(diào)度的基本單位,但進(jìn)程不是。
- 線(xiàn)程沒(méi)有地址空間,線(xiàn)程包含在進(jìn)程的地址空間中。
安卓開(kāi)發(fā)可以有多個(gè)進(jìn)程;iOS往往是單進(jìn)程。
多線(xiàn)程
多個(gè)線(xiàn)程在多CPU下時(shí),效率是非常之高的,相比于一個(gè)CPU在一個(gè)進(jìn)程中只能執(zhí)行一條線(xiàn)程,現(xiàn)如今的多核CPU對(duì)多線(xiàn)程并發(fā)處理邏輯提供非常好的支持。但多線(xiàn)程也會(huì)有其優(yōu)點(diǎn)和不足
優(yōu)點(diǎn)
- 可以提高應(yīng)用程序的感知響應(yīng)能力和在多核系統(tǒng)上的實(shí)時(shí)性能。
- 能適當(dāng)提高資源的利用率(CPU、內(nèi)存)。
- 線(xiàn)程上的任務(wù)執(zhí)行完成后,線(xiàn)程會(huì)自動(dòng)銷(xiāo)毀。
缺點(diǎn)
- 1.開(kāi)啟線(xiàn)程需要占用一定的內(nèi)存空間(默認(rèn)情況下,每一個(gè)線(xiàn)程都占512KB)。
- 如果開(kāi)啟大量的線(xiàn)程,會(huì)占用大量的內(nèi)存空間,降低程序的性能。
- 線(xiàn)程越多,CPU在調(diào)度線(xiàn)程上的開(kāi)銷(xiāo)就越大。
- 程序設(shè)計(jì)會(huì)變得更加復(fù)雜,比如線(xiàn)程間的通信、多線(xiàn)程的數(shù)據(jù)共享。
多線(xiàn)程 & CPU
- 單核CPU在同一時(shí)間,CPU只能處理1個(gè)線(xiàn)程,即此時(shí)只有1個(gè)線(xiàn)程在執(zhí)行。
- 多線(xiàn)程同時(shí)執(zhí)行: 指CPU快速的在多個(gè)線(xiàn)程之間切換;CPU調(diào)度線(xiàn)程的時(shí)間足夠塊,就造成了多線(xiàn)程“同時(shí)”執(zhí)行的效果。
- 如果線(xiàn)程足夠多,CPU會(huì)在N個(gè)線(xiàn)程之間切換,消耗大量的CPU資源;每個(gè)線(xiàn)程被調(diào)度的次數(shù)越低,線(xiàn)程的執(zhí)行效率越低。
多線(xiàn)程的技術(shù)方案
在iOS中使用的多線(xiàn)程分為以下四種:pthread、NSThread、GCD、NSOperation,下圖為各自的區(qū)別

內(nèi)存區(qū)
進(jìn)程、線(xiàn)程的執(zhí)行都是需要依托內(nèi)存去執(zhí)行的,同一進(jìn)程內(nèi)的線(xiàn)程會(huì)共享本進(jìn)程的內(nèi)存。而「內(nèi)存」會(huì)有多種,在iOS中分為:棧區(qū)、堆區(qū)、全局區(qū)(靜態(tài)區(qū))、常量區(qū)、代碼區(qū)。接下來(lái)看看各自?xún)?nèi)存五大區(qū)都負(fù)責(zé)什么。
棧區(qū)(stack)
- 在程序創(chuàng)建臨時(shí)變量時(shí)(即在運(yùn)行時(shí)),由系統(tǒng)自動(dòng)分配,當(dāng)不需要時(shí)自動(dòng)清除的變量的存儲(chǔ)區(qū)。
- 變量:局部變量、函數(shù)參數(shù)等。
- 在一個(gè)進(jìn)程中,編譯器用來(lái)實(shí)現(xiàn)函數(shù)調(diào)用的地方是用戶(hù)棧,它位于虛擬地址空間的頂部,棧地址是連續(xù)存儲(chǔ)且向下擴(kuò)展,遵循先進(jìn)后出原則(FILO);用戶(hù)棧在程序執(zhí)行期間可以動(dòng)態(tài)的擴(kuò)展和收縮,這個(gè)是棧和堆的共同點(diǎn)。
- 棧區(qū)大小根據(jù)系統(tǒng)不一。在iOS主線(xiàn)程下,大概空間為1M;輔助線(xiàn)程為512KB;MacOS主線(xiàn)程下,大概空間為8M。
按照蘋(píng)果文檔介紹,輔助線(xiàn)程允許的最小堆棧大小為16 KB,并且堆棧大小必須為4 KB的倍數(shù)。在線(xiàn)程創(chuàng)建時(shí)會(huì)在進(jìn)程空間中預(yù)留此內(nèi)存的空間,但是直到需要它們時(shí),才會(huì)創(chuàng)建與該內(nèi)存關(guān)聯(lián)的實(shí)際頁(yè)面。這還取決于CPU負(fù)載,計(jì)算機(jī)速度以及可用系統(tǒng)和程序內(nèi)存的數(shù)量。

堆區(qū)(Heap)
- 由類(lèi)創(chuàng)建對(duì)象而開(kāi)辟的內(nèi)存空間,譬如:
alloc 、new等。 - 它是不連續(xù)的存儲(chǔ)空間,地址是由低向高擴(kuò)展,遵循先進(jìn)先出原則(FIFO),這與棧相反。
- 堆亦可以動(dòng)態(tài)的擴(kuò)展與收縮。這表現(xiàn)在運(yùn)行時(shí)的對(duì)象的創(chuàng)建與釋放?,F(xiàn)如今開(kāi)發(fā)都是在ARC環(huán)境下,對(duì)象的釋放由系統(tǒng)操作完成,當(dāng)該對(duì)象的應(yīng)用計(jì)數(shù)為0時(shí),就會(huì)被release掉。MRC下則需要程序員手動(dòng)釋放。
通過(guò)下面的例子可以看看棧區(qū)地址和堆區(qū)地址的不同
// 定義一個(gè)Acount類(lèi)
@interface Account()
@end
@implementation Account
- (void)printWithName:(NSString *)name
{
// 參數(shù)name是一個(gè)指針,指向傳入的參數(shù)指針?biāo)赶虻膶?duì)象內(nèi)存地址。name是在棧中
NSLog(@"name指針地址:%p,name指針指向的對(duì)象內(nèi)存地址:%p",&name,name);
}
/* account 是指針變量,在棧中;[Account alloc]開(kāi)辟的內(nèi)存空間就是在堆中
* account 指針指向了[[Account alloc]init]所創(chuàng)建的對(duì)象。
*/
Account *account = [[Account alloc]init];

通過(guò)打印地址可以知道,傳入?yún)?shù)的對(duì)象地址與print方法參數(shù)的對(duì)象指針地址不一樣,但是內(nèi)存地址是一樣的,p account 打印的則是堆空間地址,一般以0x6開(kāi)頭,??臻g地址一般以0x7開(kāi)頭。
全局區(qū)(靜態(tài)區(qū))
- 全局變量和靜態(tài)變量存儲(chǔ)的區(qū)域。程序運(yùn)行即一直存在,程序結(jié)束后由系統(tǒng)釋放。
(全局變量是指變量值可以在運(yùn)行時(shí)被動(dòng)態(tài)修改,而靜態(tài)變量是static修飾的變量,包含靜態(tài)局部變量和靜態(tài)全局變量。) - 未初始化全局區(qū):
.bss段 - 初始化全局區(qū):
.data段
靜態(tài)變量有兩種
- 全局靜態(tài)變量
優(yōu)點(diǎn):不管對(duì)象方法還是類(lèi)方法都可以訪(fǎng)問(wèn)和修改全局靜態(tài)變量,并且外部類(lèi)無(wú)法調(diào)用靜態(tài)變量,定義后只會(huì)指向固定的指針地址,供所有對(duì)象使用,節(jié)省空間。
缺點(diǎn):存在的生命周期長(zhǎng),從定義直到程序結(jié)束。
建議:從內(nèi)存優(yōu)化和程序編譯的角度來(lái)說(shuō),盡量少用全局靜態(tài)變量,因?yàn)榇嬖诘穆暶髦芷陂L(zhǎng),一直占用空間。程序運(yùn)行時(shí)會(huì)單獨(dú)加載一次全局靜態(tài)變量,過(guò)多的全局靜態(tài)變量會(huì)造成程序啟動(dòng)慢。
- 局部靜態(tài)變量
優(yōu)點(diǎn):定義后只會(huì)存在一份值,每次調(diào)用都是使用的同一個(gè)對(duì)象內(nèi)存地址的值,并沒(méi)有重新創(chuàng)建,節(jié)省空間,只能在該局部代碼塊中使用。
缺點(diǎn):存在的生命周期長(zhǎng),從定義直到程序結(jié)束,只能在該局部代碼塊中使用。
建議:局部和全局靜態(tài)變量從根本意義上沒(méi)有什么區(qū)別,只是作用域不同。如果值僅是一個(gè)類(lèi)中的對(duì)象和類(lèi)方法使用并且值可變,可以定義全局靜態(tài)變量,如果是多個(gè)類(lèi)使用并可變,建議值定義在model作為成員變量使用。如果是不可變值,建議使用宏定義 ,譬如:static NSString * value;
常量區(qū)(cosnt)
- 存放常量且不允許修改的存儲(chǔ)區(qū),在編譯時(shí)已經(jīng)確定,程序結(jié)束后由系統(tǒng)釋放。一般用于接口或者文字顯示這種固定值。添加extern可以對(duì)外全局常量,任意位置都可以訪(fǎng)問(wèn)。
// .h中定義extern
extern NSString *const name;
// .m中定義值
NSString *const name = @"123";
代碼區(qū)
- 編譯時(shí)分配的主要用于存放程序運(yùn)行時(shí)的代碼。代碼會(huì)被編譯成
二進(jìn)制文件存進(jìn)內(nèi)存。
內(nèi)存五大區(qū)示意圖

線(xiàn)程生命周期
線(xiàn)程的生命周期可以用下面的一個(gè)示意圖來(lái)概括

- 當(dāng)一個(gè)線(xiàn)程對(duì)象被創(chuàng)建,調(diào)用
start方法后會(huì)處于就緒狀態(tài),此時(shí)線(xiàn)程加入可調(diào)度線(xiàn)程池等待執(zhí)行;CPU通過(guò)調(diào)度當(dāng)前線(xiàn)程運(yùn)行它,當(dāng)線(xiàn)程運(yùn)行完之后,結(jié)束線(xiàn)程;CPU在調(diào)度線(xiàn)程過(guò)程中,如果CPU負(fù)載小,CPU的多核機(jī)制會(huì)調(diào)用其他線(xiàn)程同時(shí)執(zhí)行。 - 當(dāng)一個(gè)線(xiàn)程正在運(yùn)行過(guò)程中,如果調(diào)用了
Sleep or 同步鎖阻塞線(xiàn)程執(zhí)行,會(huì)將運(yùn)行線(xiàn)程重可調(diào)度池中移出;當(dāng)Sleep阻塞結(jié)束,會(huì)重新將線(xiàn)程加入可調(diào)度池進(jìn)入就緒狀態(tài)。
線(xiàn)程池執(zhí)行策略

線(xiàn)程安全
在一個(gè)進(jìn)程中,多個(gè)線(xiàn)程在同時(shí)執(zhí)行,線(xiàn)程之間可能會(huì)訪(fǎng)問(wèn)同一地址空間或資源,這樣就會(huì)導(dǎo)致數(shù)據(jù)錯(cuò)亂,這個(gè)時(shí)候線(xiàn)程安全就變得尤為重要。加鎖就是很好的解決方案。
鎖是用于線(xiàn)程編程中保持線(xiàn)程同步的方式或排除并發(fā)的一種策略。為了防止多線(xiàn)程訪(fǎng)問(wèn)資源的搶奪。鎖可以輕松保護(hù)大部分代碼,保證鎖內(nèi)的代碼,同?時(shí)間,只有?條線(xiàn)程能夠執(zhí)?,從而可以確保該代碼的正確性。
關(guān)于鎖的使用及介紹,后續(xù)文章再詳細(xì)分析。