多線程原理
線程和進程的關(guān)系和區(qū)別
1、線程定義
- 線程是進程的基本執(zhí)行單元,一個進程的所有任務(wù)都要在線程中執(zhí)行
- 進程要想執(zhí)行任務(wù),必須得有線程,進程至少要有一條線程
- 程序啟動默認會創(chuàng)建一條線程,這條線程被稱為主線程或者UI線程
2、進程定義
- 進程是指在系統(tǒng)中正在運行的一個應(yīng)用程序
- 每個進程之間是獨立的,每個進程運行在其專用的受保護的內(nèi)存中
3、進程和線程的區(qū)別
- 地址空間:同一進程的線程共享本進程的地址空間,而進程之間則是獨立的地址空間
- 資源擁有:同一進程內(nèi)的線程共享本進程的資源,如內(nèi)存、I/O、CPU等,但是進程之間的資源是獨立的。
- 可靠性:一個進程崩潰后,在保護模式下不會對其他進程產(chǎn)生影響,而一個線程崩潰整個進程都得死掉。所以多進程要比多線程健壯。
- 進程切換時,消耗的資源大,效率低。所以涉及到頻繁的切換時,使用線程要好于進程。同樣如果要求同時進行并且又要共享某些變量的并發(fā)操作,只能用線程不能用進程
- 執(zhí)行過程:每個獨立的進程有一個程序運行的入口和順序執(zhí)行序列。但是線程不能獨立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制。
- 線程是處理器調(diào)度的基本單位,但是進程不是。
| 對比維度 | 多進程 | 多線程 | 總結(jié) |
|---|---|---|---|
| 數(shù)據(jù)共享、同步 | 數(shù)據(jù)共享復(fù)雜,需要用IPC;數(shù)據(jù)是分開的,同步簡單 | 因為共享進程數(shù)據(jù),數(shù)據(jù)共享簡單,但也是因為這個原因?qū)е峦綇?fù)雜 | 各有優(yōu)勢 |
| 內(nèi)存、CPU | 占用內(nèi)存多,切換復(fù)雜,CPU利用率低 | 占用內(nèi)存少,切換簡單,CPU利用率高 | 線程占優(yōu) |
| 創(chuàng)建銷毀、切換 | 創(chuàng)建銷毀、切換復(fù)雜,速度慢 | 創(chuàng)建銷毀、切換簡單,速度很快 | 線程占優(yōu) |
| 編程、調(diào)試 | 編程簡單,調(diào)試簡單 | 編程復(fù)雜,調(diào)試復(fù)雜 | 進程占優(yōu) |
| 可靠性 | 進程間不會互相影響 | 一個線程掛掉將導(dǎo)致整個進程掛掉 | 進程占優(yōu) |
| 分布式 | 適應(yīng)于多核、多機分布式;如果一臺機器不夠,擴展到多臺機器比較簡單 | 適應(yīng)于多核分布式 | 進程占優(yōu) |
多線程的意義
- 優(yōu)點
- 能適當(dāng)提高程序的執(zhí)行效率
- 能適當(dāng)提高資源的利用率
- 線程上的任務(wù)執(zhí)行完成后,線程會自動銷毀
- 缺點
- 開啟線程需要占用一定的內(nèi)存空間(默認一個線程大小為512KB)
- 如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能
- 線程越多,CPU在調(diào)用線程上的開銷就越大
- 程序設(shè)計更加復(fù)雜,比如線程間的通信、多線程的數(shù)據(jù)共享
多線程的原理
CPU在不同的線程之間以一個非常小的時間片不斷的進行調(diào)度。
多線程技術(shù)方案
1、pthread
- 一套通用的多線程API
- 適用于Unix/Linux/Windows等系統(tǒng)
- 跨平臺/可移植
- 使用難度大
- C語言,需要手動管理線程生命周期
2、NSThread
- 使用更加面向?qū)ο?/li>
- 簡單易用,可直接操作線程對象
- OC語言,需要手動管理線程對象
3、GCD
- 蘋果為多核并行運算開發(fā)的解決方案
- 會自動利用更多的CPU內(nèi)核(比如雙核、四核)
- 程序員只需要將任務(wù)添加到隊列中,不需要編寫任何線程管理代碼
- OC語言,自動管理生命周期
4、NSOperation
- 基于GCD,但是比GCD多了一些簡單實用的功能
- 使用更加面向?qū)ο?/li>
- 可添加完成的代碼塊,在操作完成之后執(zhí)行
- 添加操作之間的依賴關(guān)系,方便的控制執(zhí)行順序
- 可以設(shè)定操作執(zhí)行的優(yōu)先級
- 可以很方便的取消一個操作的執(zhí)行
- 使用KVO觀察對操作執(zhí)行狀態(tài)的更改:isExecuteing、isFinished、isCancelled
- OC語言,手動管理生命周期
C與OC的橋接
- __bridge只做類型轉(zhuǎn)換,但是不修改對象(內(nèi)存)管理權(quán);
- __bridge_retained(也可以使用CFBridgingRetain)將Objective-C的對象轉(zhuǎn)換為Core Foundation的對象,同時將對象(內(nèi)存)的管理權(quán)交給Core Foundation的對象,后續(xù)使用CFRelease或者相關(guān)方法來釋放對象;
- __bridge_transfer(也可以使用CFBridgingRelease)將Core Foundation的對象轉(zhuǎn)換為Objective-C的對象,同時將對象(內(nèi)存)的管理權(quán)交給ARC。
線程生命周期
線程的生命周期分為5個部分,新建、就緒、運行、阻塞、死亡。

- 新建:實例化線程對象
- 就緒:向線程對象發(fā)送start消息,線程對象被加入可調(diào)度線程池等待CPU調(diào)度
- 運行:CPU負責(zé)調(diào)度可調(diào)度線程池中線程執(zhí)行。當(dāng)前線程任務(wù)執(zhí)行完畢后,進入就緒狀態(tài),即有一個短暫停留期,如果在這期間有新的任務(wù),則繼續(xù)執(zhí)行這個任務(wù),如果沒有新的任務(wù),則線程銷毀,等有下一個任務(wù)的時候,則開啟新的線程執(zhí)行。
- 阻塞:當(dāng)滿足某個預(yù)定條件時,可以使用休眠或鎖,阻塞線程執(zhí)行。sleepForTimeInterval(休眠指定時長),sleepUntilDate(休眠到指定日期),@synchronized(self):(互斥鎖)。
- 死亡:正常死亡,線程執(zhí)行完畢。非正常死亡,當(dāng)滿足某個條件后,在線程內(nèi)部中止執(zhí)行或者在主線程中止線程對象。
線程的exit、cancel和suspend操作區(qū)別。
- exit:一旦強行終止線程,后續(xù)的所有代碼都不會被執(zhí)行
- cancel:并不會直接取消線程,只是給線程對象添加isCancelled標(biāo)記
- suspend:掛起狀態(tài),通過resume,可以恢復(fù)線程的任務(wù)執(zhí)行

線程池的實現(xiàn)原理
線程池的實現(xiàn)原理如下圖所示:

線程安全與鎖
1、什么是線程安全?
????多線程操作共享數(shù)據(jù)不會出現(xiàn)想不到的結(jié)果就是線程安全的,否則,是線程不安全的。比如:多個線程同時訪問或讀取同一共享數(shù)據(jù),每個線程的讀到的數(shù)據(jù)都是一樣的,也就不存在線程不安全。如果多個線程對同一資源進行讀寫操作,那么每個線程讀到的結(jié)果就是不可預(yù)料的,線程是不安全的。
2、多線程鎖
????線程安全的解決方法就是加鎖 。
????鎖是最常用的同步工具。一段代碼段在同一個時間只能允許被一個線程訪問。
????不要將過多的其他操作代碼放到里面,否則一個線程執(zhí)行的時候另一個線程就一直在等待,就無法發(fā)揮多線程的作用了。下圖是iOS常用鎖的性能表。

具體各種鎖的介紹看文章:
1、iOS多線程-各種線程鎖的簡單介紹
2、iOS開發(fā)中的11種鎖整理
3、atomic與nonatomic的區(qū)別
- nonatomic 非原子屬性
- atomic 原子屬性(線程安全),針對多線程設(shè)計,默認值
- 保證同一時間只有一個線程能夠?qū)懭耄ǖ峭粫r間多個線程都可取值)
- atomic本身就有一把鎖(自旋鎖)
- 單寫多讀:單個線程寫入,多個線程可以讀取
- atomic:并非完全的線程安全,只有用setter方法寫入才是線程安全的,需要消耗大量資源
- nonatomic:非線程安全,適合內(nèi)存小的移動設(shè)備、
建議:
- 所有屬性都聲明為nonatomic
- 盡量避免多線程搶奪同一塊資源
- 盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理,減小移動客戶端的壓力
線程和runloop的關(guān)系
1、runloop與線程是一一對應(yīng)的,一個runloop對應(yīng)一個核心的線程,為什么說是核心的,是因為runloop是可以嵌套的,但是核心的只能有一個,他們的關(guān)系保存在一個全局的字典里。
2、runloop是來管理線程的,當(dāng)線程的runloop被開啟后,線程會在執(zhí)行完任務(wù)后進入休眠狀態(tài),有了任務(wù)就會被喚醒去執(zhí)行任務(wù)。
3、runloop在第一次獲取時被創(chuàng)建,在線程結(jié)束時被銷毀。
4、對于主線程來說,runloop在程序一啟動就默認創(chuàng)建好了。
5、對于子線程來說,runloop是懶加載的,只有當(dāng)我們使用的時候才會去創(chuàng)建,所以在子線程用定時器要注意:確保子線程的runloop被創(chuàng)建,不然定時器不會回調(diào)。
參考文章:
1、淺談多進程多線程的選擇
2、多線程原理分析
3、iOS多線程全套:線程生命周期,多線程的四種解決方案,線程安全問題,GCD的使用,NSOperation的使用
4、iOS線程通信和進程通信的例子(NSMachPort和NSTask,NSPipe)