-
Threads的替代方案:
- Operation Objects:是一個(gè)任務(wù)包裝器,這個(gè)會(huì)在非主線程執(zhí)行。這個(gè)包裝器隱藏了線程管理的細(xì)節(jié)吮播,讓用戶可以專注在線程本身上。
- GCD眼俊,GCD可以比用thread更高效的執(zhí)行任務(wù)意狠。
- Idle-time notifications:對(duì)于優(yōu)先級(jí)非常低的任務(wù),可以考慮使用Idle-time notification疮胖。
線程在時(shí)間上和空間上創(chuàng)建是需要代價(jià)的摄职,所以推薦在線程中去做非常多的重要工作或者建立run loop以允許復(fù)用一些顯示任務(wù)誊役。
Run loop是一片管理事件異步到達(dá)線程的基礎(chǔ)設(shè)施。
-
以下是保證你代碼正確性的實(shí)現(xiàn)線程的一些方法:
- 避免明確的建立線程谷市。預(yù)期手動(dòng)建立線程,不妨嘗試使用異步API击孩,GCD迫悠,operation objects來(lái)完成工作。
- 保持線程的忙碌:你應(yīng)該確保分配給線程的人物是長(zhǎng)周期的巩梢、多產(chǎn)的(不要浪費(fèi)).
- 避免共享數(shù)據(jù)結(jié)構(gòu):最簡(jiǎn)單的避免線程相關(guān)資源沖突的方法是給每一個(gè)線程它需要資源的copy创泄。平行代碼在線程間最小化通信以及溝通時(shí)效率達(dá)到最大。
- 推薦在主線程去接收用戶相關(guān)的事件和初始化UI括蝠。
- 留意在退出時(shí)的線程行為:進(jìn)程只有在非分派的線程退出時(shí)才會(huì)停止鞠抑。如果你在編寫CoCoa應(yīng)用,你應(yīng)當(dāng)使用applicationShouldTerminate:delegate方法去使得app在一段時(shí)間之后終止或者一起取消掉忌警。
- 處理Exceptions:一些情況下搁拙,exception處理這也許會(huì)被自動(dòng)創(chuàng)建,例如法绵,@synchronized標(biāo)簽就會(huì)默認(rèn)包含exception 處理器箕速。
- 干凈地終止你的線程。最佳的退出線程的方法就是讓它自然退出朋譬,讓它達(dá)到主鏈路的終點(diǎn)盐茎。
- 在Libraries中保證線程安全:對(duì)于Libraries 開(kāi)發(fā)者,不能只在app變成多線程時(shí)創(chuàng)建locks徙赢。如果要在某個(gè)線程中l(wèi)ock字柠,最好用libraries之前創(chuàng)建,推薦在創(chuàng)建library之前就用locks狡赐。切記Lock和unlock一定要配對(duì)窑业。在使 * 用Cocoa library時(shí),最好注冊(cè)一個(gè)觀察者接收NSwillBecomMuliThreadedNotification阴汇,這樣就在application變成多線程時(shí)接收到了通知数冬。
-
線程創(chuàng)建消耗:
- Kernal 數(shù)據(jù)結(jié)構(gòu):大概1KB
- 棧空間:512KB(非主線程);1MB(主線程)搀庶。最小的非主線程椆丈矗空間是16KB,并且棧空間必須是4KB的倍數(shù)哥倔。
- Creation時(shí)間:大概90毫秒秸架。
- 另一個(gè)消耗是成品消耗。所以說(shuō)咆蒿,應(yīng)當(dāng)盡量避免使用同步东抹,而且等待locks或者不作為更浪費(fèi)時(shí)間蚂子。
如果你有正在運(yùn)行的NSThread對(duì)象的化,一種可以send消息的方法是使用
performSelector:onThread:withObject:waitUntileDone:
方法缭黔。設(shè)置線程的検尘ィ空間。在Cocoa下馏谨,在調(diào)用start方法之前别渔,使用setStackSize:方法來(lái)制定stack的size。
配置Thread-Local的存儲(chǔ)惧互。在Cocoa下哎媚,你可以使用NSThread對(duì)象的threadDictionary方法去接收一個(gè)NSMutableDictionary對(duì)象,理論上就可以給thread添加任何keys了喊儡。
一般情況下拨与,將thread保留在其默認(rèn)值上。Cocoa Threads艾猜,你可以使用setThreadPriority:類方法(NSThread)來(lái)設(shè)置當(dāng)前運(yùn)行線程的優(yōu)先級(jí)买喧。
創(chuàng)建Autorelease Pool:如果一個(gè)app使用GC,而不是內(nèi)存管理模型箩朴,則創(chuàng)建autorelease pool并不是必須的岗喉。Autorelease pool在GC下并無(wú)害,但是大部分都會(huì)被忽略炸庞。Autorelease pool必須要支持managed model code钱床,并且如果app 運(yùn)行在gc下時(shí)會(huì)非常容易被忽略。所以如果你的app是運(yùn)行在managed memory model下埠居,創(chuàng)建一個(gè)autorelease pool應(yīng)當(dāng)是最先在thread實(shí)體路徑中做的事兒查牌。類似的,destory這個(gè)autorelease pool是在這個(gè)thread中最后一個(gè)要做的事滥壕。
因?yàn)閠op-level的autorelease pool在thread退出之前不會(huì)釋放它包含的對(duì)象纸颜,所以long-lived的thread應(yīng)當(dāng)新建額外的autorelease pool,去更頻繁的釋放對(duì)象绎橘。
當(dāng)你想運(yùn)行在不同的線程上時(shí),你有兩個(gè)選項(xiàng):第一個(gè)選項(xiàng)是將代碼寫在一個(gè)長(zhǎng)的task上称鳞,并且?guī)缀醪槐恢袛啵Y(jié)束時(shí)終止線程冈止;另一個(gè)選項(xiàng)是將線程放到一個(gè)loop中,在到達(dá)時(shí)動(dòng)態(tài)的執(zhí)行請(qǐng)求熙暴,這種方法需要建立這個(gè)線程的run loop.
終止線程:推薦的終止線程的方法是讓它自然的退出慌盯。雖然有killing方法掂器,但是嚴(yán)重不推薦使用。如果需要中途去終止一個(gè)線程唉匾,需要設(shè)計(jì)線程去從外部響應(yīng)cancel或者退出消息。對(duì)于長(zhǎng)線的操作,需要定期檢查是否有需要終止的線程芋簿。一種方式去取消msg是使用一個(gè)run loop 輸入源去接受這樣的msg。
while(moreWorkToDo && !exitNoew)
{
runLoop runUntilDate:[NSDate date];
exitNow =[ [threadDict valueForKey:@”ThreadShouldExitNow”] boolValue];
}
Run loop的目的是當(dāng)有工作要做時(shí)与斤,保持線程busy;當(dāng)沒(méi)有工作可做時(shí)讓線程sleep撩穿。Runloop,字如其名食寡,就是一個(gè)可以運(yùn)行事件處理器的loop,并且這個(gè)loop可以響應(yīng)未來(lái)的事件抵皱。
Runloop接收兩種不同類型的源善榛,一種是Input source,傳遞異步事件呻畸,接收自完全不同的application或者其它thread移盆;另一種是Timer source,傳遞同步事件伤为,在一個(gè)計(jì)劃好的時(shí)間或者重復(fù)的interval中出現(xiàn)咒循。
如上圖所示,input source會(huì)傳遞異步事件給響應(yīng)的處理函數(shù)并且觸發(fā)runUntilDate:方法來(lái)退出绞愚。 Timer source將事件傳遞到handler路徑上但是并不導(dǎo)致run loop退出叙甸。
除了處理input source之外,run loop還能生成關(guān)于run loop行為的通知爽醋。 注冊(cè) run-loop的觀察者可以接收這些通知蚁署,并且可以在線程上做一些額外的操作。
Run Loop的Modes:run loop的mode是input sources和需要監(jiān)聽(tīng)的集合蚂四,也是需要被通知的觀察者的集合光戈。每次運(yùn)行run loop時(shí)哪痰,你都指定了一種特殊的mode去運(yùn)行。傳給run loop之后久妆,只有符合mode條件的source才會(huì)被監(jiān)視和被允許傳遞事件晌杰。
Mode是用來(lái)從那些不想要的source中篩選出需要的sources。大部分時(shí)間筷弦,你會(huì)希望run loop運(yùn)行在default mode下肋演。一個(gè)模態(tài)面板,也許會(huì)運(yùn)行在“模態(tài)”模式下烂琴。在這種模式下爹殊,只有相關(guān)的source才會(huì)傳遞到那個(gè)thread上。對(duì)于非主線程而言奸绷,你也許使用custom modes來(lái)防止通過(guò)非常重要的實(shí)時(shí)的操作去傳遞低優(yōu)先級(jí)源梗夸。
-
定義好的run loop modes:
- Default:NSDefaultRunloopMode,默認(rèn)通過(guò)這個(gè)mode去配置和執(zhí)行你的input sources
- Connection:NSConnectionReplyMode号醉,這個(gè)很少用反症,用來(lái)倆節(jié)NSConnection對(duì)象以監(jiān)聽(tīng)回應(yīng)。
- Modal:NSModalPanelRunLoopMode畔派,Cocoa使用這個(gè)Mode來(lái)識(shí)別為了modal panel而用的事件。
- Event tracking:NSEventTrackingRunLoopMode胞谈,Cocoa使用這個(gè)mode去限制輸入事件呜魄,尤其在mouse-dragging loops或者其它類型的ui tracking loops中爵嗅。
- Common modes:NSRunLoopCommonModes笨蚁,這個(gè)modes是常用mode的集合。對(duì)于Cocoa 應(yīng)用伪很,這個(gè)集合包括default锉试,modal呆盖,和event tracking mode。 CoreFoundation 默認(rèn)包含default mode宙项,你可以添加custom modes 通過(guò)set CFRunLoopAddCommonMode函數(shù)來(lái)完成株扛。
- Input source: 輸入源會(huì)將事件異步的傳遞到你的線程中洞就。只要你的run loop建立了連接,input-source 是port-based還是custom就無(wú)所謂了改基。兩種類型唯一的區(qū)別就是被通知的方式不同。Port-based是通過(guò)kernal自動(dòng)的通知躁染,而custom source必須手動(dòng)通過(guò)其它線程來(lái)通知吞彤。
- Port-based Source: Cocoa和Core Fundation提供了內(nèi)置的創(chuàng)建port-based 輸入源的支持叹放,通過(guò)使用port-related 對(duì)象和函數(shù)井仰。在Core Foundation里俱恶,你必須手動(dòng)創(chuàng)建ports和它的run loop source.
- Custom Input Source: 創(chuàng)建一個(gè)custom input source,你必須使用和CFRunLoopSourceRef相關(guān)聯(lián)的function了罪。
- Cocoa Perform Selector Source:Cocoa定義了custom input source泊藕,允許你在任何線程上去執(zhí)行selector娃圆。當(dāng)selector執(zhí)行完畢,selector source會(huì)將自己從run loop中移除景醇。
-
Run Loop的事件執(zhí)行順序:
- 通知已經(jīng)進(jìn)入run loop的observer
- 通知那些timer準(zhǔn)備觸發(fā)的observer
- 通知那些不基于port的準(zhǔn)備觸發(fā)的input source的observer
- 觸發(fā)所有不基于port的input source
- 如果一個(gè)port-based 輸入源準(zhǔn)備好并等待fire三痰,則事件會(huì)立即執(zhí)行窜管。執(zhí)行第九步幕帆。
- 通知那些馬上要sleep的thread的observer
- 將thread帶入到sleep,除非:
i.一個(gè)基于port的input source事件來(lái)了
ii. Timer觸發(fā)了
iii. Run loop被叫醒了 - 通知observers常熙,thread已經(jīng)醒了
- 執(zhí)行進(jìn)行中的event
- 如果用戶定義的timer出發(fā)了裸卫,執(zhí)行timer事件并且重啟loop墓贿,跳到第二步
- 如果input source 觸發(fā)了聋袋,傳遞事件
- 如果run loop醒了但是沒(méi)有超時(shí)幽勒,重啟loop刀荒,跳到第二步
通知observer缠借,runloop退出了泼返。
如果這些events中間的時(shí)間非常寶貴,那么你可以使用sleep和awak-from-sleep通知來(lái)幫助你關(guān)聯(lián)這些真實(shí)事件之間的時(shí)間叫乌。
什么時(shí)候應(yīng)當(dāng)使用run loop憨奸? 只有在你為你的應(yīng)用創(chuàng)建非主線程的時(shí)候運(yùn)行一個(gè)run loop凿试。在非主線程上那婉,你應(yīng)當(dāng)決定是否應(yīng)該使用run loop详炬。如果應(yīng)該,則自己配置并且start在跳。你不應(yīng)該start 一個(gè)線程的run loop硬毕,什么時(shí)候都不應(yīng)該礼仗。
-
如果你想做下列事情元践,你應(yīng)該去start一個(gè)run loop:
- 使用ports或者custom input source去和其它線程交流
- 在thread上使用timer
- 在Cocoa application上使用performSelector…
- 讓線程去執(zhí)行間歇性的tasks
停止一個(gè)線程单旁,最好讓它自然停止象浑,而不是強(qiáng)制終止愉豺。
Start一個(gè)run loop:start一個(gè)run loop只有在你的app里的非主線程中才有必要蚪拦。一個(gè)run loop必須至少有一個(gè)input source或者timer來(lái)監(jiān)聽(tīng)驰贷。
-
有幾種方法可以start一個(gè)run loop,包括:
- 無(wú)條件的
- 設(shè)置一個(gè)時(shí)間限制
- 在特殊的mode中
無(wú)條件的運(yùn)行你的run loop指的是將thread放到一個(gè)永恒的loop中次兆,你會(huì)獲得非常有限的控制run loop的權(quán)利芥炭。你可以添加或者刪除input source或者timer蚤认,但是stop這個(gè)run loop的唯一方式就是kill掉它砰琢。沒(méi)辦法在custom mode下運(yùn)行run loop陪汽。
與無(wú)條件運(yùn)行run loop相比挚冤,給一個(gè)時(shí)間限制更好赞庶。
-
退出run loop:有兩種方法可以在運(yùn)行一個(gè)事件前使得run loop退出歧强,分別是:
- 給一個(gè)timeout值
- 告訴run loop停止。
- 其中肤京,給一個(gè)timeout值是推薦的忘分,前提是你可以管理它妒峦。
- 其次舟山,雖然移除run loop的input source和timers也許也能導(dǎo)致run loop退出,但是這不是一個(gè)停止run loop的可靠的方法寒矿。
Cocoa NSRunLoop雷并不像Core Foundation副本一樣符相,是一個(gè)線程安全的蠢琳。
-
同步方法:
- Atomic Operations:原子操作指的是一些形式簡(jiǎn)單的同步,以簡(jiǎn)單的數(shù)據(jù)類型為媒介蓝牲。原子操作的優(yōu)勢(shì)是他們不會(huì)block或者競(jìng)爭(zhēng)資源例衍。
- Memory Barriers 是一種非block型同步工具佛玄,通常用來(lái)確保memory操作以正確的順序執(zhí)行累澡。
- Locks:可以使用Locks來(lái)保護(hù)code的重要部分愧哟,以確保在同一個(gè)時(shí)間段只有一個(gè)代碼段可以被執(zhí)行。
-
Lock類型有:
- Mutex:是一種確保某時(shí)間只有一個(gè)thread可以運(yùn)行的信號(hào)量圈驼。
- Recursive lock:recursive lock主要用在遞歸操作之中,但是也會(huì)用在多個(gè)線程中每一個(gè)線程都分別需要同一個(gè)lock的情況橄抹。
- Read-write lock:這種lock可以用在大范圍的操作中楼誓,而且可以顯著的提高性能名挥,尤其當(dāng)保護(hù)的數(shù)據(jù)結(jié)構(gòu)經(jīng)常被讀但是偶爾被寫。(POSIX ONLY)
Conditions:Conditions最常用在確定資源是否可用或者確保tasks以一種特定order呈現(xiàn)参淫。當(dāng)一個(gè)thread tests 一個(gè)condition愧杯,它只會(huì)讓condition為ture的運(yùn)行力九,否則block跌前。Condition和mutex lock之間的區(qū)別在于多線程有可能在同一時(shí)間被允許執(zhí)行condition抵乓。 一種你有可能用到condition的情況是管理正在進(jìn)行的事件池。
使用多線程要考慮性能損耗章鲤。也就是說(shuō)败徊,多線程下同樣的代碼掏缎,要比單線程下同樣的代碼執(zhí)行效率要低眷蜈,這個(gè)很難提升酌儒。
*Thread-Safe Designs:避免同時(shí)同步:對(duì)于你工作中的新項(xiàng)目忌怎,或者已經(jīng)存在的項(xiàng)目卢鹦,設(shè)計(jì)時(shí)讓你的代碼和數(shù)據(jù)結(jié)構(gòu)盡量避免同步,是最好的解決方式鸥印。如果每一個(gè)操作都有自己的數(shù)據(jù)集,哪也不需要用到lock來(lái)保護(hù)數(shù)據(jù)狂鞋。即便是兩個(gè)tasks用到了同一個(gè)數(shù)據(jù),那也可以看看有沒(méi)有辦法將數(shù)據(jù)進(jìn)行分割构回,或者給每一個(gè)tasks對(duì)應(yīng)的copy對(duì)象纤掸。小心活鎖或者死鎖:最好的避免活鎖或者死鎖的方法就是在一個(gè)時(shí)間只使用一個(gè)lock浑塞。如果你需要在同一時(shí)間用到多于一種鎖酌壕,那確保其它threads不要在這個(gè)時(shí)間內(nèi)做類似的事情。
正確的使用volatile變量:如果你已經(jīng)用到了mutex來(lái)保護(hù)一段代碼果港,不要想當(dāng)然的認(rèn)為你需要使用volatile關(guān)鍵字來(lái)保護(hù)重要的變量辛掠。Volatile變量強(qiáng)制每次從內(nèi)存讀取而非寄存器讀取萝衩,這兩個(gè)一起使用會(huì)導(dǎo)致性能出現(xiàn)問(wèn)題猩谊。* 如果mutex自己就足夠保護(hù)變量牌捷,則不要用volatile關(guān)鍵字暗甥。當(dāng)然,也不要為了避免使用mutex就去使用volatile鸿市。一般來(lái)說(shuō),使用mutex或者其它同步機(jī)制比使用volatile變量要好得多剥懒。Volatile關(guān)鍵字只能保證變量從memory中讀取初橘,而不是通過(guò)寄存器讀取
使用Atomic Operations: 雖然Locks是一種在線程中同步的有效方法保檐,使用lock也是很消耗資源的操作夜只,即便是在無(wú)競(jìng)爭(zhēng)的case中蒜魄。相比較谈为,很多atomic operation使用碎片時(shí)間來(lái)完成工作伞鲫,所以可以作為有效率的lock榔昔。 * Atomic operation允許你執(zhí)行簡(jiǎn)單的數(shù)學(xué)操作和邏輯運(yùn)算,在32-bit值或者64bit值上嘹朗。例如Add屹培、Increment怔檩、Decrement媒吗、Logical AND等等闸英。
使用NSLock類:tryLock方法會(huì)嘗試獲取lock,但是如果lock不可得出吹,不會(huì)block状防巍;取而代之的是竹勉,方法會(huì)返回一個(gè)NO次乓。lockBeforeDate方法會(huì)在特定時(shí)間內(nèi)獲取鎖,如果成功返回YES孽水,否則unlock并返回NO票腰。
使用@synchronized關(guān)鍵字:@synchronized關(guān)鍵字做了其它mutex lock做的事:防止不同的線程在同一時(shí)間訪問(wèn)同一個(gè)資源。通過(guò)傳給@synchronized表達(dá)式一個(gè)id女气,它就可以區(qū)分保護(hù)的block杏慰。
-
其它Cocoa Locks:
- NSRecursiveLock對(duì)象: RecursiveLock對(duì)象定義了一個(gè)可以被同一個(gè)線程使用多次的lock,而且不會(huì)導(dǎo)致死鎖炼鞠。顧名思義缘滥,這種鎖通常用來(lái)在遞歸方法里防止遞歸不會(huì)鎖住線程時(shí)使用。
- NSConditionLock對(duì)象:NSConditoinLock對(duì)象定義了一個(gè)mutex lock谒主,只在value值為制定值時(shí)lock和unlock朝扼。 一般來(lái)說(shuō)擎颖,你在使用NSConditionLock時(shí)都是當(dāng)threads需要在特定的順序執(zhí)行,比如一個(gè)線程生產(chǎn)一個(gè)線程消費(fèi)。所以消費(fèi)者只在值為想要的值時(shí)鎖定住生產(chǎn)者荤崇。
- NSDistributedLock對(duì)象:可以被用在多個(gè)app在多個(gè)hosts上每篷,為了限制對(duì)于同一個(gè)共享資源的訪問(wèn)舱权,比如file文件。 NSDistribultedLock不遵循NSLocking協(xié)議,因此沒(méi)有l(wèi)ock方法。NSDistributedLock提供了tryLock方法讓你決定是否繼續(xù)進(jìn)行羡亩。因?yàn)槔^承自文件系統(tǒng)及志,NSDistributedLock對(duì)象不會(huì)釋放迫卢,除非owner自己釋放每界。如果你的app crash了,之前恰好hold一個(gè)distributedLock,那其它客戶端可能就無(wú)法訪問(wèn)保護(hù)的資源了。這時(shí)候疏虫,你可以使用breakLock方法去break已經(jīng)存在的lock官扣。
-
可變變量VS不可變變量:
- 不可變量一般來(lái)說(shuō)是線程安全的焊唬,一旦你創(chuàng)建了他們,你可以從線程獲取或者傳給線程這些變量。
- 可變對(duì)象一般來(lái)說(shuō)是線程不安全的。
- 即使一個(gè)方法聲稱要返回一個(gè)不可變的變量,你不能單純的以為變量就是不可變的。如果想要確保變量是不可變的,最好進(jìn)行一步immutable copy操作。
Class初始化:OC runtime系統(tǒng)會(huì)發(fā)送initialize消息給每一個(gè)class對(duì)象,這個(gè)過(guò)程遭遇class接收其它消息乖仇。這個(gè)方法建立了runtime 環(huán)境警儒,在它被正式使用之前。在多線程app里,runtime 確保只有一個(gè)線程執(zhí)行initialize方法,這個(gè)線程就是哪個(gè)發(fā)送給class initilaze方法的那個(gè)線程。
RunLoops:每一個(gè)線程有且自由一個(gè)run loop。如果你的app是基于Application Kit的,那么主run loop會(huì)自動(dòng)運(yùn)行夫嗓。但是非主線程(和foundation-only app)必須自己執(zhí)行run loop.
-
NSView使用限制(Mac下窍株,和UIView是對(duì)應(yīng)的):
- 你應(yīng)當(dāng)創(chuàng)建冒滩、銷毀、改變大小、移動(dòng)并且執(zhí)行其他操作時(shí)寻咒,務(wù)必保證NSView對(duì)象在主線程之上限煞。
- 如果非主線程上的ap想要讓view的部分內(nèi)容在主線程上redraw健霹,不應(yīng)該使用像display、setNeedsDisplay:垒棋,setNeedsDisplayInRect:或者是setViewsNeedDisplay等方法乍构。相反,應(yīng)該發(fā)消息給主線程或者通過(guò)performSelectorOnMainThread:withObject:waitUntilDown:方法來(lái)完成
錯(cuò)誤的使用graphics states會(huì)導(dǎo)致繪畫的效率低于在主線程上繪畫的效率扔茅。
NSGraphicsContext使用限制:如果你在非主線程做任何drawing萤晴,一個(gè)NSGraphicsContext實(shí)例會(huì)被創(chuàng)建殖演,而且是特別為那個(gè)線程量身制作座硕。如果在非主線程做任何drawing诸尽,需要手動(dòng)的清理你的drawing調(diào)用。Cocoa不會(huì)在非主線程上自動(dòng)更新view的內(nèi)容,所以你需要調(diào)用flushGraphics方法(NSGraphicsContext)當(dāng)你完成drawing粪薛。如果你的app只在主線程draw內(nèi)容搏恤,那么不需要你調(diào)用flush俏扩。