OC底層原理18-線(xiàn)程編程

iOS--OC底層原理文章匯總

線(xiàn)程

    1. 線(xiàn)程是進(jìn)程的基本執(zhí)行單元,一個(gè)進(jìn)程的所有任務(wù)都在線(xiàn)程中執(zhí)行,一個(gè)進(jìn)程可以有多個(gè)線(xiàn)程。
    1. 進(jìn)程想要執(zhí)行任務(wù),必須得有線(xiàn)程,進(jìn)程至少需要一條線(xiàn)程。
    1. 程序啟動(dòng)默認(rèn)開(kāi)啟一條線(xiàn)程,這條線(xiàn)程被稱(chēng)為主線(xiàn)程或UI線(xiàn)程。

進(jìn)程

    1. 進(jìn)程是在系統(tǒng)中正在執(zhí)行的一個(gè)應(yīng)用程序。
    1. 每個(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ú)立
  1. 一個(gè)進(jìn)程崩潰后,在保護(hù)模式下不會(huì)對(duì)其他進(jìn)程產(chǎn)生影響,但是一個(gè)線(xiàn)程奔潰會(huì)導(dǎo)致整個(gè)進(jìn)程都死掉。所以多進(jìn)程要比多線(xiàn)程健壯。
  2. 進(jìn)程切換時(shí),效率高,但消耗的資源大。所以涉及到頻繁切換時(shí),使用線(xiàn)程要比進(jìn)程好。同樣如果要同時(shí)進(jìn)行并且又要共享某些變量的并發(fā)操作,只能用線(xiàn)程而不能用進(jìn)程。
  3. 執(zhí)行過(guò)程:每個(gè)獨(dú)立的進(jìn)程有一個(gè)程序運(yùn)行的入口、順序執(zhí)行序列和程序入口。但是線(xiàn)程不能獨(dú)立運(yùn)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個(gè)線(xiàn)程執(zhí)行控制。
  4. 線(xiàn)程是處理器調(diào)度的基本單位,但進(jìn)程不是。
  5. 線(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)

    1. 可以提高應(yīng)用程序的感知響應(yīng)能力和在多核系統(tǒng)上的實(shí)時(shí)性能。
    1. 能適當(dāng)提高資源的利用率(CPU、內(nèi)存)。
    1. 線(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)。
    1. 如果開(kāi)啟大量的線(xiàn)程,會(huì)占用大量的內(nèi)存空間,降低程序的性能。
    1. 線(xiàn)程越多,CPU在調(diào)度線(xiàn)程上的開(kāi)銷(xiāo)就越大。
    1. 程序設(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ū)別

四大多線(xiàn)程方案

內(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ù)量。

內(nèi)存創(chuàng)建空間需求

堆區(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ū)示意圖


內(nèi)存五大區(qū)

線(xiàn)程生命周期

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

線(xiàn)程生命周期
  1. 當(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í)行。
  2. 當(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)程池執(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ì)分析。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容