? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ios多線程詳解
一坚弱、前言
在ios中每個進(jìn)程啟動后都會建立一個主線程(UI進(jìn)程),這個線程是其他線程的父線程瓢喉。由于在ios中除了主線程,其他子線程都是獨(dú)立于Cocoa Touch的沪羔。多線程的實現(xiàn)有以下幾種方式:
NSThread:
(1)使用NSThread對象建立一個線程,非常方便翎碑。
(2)但是!使用NSThread管理多個線程非常困難,不推薦使用殖属。
GCD--Grand Central Dispatch:
(1)基于C語言的底層API颁糟。
(2)用block定義任務(wù),使用起來非常靈活方便铺董。
(3)提供了更多的控制能力以及操作隊列中所不能使用的底層函數(shù)统捶。
NSOperation/NSOperationQueue:
(1)是使用GCD實現(xiàn)的一套Object-C的API。
(2)是面向?qū)ο蟮木€程技術(shù)柄粹。
(3)提供了一些在GCD中不易實現(xiàn)的特性,如:限制最大并發(fā)數(shù)量、操作之間的依賴關(guān)系匆绣。
二驻右、線程與進(jìn)程
1.進(jìn)程
進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位,每一個進(jìn)程都有自己獨(dú)立的虛擬內(nèi)存空間。簡單來說,進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個應(yīng)用程序,每一個程序都是一個進(jìn)程,并且進(jìn)程之間是相互獨(dú)立的,每個進(jìn)程均運(yùn)行在其專業(yè)且受保護(hù)的內(nèi)存空間內(nèi)崎淳。
2.線程
是程序執(zhí)行流的最小單元堪夭,是系統(tǒng)獨(dú)立調(diào)度和分派CPU的基本單位。
一個進(jìn)程中至少有一條線程,即主線程。創(chuàng)建線程的目的就是為了開啟一條新的執(zhí)行路徑,運(yùn)行指定的代碼,與主線程中的代碼同時執(zhí)行森爽。
3.多線程
計算機(jī)同一個時間執(zhí)行多個線程,進(jìn)而提升整體處理性能恨豁。
原理:
(1)同一時間,CPU只能處理1條線程,只有一條線程在工作。
(2)多線程并發(fā)執(zhí)行,其實是CPU快速的在多條線程中切換(調(diào)度)爬迟。
(3)如果CPU調(diào)度線程的速度夠快橘蜜,就造成多線程同時執(zhí)行多假象。
優(yōu)點(diǎn):
(1)能適當(dāng)提高程序的執(zhí)行效率付呕。
(2)能適當(dāng)提高資源的利用率(CPU,內(nèi)存利用率)计福。
缺點(diǎn):
(1)開啟新線程需要占用一定的內(nèi)存空間(默認(rèn)情況下,主線程占1M,子線程占512K)。如果開啟大量的線程,會占用大量的內(nèi)存空間,降低程序的性能徽职。
(2)線程越多象颖,CPU在線程間切換(調(diào)度)上的開銷就越大。
注:主線程棧區(qū)的1M特別寶貴姆钉。不能殺掉一個線程!但可以暫停说订、休眠。
三潮瓶、NSThread的使用
1.線程的創(chuàng)建
NSThread創(chuàng)建線程有以下三種方法:
[NSThread detachNewThreadSelector:(nonnull SEL)> toTarget:(nonnull id) withObject:(nullable id)]
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(doSomething) object:nil];
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg
NSThread對象的常見屬性:
NSThread類方法:
(1)當(dāng)前線程:
int number = [NSThread currentThread];
number == 1 表示主線程陶冷,number != 1表示后臺線程
(2)阻塞方法:
休眠到指定時間
[NSThread sleepUntilDate:[NSDate date]];
休眠指定時長
[NSThread sleepForTimeInterval:4.5];
(3)其他類方法:
退出線程
[NSThread exit];
當(dāng)前線程是否為主線程
[NSThread isMainThread];
是否多線程
[NSThread isMultiThreaded];
返回主線程的對象
NSThread *mainThread = [NSThread mainThread];
2.線程的狀態(tài)
線程的狀態(tài)如下圖:
(1)新建:實例化對象
(2)就緒:向線程對象發(fā)送start消息,線程對象被加入"可調(diào)度線程池"等待CPU調(diào)度,detach方法和performSelectorInBackground方法會直接實例化一個線程對象并且加入"可調(diào)度線程池"筋讨。
(3)運(yùn)行:CPU負(fù)責(zé)調(diào)度"可調(diào)度線程池"中線程的執(zhí)行,線程完成執(zhí)行之前,其狀態(tài)可能在"就緒"和"運(yùn)行"之間切換埃叭。
(4)阻塞:當(dāng)滿足某個條件時,可以使用休眠或鎖阻塞線程執(zhí)行,方法有sleepForTimeInterval,sleepUntilDate,@synchronized(self)x線程鎖。線程進(jìn)入阻塞狀態(tài)下時,會被從"可調(diào)度線程池"中移出,CPU不再調(diào)度悉罕。
(5)死亡:死亡后線程對象的isFinished屬性為YES,如果是對線程發(fā)送cancel消息,線程對象的isCenceled屬性為YES,死亡后stackSize==0,內(nèi)存空間被釋放赤屋。
2.多線程的安全問題
多個線程訪問同一塊資源進(jìn)行讀寫,如果不加控制隨意訪問容易產(chǎn)生數(shù)據(jù)錯亂,從而引發(fā)數(shù)據(jù)安全問題。為了解決這一問題,就有了加鎖的概念壁袄。加鎖的原理就是當(dāng)有一個線程正在訪問資源進(jìn)行寫的時候类早,不允許其他線程再訪問該資源,只有當(dāng)該線程訪問結(jié)束后嗜逻,其他線程才按順序進(jìn)行訪問涩僻。對于讀取數(shù)據(jù),有些程序設(shè)計是允許多線程同時讀的,有些不允許。 UIKit中幾乎所有控件都不是線程安全的,因此需要在主線程中更新UI.
解決多線程安全問題:
(1)互斥鎖
?注意:鎖定1份代碼只用1把鎖栈顷,用多把鎖是無效的
@synchronized(鎖對象) { 需要鎖定的代碼? }
使用互斥鎖,在同一時間,只允許一條線程執(zhí)行鎖中的代碼逆日。因為互斥鎖的代價十分昂貴,所以鎖定的代碼范圍應(yīng)該盡可能吧小,只要鎖住資源讀寫部分的代碼即可萄凤。
(2)使用NSLock對象
(3)atomic加鎖
OC在定義屬性的時候有nonatomic和atomic兩種選擇室抽。
atomic:原子屬性,為setter方法加鎖(默認(rèn)就是atomic)靡努。線程安全,但需要消耗大量資源坪圾。
nonatomic:非原子屬性,不會為setter方法加鎖晓折。非線程安全,但效率高。
atomic加鎖原理:
ios開發(fā)的建議:
所有屬性都聲明nonatomic兽泄。
盡量避免多線程搶奪同一塊資源漓概。
盡量將加鎖、資源搶奪的業(yè)務(wù)邏輯交給服務(wù)器端處理病梢,減小移動端的壓力胃珍。
四、GCD的使用
GCD(Grand Central Dispatch)偉大的中央調(diào)度系統(tǒng),是蘋果為多核并行運(yùn)算提出的C語言并發(fā)技術(shù)框架飘千。GCD會自動利用更多的CPU內(nèi)核,會自動管理線程的生命周期(線程創(chuàng)建,調(diào)度任務(wù),線程銷毀),只需要告訴GCD想要如何執(zhí)行什么任務(wù),不需要編寫任何線程管理代碼堂鲜。
一些專業(yè)術(shù)語:
dispatch:調(diào)度/派遣
queue:隊列,用來存放任務(wù)的先進(jìn)先出(FIFO)的容器。
sync:同步函數(shù),只是在當(dāng)前線程中執(zhí)行任務(wù),不具備開啟新線程的能力护奈。
async:異步函數(shù),可以在新的線程中執(zhí)行任務(wù),具備開啟新線程的能力缔莲。
concurrent:并發(fā),多個任務(wù)同時進(jìn)行。
串行:一個任務(wù)執(zhí)行完畢后,再執(zhí)行下一個任務(wù)霉旗。
1.GCD中的核心概念:
任務(wù): 任務(wù)就是要在線程中執(zhí)行的操作痴奏。我們將要執(zhí)行的代碼用block封裝好,然后將任務(wù)添加到隊列容器中,并指定任務(wù)的執(zhí)行方式。等待CPU從隊列中取出任務(wù)放到對應(yīng)的線程中執(zhí)行厌秒。
隊列
串行隊列:一次只調(diào)度一個任務(wù),一個任務(wù)完成后再調(diào)度下一個任務(wù)读拆。
并發(fā)隊列:可以同時調(diào)度多個任務(wù),調(diào)度任務(wù)的方式,取決于執(zhí)行任務(wù)的函數(shù),并發(fā)功能只有在異步函數(shù)下才有效。異步情況下鸵闪,開啟的新線程極限數(shù)量由GCD底層決定檐晕。
如果在MRC下需要使用dispatch_release釋放隊列:
主隊列:負(fù)責(zé)在主線程上調(diào)度任務(wù),如果在主線程上有任務(wù)執(zhí)行,會等待主線程空閑后再進(jìn)行調(diào)度。主隊列用于UI以及觸摸事件等的操作蚌讼。
全局并發(fā)隊列: 由蘋果API提供的,方便程序員使用多線程辟灰。
全局并發(fā)隊列的優(yōu)先級:
define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高優(yōu)先級
define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默認(rèn)(中)優(yōu)先級
注意,自定義隊列的優(yōu)先級都是默認(rèn)優(yōu)先級
define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低優(yōu)先級
define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后臺優(yōu)先級
全局并發(fā)隊列與并發(fā)隊列的區(qū)別:
(1)全局并發(fā)隊列沒有隊列名稱篡石。
(2)在MRC中,全局并發(fā)隊列不需要手動釋放芥喇。
執(zhí)行任務(wù)的函數(shù)
(1)同步函數(shù)(dispatch_sync)
任務(wù)被添加到隊列后,隊列中的任務(wù)一個接著一個執(zhí)行。
在主線程中,向主隊列添加同步任務(wù),會造成死鎖凰萨。
在其他線程中,向主隊列添加同步任務(wù),則會在主線程中同步執(zhí)行继控。
(2)異步函數(shù)(dispatch_async)
GCD的其他用法
(1)延時執(zhí)行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
????// 2秒后異步執(zhí)行這里的代碼...
});
(2)一次性執(zhí)行
應(yīng)用場景:保證某段代碼在程序運(yùn)行過程中只被執(zhí)行一次,在單例模式中經(jīng)常被用到。
(3)調(diào)度組(隊列組)
五胖眷、NSOperation
NSOperation是蘋果推薦的并發(fā)技術(shù),它提供了一些GCD不是很好實現(xiàn)的功能武通。NSOperation是基于GCD的面向?qū)ο蟮腛C語言封裝。相比GCD,NSOperation的操作更簡單珊搀。NSOperation是一個抽象類厅须,不能直接使用,而是使用它的子類。蘋果為我們提供了其兩個子類:NSInvocationOperation,NSBlockOperation.以及繼承NSOperation的自定義子類食棕。
NSOperation的使用常常是配合NSOperationQueue來進(jìn)行的朗和。只要使用NSOperation子類創(chuàng)建的實例就能添加到NSOperationQueue中,一旦添加到隊列,操作就會自動異步執(zhí)行。如果沒有添加到隊列,而是使用start方法,則會在當(dāng)前線程中執(zhí)行簿晓。
(1)NSInvocationOperation
直接創(chuàng)建一個NSInvocationOperation對象,然后調(diào)用start方法會直接在主線程中執(zhí)行眶拉。
添加到NSOperationQueue中:
(2)NSBlockOperation
NSBlockOperation與NSInvocationOperation用法相同,只是創(chuàng)建的方式不同,它不需要去調(diào)用方法,而是直接使用代碼塊。這也使得NSBlockOperation比NSInvocationOperation更流行憔儿。
(3)NSOperationQueue的一些高級操作
NSOperationQueue的高級操作有:隊列的掛起,隊列的取消,添加操作的依賴關(guān)系和設(shè)置最大并發(fā)數(shù)量忆植。
最大并發(fā)數(shù):
線程的掛起:
取消隊列里的所有操作:
?六、三種線程技術(shù)比較
1.NSThread
優(yōu)點(diǎn):NSThread比其他兩個輕量級,使用簡單谒臼。
缺點(diǎn):需要管理自己的線程生命周期朝刊、加鎖、睡眠以及喚醒等蜈缤。
2.GCD
GCD是ios4.0以后才出現(xiàn)的并發(fā)技術(shù)
使用方式:將任務(wù)添加到隊列(串行/并行(全局))中,指定執(zhí)行任務(wù)的方法(同步函數(shù)sync,異步函數(shù)async)拾氓。
NSOperation無法做到的:延遲執(zhí)行,隊列組(NSOperation實現(xiàn)會比較復(fù)雜)。
3.NSOperation
NSOperation在ios2.0時就出現(xiàn)了(當(dāng)時不好用,后蘋果對其改造)
使用方式:將操作(異步執(zhí)行)添加到隊列(并發(fā)/全局)中底哥。
提供了GCD不好實現(xiàn)的功能:最大并發(fā)數(shù)咙鞍、取消所有任務(wù)、依賴關(guān)系趾徽。
GCD是比較底層的封裝续滋,我們知道較低層的代碼一般性能都是比較高的,相對于NSOperationQueue孵奶。所以追求性能疲酌,而功能夠用的話就可以考慮使用GCD。如果異步操作的過程需要更多的用戶交互和被UI顯示出來了袁,NSOperationQueue會是一個好選擇朗恳。如果任務(wù)之間沒有什么依賴關(guān)系,而是需要更高的并發(fā)能力早像,GCD則更有優(yōu)勢僻肖。
尾語:
高德納的教誨:“在大概97%的時間里,我們應(yīng)該忘記微小的性能提升卢鹦。過早優(yōu)化是萬惡之源臀脏。”只有Instruments顯示有真正的性能提升時才有必要用低級的GCD冀自。