由于文章長(zhǎng)度限制,本文作為[譯]線程編程指南(一)后續(xù)部分。
Run Loops
Run loop是與線程相關(guān)的基礎(chǔ)結(jié)構(gòu)之一。Run loop是一個(gè)用來(lái)調(diào)度工作并接收事件的事件處理循環(huán)。Run loop其目的在于有任務(wù)的時(shí)候讓線程保持忙碌狀態(tài)而無(wú)事可做時(shí)讓線程睡眠。
Run loop管理并不是完全自動(dòng)的。你還必須設(shè)計(jì)出線程代碼在適當(dāng)?shù)臅r(shí)間啟動(dòng)run loop,并響應(yīng)傳入的事件。Cocoa和Core Foundation提供run loop對(duì)象以幫助你配置和管理線程的run loop。應(yīng)用程序不需要顯式地創(chuàng)建這些對(duì)象;每一個(gè)線程,包括應(yīng)用程序的主線程,都有一個(gè)相關(guān)的run loop對(duì)象。只有輔助線程才需要顯式地運(yùn)行它們的run loop。應(yīng)用程序會(huì)自動(dòng)設(shè)置和運(yùn)行主線程的run loop并將其作為程序啟動(dòng)過(guò)程的一部分。
以下各節(jié)提供有關(guān)run loop的更多信息,以及如何在應(yīng)用程序中配置它們。
剖析Run Loop
Run loop正如其名稱一樣,它是一個(gè)讓線程進(jìn)入并用來(lái)運(yùn)行事件回調(diào)的循環(huán)。你的代碼提供的控制語(yǔ)句會(huì)用于實(shí)現(xiàn)run loop中的實(shí)際循環(huán)部分,換句話說(shuō),你的代碼中的while和for循環(huán)驅(qū)動(dòng)著run loop的運(yùn)行。在run loop內(nèi),你將使用run loop對(duì)象來(lái)驅(qū)動(dòng)事件處理代碼執(zhí)行以接收事件和調(diào)用處理回調(diào)。
Run loop通過(guò)兩種源來(lái)接收事件。輸入源傳遞異步的事件,通常是其他線程和另一個(gè)應(yīng)用傳入。定時(shí)源傳遞同步事件,發(fā)生在調(diào)度時(shí)期或者重復(fù)周期。**當(dāng)相關(guān)事件到達(dá)時(shí)兩種類型的源都會(huì)使用應(yīng)用指定的回調(diào)例程來(lái)處理事件。
圖3-1展示了run loop及其不同類型源的概念結(jié)構(gòu)。輸入源傳遞異步事件給相應(yīng)的回調(diào)并引起runUntilDate:方法調(diào)用(實(shí)際由線程的NSRunLoop對(duì)象調(diào)用)以退出run loop。定時(shí)源傳遞事件給回調(diào)例程但并不導(dǎo)致run loop退出。
圖3-1 Run loop及其源結(jié)構(gòu)
[圖片上傳失敗...(image-725270-1536734662071)]
除了處理輸入源之外,run loop同樣會(huì)為其行為創(chuàng)建通知。注冊(cè)一個(gè)run loop的觀察者可以獲取到這些通知并使用它們?cè)诰€程中做進(jìn)一步的操作。
以下章節(jié)將提供有關(guān)run loop構(gòu)成和該模式如何操作的更多細(xì)節(jié)。同樣會(huì)描述事件處理的不同階段通知的創(chuàng)建過(guò)程。
Run Loop模式
一個(gè)run loop的模式包括了一系列待監(jiān)測(cè)的輸入源、定時(shí)源以及待通知的觀察者。每次運(yùn)行run loop時(shí),都會(huì)(顯式或隱式地)為其指定一個(gè)運(yùn)行“模式”。在run loop過(guò)程中,只監(jiān)測(cè)與該模式相關(guān)聯(lián)的源,并允許相關(guān)的事件傳遞。(類似地,只有與該模式相關(guān)聯(lián)的觀察者通知run loop執(zhí)行)與其他模式相關(guān)聯(lián)來(lái)源于任何新事件的消息,直到隨后在適當(dāng)模式下才進(jìn)入循環(huán)。
在代碼當(dāng)中,使用名稱來(lái)定義模式。Cocoa和Core Foundation定義了默認(rèn)模式和幾個(gè)常用模式,在代碼中都是以字符串的形式表示。你也可以定義一個(gè)自定義模式并用字符串為模式命名。雖然自定義模式的名稱是任意的,但模式的內(nèi)容卻不是。你必須確認(rèn)為其加入一個(gè)或多個(gè)輸入源、定時(shí)源、觀察者才會(huì)使其產(chǎn)生真正的效果。
你可以使用模式來(lái)過(guò)濾掉run loop中不感興趣的事件。多數(shù)情況下,你會(huì)希望run loop運(yùn)行在系統(tǒng)默認(rèn)定義的“default”模式下。對(duì)于“模態(tài)面板”,可能會(huì)運(yùn)行在“模態(tài)”模式下。當(dāng)進(jìn)入該模式時(shí),只有源相關(guān)的模態(tài)面板才能在線程中傳遞事件。對(duì)于輔助線程而言,你需要使用自定義模式來(lái)防止低優(yōu)先級(jí)源在時(shí)間敏感的操作中傳遞事件。
注意:模式基于事件的源區(qū)分而不是事件類型。例如,你不能使用模式來(lái)僅匹配鼠標(biāo)點(diǎn)擊事件或者鍵盤事件。你應(yīng)該使用模式來(lái)監(jiān)聽不同的端口,偶爾掛起定時(shí)器,或者切換當(dāng)前被監(jiān)測(cè)源和觀察者。
表3-1列舉了Cocoa和Core Foundation中的標(biāo)準(zhǔn)模式及其如何使用的描述。名稱字段列舉了在代碼中使用該模式的名稱常量。
表3-1 預(yù)定義的run loop模式
| 模式 | 名稱 | 描述 |
|---|---|---|
| 默認(rèn)(Default) | NSDefaultRunLoopMode (Cocoa)、kCFRunLoopDefaultMode (Core Foundation) | 大多數(shù)操作使用的默認(rèn)模式。大多數(shù)時(shí)間,你會(huì)使用該模式啟動(dòng)run loop并配置輸入源。 |
| 連接(Connection) | NSConnectionReplyMode (Cocoa) | Cocoa使用該模式來(lái)連接NSConnection對(duì)象以監(jiān)視其回應(yīng)。你自身很少會(huì)用到這種模式。 |
| 模態(tài)(Modal) | NSModalPanelRunLoopMode (Cocoa) | Cocoa使用該模式來(lái)追蹤相關(guān)模態(tài)面板的事件。 |
| 事件追蹤(Event tracking) | NSEventTrackingRunLoopMode (Cocoa) | Cocoa使用該模式來(lái)限制其他事件進(jìn)入當(dāng)鼠標(biāo)拖動(dòng)期間以及其他用戶界面跟蹤期間。 |
| 通用模式(Common modes) | NSRunLoopCommonModes (Cocoa)、kCFRunLoopCommonModes (Core Foundation) | 這是一個(gè)可配置的通用模式組。為其中的一個(gè)模式關(guān)聯(lián)輸入源的同時(shí)會(huì)為該組中的所有模式關(guān)聯(lián)該輸入源。對(duì)于Cocoa應(yīng)用,這個(gè)集合默認(rèn)包含了上述全部模式。對(duì)于Core Foundation來(lái)講則最初只包含了默認(rèn)模式。你可以使用CFRunLoopAddCommonMode函數(shù)向其添加自定義模式。 |
輸入源
輸入源在你的線程中異步地傳遞事件。事件的來(lái)源取決于輸入源的類型,通常是兩種中的一個(gè)類型。基于端口的輸入源在你應(yīng)用的Mach端口上進(jìn)行監(jiān)聽。自定義的輸入源監(jiān)聽自定義的事件源。只要是涉及到run loop,一個(gè)輸入源是基于端口的或是自定義的并不重要。系統(tǒng)通常會(huì)實(shí)現(xiàn)兩種類型以供使用。這兩種源唯一區(qū)別在于它們通知方式不同?;诙丝诘脑从蓛?nèi)核自動(dòng)通知,自定義的源需要從其他線程手動(dòng)通知。
當(dāng)你創(chuàng)建完輸入源之后,可以將其指定到run loop的一個(gè)或多個(gè)模式下。在任何時(shí)刻模式都會(huì)影響到輸入源的受監(jiān)視情況。在大多數(shù)時(shí)間,你會(huì)在默認(rèn)模式下運(yùn)行run loop,但是你仍可以指定到自定義模式下運(yùn)行。如果輸入源不在當(dāng)前監(jiān)視的模式下,任何它產(chǎn)生的事件會(huì)被保留直到下一次run loop運(yùn)行到正確的模式。
以下具體描述了各種類型的輸入源。
基于端口的輸入源
Cocoa和Core Foundation提供了使用基于端口的對(duì)象和函數(shù)來(lái)創(chuàng)建基于端口輸入源的內(nèi)建支持。例如,在Cocoa中,你不必直接創(chuàng)建輸入源。你僅需使用NSPort的方法創(chuàng)建端口對(duì)象并將該端口添加到run loop。端口對(duì)象會(huì)為你處理創(chuàng)建和配置所需的輸入源。
在Core Foundation中,你必須同時(shí)手動(dòng)創(chuàng)建端口和run loop源。在這種情況下,你可以使用端口相關(guān)的不透明類型(CFMachPortRef,CFMessagePortRef,或CFSocketRef)的函數(shù)來(lái)創(chuàng)建合適的對(duì)象。
自定義輸入源
為創(chuàng)建自定義輸入源,你必須使用Core Foundation中CFRunLoopSourceRef類型相關(guān)的函數(shù),并使用多個(gè)不同的回調(diào)函數(shù)來(lái)配置自定義的輸入源。Core Foundation調(diào)用這些函數(shù)來(lái)配置源,處理任何到達(dá)事件,以及當(dāng)源從run loop移除時(shí)銷毀它們。
當(dāng)事件到達(dá)時(shí),定義自定義源的行為的同時(shí),你必須定義事件的傳遞機(jī)制。源的這部分運(yùn)行在一個(gè)單獨(dú)的線程中,負(fù)責(zé)為輸入源提供數(shù)據(jù),并在數(shù)據(jù)準(zhǔn)備好時(shí)發(fā)出信號(hào)。事件傳遞機(jī)制是取決于需求而不必過(guò)于復(fù)雜。
Cocoa執(zhí)行選擇器源
除了基于端口的輸入源之外,Cocoa定義了一種可以允許在任何線程上執(zhí)行選擇器的自定義輸入源。同基于端口的輸入源類似,執(zhí)行選擇器的請(qǐng)求被序列化到目標(biāo)線程,緩解了同一線程上運(yùn)行的多個(gè)方法間可能存在的同步問(wèn)題的產(chǎn)生。與基于端口的源不同的是,執(zhí)行選擇器的源會(huì)在執(zhí)行完選擇器之后將自身從run loop上移除。
注意:早在OS X 10.5之前,執(zhí)行選擇器源被主要用于主線程的消息發(fā)送上,但在OS X 10.5之后以及iOS上,你可以使用它們向任何線程發(fā)送消息。
當(dāng)在另一個(gè)線程上執(zhí)行選擇器時(shí),目標(biāo)線程必須有一個(gè)活動(dòng)的run loop。這意味著線程一旦創(chuàng)建,你的代碼就必須顯式地開啟一個(gè)run loop。由于主線程會(huì)開啟它自身的run loop,然而,你也可以在應(yīng)用的委托回調(diào)方法applicationDidFinishLaunching:完成該調(diào)用。Run loop會(huì)在每次通過(guò)循環(huán)時(shí)完成所有隊(duì)列中的選擇器調(diào)用,而不是在每次循環(huán)中只處理一個(gè)。
表3-2列舉了NSObject中可用于在其他線程上執(zhí)行選擇器的方法。由于這些方法都由NSObject聲明,所以你能夠在任何能訪問(wèn)到Objective-C對(duì)象的線程上使用它們,包括POSIX線程。這些方法事實(shí)上不會(huì)創(chuàng)建新的線程來(lái)執(zhí)行選擇器。
表3-2 在其他線程上執(zhí)行選擇器
| 名稱 | 描述 |
|---|---|
performSelectorOnMainThread:withObject:waitUntilDone:performSelectorOnMainThread:withObject:waitUntilDone:modes:
|
在應(yīng)用主線程的下一個(gè)run loop周期內(nèi)執(zhí)行指定的選擇器。該方法會(huì)阻塞當(dāng)前線程直到選擇器執(zhí)行完畢。 |
performSelector:onThread:withObject:waitUntilDone:performSelector:onThread:withObject:waitUntilDone:modes:
|
在任何一個(gè)NSThread對(duì)象的線程上執(zhí)行指定選擇器。該方法會(huì)阻塞當(dāng)前線程直到選擇器執(zhí)行完畢。 |
performSelector:withObject:afterDelay:performSelector:withObject:afterDelay:inModes:
|
在當(dāng)前線程的下一個(gè)run loop周期內(nèi)的選擇性延遲時(shí)間后執(zhí)行指定的選擇器。由于它會(huì)等到下一個(gè)run loop的周期才執(zhí)行,所以該方法提供了一個(gè)從當(dāng)前起自動(dòng)的最小延遲執(zhí)行時(shí)間。隊(duì)列中的多個(gè)選擇器在排列好后會(huì)依次執(zhí)行。 |
cancelPreviousPerformRequestsWithTarget:cancelPreviousPerformRequestsWithTarget:selector:object:
|
取消上一個(gè)執(zhí)行請(qǐng)求。 |
定時(shí)源
定時(shí)源在將來(lái)的一個(gè)預(yù)定時(shí)間向線程同步地傳遞事件。定時(shí)器作為線程通知自身完成某些工作的一種方式。例如,搜索框一旦在用戶連續(xù)輸入關(guān)鍵字間隙可以使用計(jì)時(shí)器來(lái)發(fā)起自動(dòng)搜索。在開始搜索之前,搜索操作的延時(shí)可以讓用戶盡可能多的輸入所需的關(guān)鍵字符串。
盡管定時(shí)源創(chuàng)建了基于時(shí)間的通知,但定時(shí)器并不是一個(gè)實(shí)時(shí)機(jī)制。類似輸入源,定時(shí)器與run loop的具體模式關(guān)聯(lián)。如果定時(shí)器在run loop模式中當(dāng)前沒有處于被監(jiān)控狀態(tài),它會(huì)直到run loop運(yùn)行到支持的模式下才開啟。同理,如果定時(shí)器在run loop執(zhí)行到回調(diào)中是觸發(fā),該定時(shí)器會(huì)等到下一次進(jìn)入run loop時(shí)才回去調(diào)用回調(diào)。如果run loop根本就沒有運(yùn)行,定時(shí)器永遠(yuǎn)不會(huì)被觸發(fā)。
你可以創(chuàng)建定時(shí)器以一次或者重復(fù)多次地產(chǎn)生事件。重復(fù)性的定時(shí)器會(huì)自動(dòng)基于其調(diào)度觸發(fā)時(shí)間重新調(diào)度,而并不是實(shí)際的觸發(fā)時(shí)間。例如,如果一個(gè)定時(shí)器原定于每5秒完成一次觸發(fā),調(diào)度觸發(fā)時(shí)間總是落在原有的5秒間隔上,即便實(shí)際的觸發(fā)時(shí)間產(chǎn)生了延遲。如果觸發(fā)時(shí)間被延遲過(guò)多則會(huì)導(dǎo)致其錯(cuò)過(guò)一次或多次的調(diào)度觸發(fā)機(jī)會(huì),使得定時(shí)器在某一段時(shí)間內(nèi)只觸發(fā)了一次。在調(diào)度丟失的時(shí)間完成觸發(fā)之后,定時(shí)器會(huì)在下一次調(diào)度觸發(fā)時(shí)間重新調(diào)度。
Run Loop觀察者
對(duì)比那些在合適時(shí)間觸發(fā)或同步或異步的事件的源,run loop觀察者則在run loop自身執(zhí)行的特定位置觸發(fā)事件。你可以使用run loop觀察者對(duì)線程的既定事件做準(zhǔn)備或者在線程睡眠之前做好準(zhǔn)備。你可以在run loop中為觀察者關(guān)聯(lián)如下事件:
- Run loop進(jìn)入。
- Run loop將要處理定時(shí)器。
- Run loop將要處理輸入源。
- Run loop將要睡眠。
- Run loop將要喚醒,但在其處理事件之前。
- Run loop退出。
你可以使用Core Foundation提供的方式來(lái)為應(yīng)用創(chuàng)建run loop觀察者。為創(chuàng)建run loop觀察者,你需要?jiǎng)?chuàng)建CFRunLoopObserverRef類型的實(shí)例。該類型會(huì)追蹤自定義的回調(diào)函數(shù)以及感興趣的活動(dòng)。
和定時(shí)器一樣,觀察者可以被一次性或重復(fù)性的使用。一次性觀察者在觸發(fā)之后會(huì)將自己從run loop中移除,而重復(fù)執(zhí)行的觀察者則繼續(xù)保留。你可以在觀察者創(chuàng)建時(shí)指定其是一次性的還是重復(fù)性的。
Run Loop事件隊(duì)列
在每次run loop運(yùn)行時(shí),線程的run loop執(zhí)行掛起事件并為任何關(guān)聯(lián)的觀察者創(chuàng)建通知。這些事件的通知順序如下:
- 通知觀察者run loop進(jìn)入。
- 通知觀察者就緒的定時(shí)器即將觸發(fā)。
- 通知觀察者non-port-based(非基于端口)輸入源即將觸發(fā)。
- 觸發(fā)已就緒的non-port-based輸入源。
- 如果基于端口的輸入源等到觸發(fā),立即處理該事件。跳到步驟9。
- 通知觀察者線程即將睡眠。
- 保持線程睡眠直到下列其中一個(gè)事件發(fā)生:
- 基于端口的輸入源事件到達(dá)。
- 定時(shí)器觸發(fā)。
- Run loop超時(shí)。
- Run loop被顯式地喚醒。
- 通知觀察者線程喚醒。
- 處理掛起事件。
- 如果用戶定義的定時(shí)器觸發(fā),處理定時(shí)器事件并重啟run loop。跳到步驟2。
- 如果輸入源觸發(fā),傳遞該事件。
- 如果run loop顯式地喚醒且并不超時(shí),重啟run loop。跳到步驟2。
- 通知觀察者run loop已退出。
由于觀察者對(duì)定時(shí)器和輸入源的通知是在相應(yīng)事件實(shí)際發(fā)生之前傳遞的,所以通知的時(shí)間和事件實(shí)際發(fā)生的時(shí)間可能存在間隔。如果這些事件之間的時(shí)間至關(guān)重要,你可以使用sleep和awake-from-sleep通知來(lái)幫助你關(guān)聯(lián)這些事件之間的時(shí)間。
由于定時(shí)器和其他周期性的事件在run loop運(yùn)行時(shí)被傳遞,從而避免了循環(huán)在傳遞事件時(shí)被中斷。這種情況典型的例子就是你在進(jìn)入run loop之后實(shí)現(xiàn)了一個(gè)鼠標(biāo)追蹤回調(diào),然后你會(huì)重復(fù)地從應(yīng)用中收到該事件。由于你的代碼是直接抓取事件,而不是讓應(yīng)用來(lái)常規(guī)地分發(fā)那些事件,活動(dòng)的定時(shí)器并不會(huì)觸發(fā)直到退出鼠標(biāo)追蹤回調(diào)之后并返回應(yīng)用控制時(shí)。
使用run loop對(duì)象可以顯式地喚醒run loop。其他事件也有可能造成run loop的喚醒。如,加入其他的non-port-based輸入源會(huì)喚醒run loop以便使得輸入源(事件)能夠被立即處理,而不是等到事件發(fā)生時(shí)才這樣做。
何時(shí)使用Run Loop?
只有當(dāng)你為應(yīng)用創(chuàng)建輔助線程時(shí)才會(huì)需要顯式地運(yùn)行一個(gè)run loop。應(yīng)用程序主線程的run loop是一個(gè)十分關(guān)鍵的結(jié)構(gòu)。因此,應(yīng)用框架會(huì)提供主線程run loop自動(dòng)開始的代碼支持。iOS中UIApplication(或者OS X中NSApplication)的run方法會(huì)啟動(dòng)主線程run loop作為常規(guī)啟動(dòng)隊(duì)列中的一步。如果你使用Xcode的項(xiàng)目模板來(lái)創(chuàng)建應(yīng)用,你不必顯式地調(diào)用這些例程。
對(duì)于輔助線程而言,需要先決定run loop是否必須,如果是這樣,才去配置并啟動(dòng)它。你不必在所有情況下為線程開啟run loop。例如,如果你使用線程做某個(gè)長(zhǎng)期且預(yù)定的工作,也許可以避免開啟run loop。Run loop在更多情況下用在與線程做額外交互的??場(chǎng)景中。當(dāng)你計(jì)劃做以下工作時(shí)你需要開啟run loop:
- 使用基于端口或自定義輸入源來(lái)和其他線程交流時(shí)。
- 線程上使用定時(shí)器時(shí)。
- 在Cocoa應(yīng)用中使用任何
performSelector...相關(guān)的方法時(shí)。 - 保持線程執(zhí)行周期性的任務(wù)時(shí)。
如果你選擇使用run loop,其創(chuàng)建和配置是顯而易見的。正如所有線程編程技術(shù)一樣,你應(yīng)該有一個(gè)在合適場(chǎng)景下退出輔助線程的計(jì)劃,干凈地退出線程總是比強(qiáng)制終止它更好。
使用Run Loop對(duì)象
Run loop對(duì)象提供了向其添加輸入源、定時(shí)器、觀察者并運(yùn)行它的主接口。每個(gè)線程有單獨(dú)的一個(gè)run loop對(duì)象與其關(guān)聯(lián)。在Cocoa中,該對(duì)象作為NSRunLoop的實(shí)例。在低等級(jí)應(yīng)用中,它是一個(gè)指向CFRunLoopRef類型的指針。
獲取Run Loop對(duì)象
為獲得當(dāng)前線程的run loop,可以采取下列方式中的其中一種:
- 在Cocoa應(yīng)用中,使用NSRunLoop的
currentRunLoop類方法獲取NSRunLoop對(duì)象。 - 使用
CFRunLoopGetCurrent函數(shù)。
盡管它們不是免費(fèi)橋接的類型,當(dāng)需要時(shí)還是可以從NSRunLoop對(duì)象中獲取CFRunLoopRef類型。NSRunLoop定義了getCFRunLoop方法以返回可傳遞給Core Foundation例程的CFRunLoopRef類型。由于兩種對(duì)象都是指向相同的run loop,你可以根據(jù)需求混合調(diào)用NSRunLoop對(duì)象和CFRunLoopRef類型。
配置Run Loop
在輔助線程上運(yùn)行run loop之前,你必須為其添加至少一個(gè)輸入源或定時(shí)器。如果run loop沒有任何源來(lái)監(jiān)測(cè),它會(huì)在你試圖啟動(dòng)它時(shí)立即退出。
除了安裝源之外,你同樣可以安裝run loop觀察者并使用它們來(lái)檢測(cè)run loop執(zhí)行的不同階段。為創(chuàng)建run loop觀察者,你需要?jiǎng)?chuàng)建一個(gè)CFRunLoopObserverRef類型并使用CFRunLoopAddObserver函數(shù)將其添加到run loop中。Run loop觀察者必須使用Core Foundation創(chuàng)建,即便是Cocoa應(yīng)用中。
代碼3-1展示了run loop中綁定觀察者的主例程代碼。該示例的目的在于展示如何創(chuàng)建run loop觀察者,所以代碼簡(jiǎn)單地設(shè)置了一個(gè)監(jiān)測(cè)所有run loop事件的觀察者對(duì)象?;镜幕卣{(diào)例程(沒有顯示)被簡(jiǎn)化用日志輸出的方式代替。
代碼3-1 創(chuàng)建run loop觀察者
- (void)threadMain
{
// 該應(yīng)用使用垃圾回收處理,所以不需要自動(dòng)釋放池
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// 創(chuàng)建觀察者并綁定到run loop
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if (observer)
{
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
// 創(chuàng)建并調(diào)度定時(shí)器
[NSTimer scheduledTimerWithTimeInterval:0.1
target:self
selector:@selector(doFireTimer:)
userInfo:nil
repeats:YES];
NSInteger loopCount = 10;
do
{
// 運(yùn)行10次run loop后觸發(fā)定時(shí)器
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}
當(dāng)為長(zhǎng)周期線程配置run loop時(shí),最好至少添加一個(gè)輸入源以接收消息。盡管你可以只綁定一個(gè)定時(shí)器就進(jìn)入run loop,一旦定時(shí)器觸發(fā)完成它就會(huì)失效,這將導(dǎo)致該run loop退出。綁定一個(gè)重復(fù)性的定時(shí)器可以使run loop在比較長(zhǎng)的周期內(nèi)保持運(yùn)行,但會(huì)涉及到定時(shí)喚醒線程,這會(huì)有效地作為另一種形式的輪詢定時(shí)器。相比之下,輸入源會(huì)等到事件發(fā)生前,線程都會(huì)保持睡眠。
啟動(dòng)Run Loop
啟動(dòng)run loop只針對(duì)應(yīng)用中的輔助線程才是必須的操作。一個(gè)run loop必須至少有一個(gè)用于監(jiān)測(cè)的輸入源或定時(shí)器。如果沒有其中一個(gè)被綁定,run loop會(huì)立即退出。
有幾種啟動(dòng)run loop的方式,大致如下:
- 無(wú)條件啟動(dòng)。
- 限制時(shí)間內(nèi)啟動(dòng)。
- 在特定模式下啟動(dòng)。
無(wú)條件地進(jìn)入run loop是最簡(jiǎn)單的選項(xiàng),但這樣做最不可取。無(wú)條件地運(yùn)行run loop會(huì)將線程置于固定的循環(huán)當(dāng)中,這會(huì)讓你失去對(duì)run loop的控制。你可以添加或移除輸入源及定時(shí)器,但停止run loop的唯一方式是終止它。同樣在自定義模式下不能運(yùn)行run loop。
除開無(wú)條件地運(yùn)行run loop,最好設(shè)置一個(gè)超時(shí)時(shí)間后運(yùn)行run loop。當(dāng)設(shè)置時(shí)間閾值后,run loop會(huì)在事件到達(dá)或時(shí)間超時(shí)后運(yùn)行。當(dāng)事件到達(dá),事件會(huì)被分發(fā)給處理回調(diào)然后run loop退出。你的代碼可以在之后重啟run loop以處理下一次事件。如果是分配時(shí)間過(guò)期,可以簡(jiǎn)單地重新啟動(dòng)run loop或重設(shè)時(shí)間來(lái)做任何需要的事務(wù)管理。
除了設(shè)置時(shí)間閾外,你同樣可以用指定的模式來(lái)運(yùn)行run loop。模式和時(shí)間閾并不互斥并可以同時(shí)用于run loop啟動(dòng)。模式會(huì)對(duì)傳遞事件的來(lái)源做出限制。
代碼3-2展示了線程主入口例程的框架版本。該示例的關(guān)鍵部分展示了run loop的基本結(jié)構(gòu)。在本質(zhì)上,你將輸入源和計(jì)時(shí)器添加到run loop,然后重復(fù)調(diào)用例程之一以啟動(dòng)run loop。每次run loop例程返回時(shí),請(qǐng)檢查是否出現(xiàn)了可能需要退出線程的任何條件。示例使用Core Foundation的run loop例程,以便它可以檢查返回結(jié)果,并確定run loop退出的原因。如果使用的是Cocoa方式且不需要檢查返回值,你也可以使用NSRunLoop類的方法以類似的方式運(yùn)行run loop。(使用NSRunLoop類的方法完成run loop調(diào)用,示例參見代碼3-14。)
代碼3-2 運(yùn)行run loop
- (void)skeletonThreadMain
{
// 如果不使用垃圾回收處理需設(shè)置一個(gè)自動(dòng)釋放池
BOOL done = NO;
// 為run loop添加輸入源或定時(shí)器并做其他設(shè)置
do
{
// 啟動(dòng)run loop并在每個(gè)源完成處理后返回
SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES);
// 如果run loop被源顯式地停止,或者不存在輸入源或定時(shí)器,則退出run loop
if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
{
done = YES;
}
// 檢查其他的退出條件并修改done變量
}
while (!done);
// 這里編寫清理代碼,確保釋放自動(dòng)釋放池中的對(duì)象
}
遞歸地運(yùn)行run loop是可能的情況。換句話說(shuō),你可以調(diào)用CFRunLoopRun,CFRunLoopRunInMode,或任何NSRunLoop方法從輸入源或定時(shí)器的回調(diào)例程內(nèi)開始run loop。這樣做時(shí),你可以使用任何想要運(yùn)行嵌套run loop的模式,包括使用外部run loop的模式。
退出Run Loop
有兩種退出run loop的方式:
- 配置run loop運(yùn)行超時(shí)時(shí)間閾值。
- 通知run loop退出。
使用超時(shí)的方式當(dāng)然是首選,如果你能控制它。指定超時(shí)閾可以讓run loop結(jié)束所有的正常處理,包括在退出之前向run loop的觀察者傳遞通知。
使用CFRunLoopStop函數(shù)顯式地停止run loop產(chǎn)生的效果類似于超時(shí)。Run loop發(fā)出任何剩余的通知,然后退出。其區(qū)別在于,你可以在run loop無(wú)條件啟動(dòng)的情況下使用這種技術(shù)。
雖然移除run loop的輸入源和定時(shí)器也可能導(dǎo)致其退出,但這不是退出run loop的可靠方式。一些系統(tǒng)例程將輸入源添加到run loop來(lái)處理所需事件。因?yàn)槟愕拇a可能不知道這些輸入源,所以無(wú)法刪除它們,這會(huì)阻止run loop退出。
線程安全與Run Loop對(duì)象
線程安全取決于使用哪個(gè)接口來(lái)操作你的run loop。Core Foundation上的功能通常是線程安全的,并且可以從任何線程調(diào)用。如果你正在執(zhí)行更改run loop配置的操作,那么,它仍然是很好的做法,這樣就可以在任何可能的時(shí)候都擁有run loop的線程了。
Cocoa的NSRunLoop類并不如它在Core Foundation中的副本(譯者注:CFRunLoopRef類型)那樣線程安全。如果你正在使用NSRunLoop類修改你的run loop,你應(yīng)該只在同一個(gè)線程擁有的run loop這樣做。向?qū)儆诓煌€程的run loop添加輸入源或定時(shí)器,可能導(dǎo)致你的代碼崩潰或表現(xiàn)異常。
配置Run Loop源
以下各節(jié)將展示Cocoa和Core Foundation中如何設(shè)置輸入源的類型。
定義自定義輸入源
創(chuàng)建自定義輸入源涉及到以下定義:
- 需要輸入源處理的信息。
- 讓感興趣的客戶端與輸入源交互的調(diào)度器例程。
- 處理客戶端請(qǐng)求回調(diào)的例程。
- 注銷輸入源的取消例程。
因?yàn)槟銊?chuàng)建了自定義的輸入源來(lái)處理自定義信息,所以實(shí)際的配置是靈活的。調(diào)度程序,處理程序和取消例程幾乎是所需自定義輸入源的全部關(guān)鍵例程。然而大多數(shù)輸入源的行為發(fā)生在這些處理程序以外的例程。例如,你定義了一個(gè)用于輸入源向其他線程傳遞數(shù)據(jù)和信息交換的機(jī)制。
圖3-2展示了一個(gè)自定義的輸入源配置示例。在這個(gè)例子中,應(yīng)用程序的主線程維護(hù)著輸入源的引用,輸入源的自定義命令緩沖區(qū),以及安裝了輸入源的run loop。當(dāng)主線程有一個(gè)需交由工作線程的任務(wù)時(shí),它將向命令緩沖區(qū)發(fā)出命令以及工作線程所需要的任何信息來(lái)啟動(dòng)任務(wù)。(由于主線程和工作線程的輸入源都可以訪問(wèn)命令緩沖區(qū),所以訪問(wèn)必須是同步的。)一旦命令被發(fā)出,主線程會(huì)向輸入源發(fā)出信號(hào),并喚醒工作線程的run loop。在接收喚醒命令時(shí),run loop調(diào)用該輸入源的處理程序,輸入源會(huì)處理在命令緩沖區(qū)中找到的命令。
圖3-2 自定義輸入源的操作流程
[圖片上傳失敗...(image-675d05-1536734662071)]
以下部分闡釋了自定義輸入源的實(shí)現(xiàn),并顯示了需要實(shí)現(xiàn)的關(guān)鍵代碼。
定義輸入源
定義自定義的輸入源需要使用Core Foundation的例程,以此來(lái)對(duì)run loop源進(jìn)行配置和綁定。盡管基本的回調(diào)都是基于C語(yǔ)言的函數(shù),這并不意味著你需要為那些函數(shù)編寫Objective-C或C++的代碼包裝。
圖3-2中所示的輸入源使用Objective-C對(duì)象管理命令緩沖區(qū),并和run loop進(jìn)行調(diào)節(jié)工作。代碼3-3展示了輸入源對(duì)象的定義。RunLoopSource對(duì)象管理著命令緩沖區(qū)并使用該緩沖區(qū)來(lái)接收其他線程的消息。該段代碼同樣展示了RunLoopContext對(duì)象的定義,該對(duì)象作為傳遞RunLoopSource對(duì)象的容器對(duì)象以及run loop對(duì)應(yīng)用主線程的引用對(duì)象。
代碼3-3 自定義輸入源對(duì)象定義
@interface RunLoopSource : NSObject
{
CFRunLoopSourceRef runLoopSource;
NSMutableArray* commands;
}
- (id)init;
- (void)addToCurrentRunLoop;
- (void)invalidate;
// Handler method
- (void)sourceFired;
// Client interface for registering commands to process
- (void)addCommand:(NSInteger)command withData:(id)data;
- (void)fireAllCommandsOnRunLoop:(CFRunLoopRef)runloop;
@end
// These are the CFRunLoopSourceRef callback functions.
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
void RunLoopSourcePerformRoutine (void *info);
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode);
// RunLoopContext is a container object used during registration of the input source.
@interface RunLoopContext : NSObject
{
CFRunLoopRef runLoop;
RunLoopSource* source;
}
@property (readonly) CFRunLoopRef runLoop;
@property (readonly) RunLoopSource* source;
- (id)initWithSource:(RunLoopSource*)src andLoop:(CFRunLoopRef)loop;
@end
雖然Objective-C代碼管理著輸入源的自定義數(shù)據(jù),將輸入源綁定到run loop需要基于C的回調(diào)函數(shù)。這些函數(shù)在run loop源綁定到run loop后有一次初次調(diào)用,并在代碼3-4展示。因?yàn)檫@個(gè)輸入源只有一個(gè)客戶端(主線程),它會(huì)調(diào)用調(diào)度函數(shù)來(lái)向線程發(fā)送一個(gè)注冊(cè)自身應(yīng)用委托對(duì)象的消息。當(dāng)委托對(duì)象希望與輸入源通信時(shí),它會(huì)利用在RunLoopContext對(duì)象中的信息來(lái)完成。
代碼3-4 調(diào)度run loop源
void RunLoopSourceScheduleRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
RunLoopSource* obj = (RunLoopSource*)info;
AppDelegate* del = [AppDelegate sharedAppDelegate];
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj
andLoop:rl];
[del performSelectorOnMainThread:@selector(registerSource:)
withObject:theContext
waitUntilDone:NO];
}
最重要的回調(diào)處理例程是輸入源被通知時(shí)處理自定義數(shù)據(jù)的例程。代碼3-5展示了該例程與RunLoopSource的關(guān)聯(lián)。該函數(shù)簡(jiǎn)單地發(fā)起了一個(gè)名為sourceFired的方法的工作請(qǐng)求,sourceFired方法內(nèi)部會(huì)處理命令緩沖池中存在的任何命令。
代碼3-5 輸入源中執(zhí)行任務(wù)
void RunLoopSourcePerformRoutine (void *info)
{
RunLoopSource* obj = (RunLoopSource*)info;
[obj sourceFired];
}
如果你一旦調(diào)用CFRunLoopSourceInvalidate函數(shù)將輸入源從run loop中移除,系統(tǒng)會(huì)調(diào)用輸入源的取消例程。你可以在該例程中通知客戶端輸入源將要失效并讓其移除對(duì)輸入源的引用。代碼3-6展示了使用RunLoopSource對(duì)象注冊(cè)的取消回調(diào)例程。該函數(shù)向應(yīng)用的委托對(duì)象發(fā)送了另一個(gè)RunLoopContext對(duì)象,但這次是讓請(qǐng)求對(duì)象移除對(duì)run loop源的引用。
代碼3-6 停止輸入源
void RunLoopSourceCancelRoutine (void *info, CFRunLoopRef rl, CFStringRef mode)
{
RunLoopSource* obj = (RunLoopSource*)info;
AppDelegate* del = [AppDelegate sharedAppDelegate];
RunLoopContext* theContext = [[RunLoopContext alloc] initWithSource:obj andLoop:rl];
[del performSelectorOnMainThread:@selector(removeSource:)
withObject:theContext
waitUntilDone:YES];
}
注意:應(yīng)用委托對(duì)象的
registerSource:和removeSource:方法代碼在之前的代碼3-8中展示。
為Run Loop安裝輸入源
代碼3-7展示了RunLoopSource類的init和addToCurrentRunLoop方法。init方法創(chuàng)建了與run loop實(shí)際綁定的CFRunLoopSourceRef類型。它將RunLoopSource對(duì)象作為上下文信息傳遞以讓回調(diào)例程持有該對(duì)象的指針。輸入源的安裝直到工作線程調(diào)用addToCurrentRunLoop方法才發(fā)生,這時(shí)RunLoopSourceScheduleRoutine回調(diào)會(huì)被調(diào)用。一旦輸入源被添加到run loop,線程會(huì)運(yùn)行run loop并在run loop上做好準(zhǔn)備。
代碼3-7 安裝輸入源
- (id)init
{
CFRunLoopSourceContext context = {0, self, NULL, NULL, NULL, NULL, NULL,
&RunLoopSourceScheduleRoutine,
RunLoopSourceCancelRoutine,
RunLoopSourcePerformRoutine};
runLoopSource = CFRunLoopSourceCreate(NULL, 0, &context);
commands = [[NSMutableArray alloc] init];
return self;
}
- (void)addToCurrentRunLoop
{
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopAddSource(runLoop, runLoopSource, kCFRunLoopDefaultMode);
}
調(diào)節(jié)客戶端與輸入源
為了使輸入源有用,你需要對(duì)其操作并在其他線程上向其發(fā)出信號(hào)。當(dāng)無(wú)事可做時(shí)輸入源會(huì)讓關(guān)聯(lián)的線程處于睡眠狀態(tài)。這需要在應(yīng)用中的其他線程上知道該輸入源并如何和它通信。
其中的一種方式就是在輸入源初次安裝到run loop時(shí)通知客戶端輸入源將要發(fā)出注冊(cè)的請(qǐng)求。你可以為你的輸入源注冊(cè)盡可能多的客戶端,或者簡(jiǎn)單地向中心對(duì)象注冊(cè)然后向感興趣的客戶端“售出”輸入源。代碼3-8展示了應(yīng)用委托對(duì)象內(nèi)定義的注冊(cè)方法,并在RunLoopSource對(duì)象的調(diào)度函數(shù)調(diào)用。該方法從RunLoopSource對(duì)象獲取RunLoopContext對(duì)象并將其添加到它的多個(gè)源當(dāng)中。代碼也展示了將輸入源從run loop注銷的函數(shù)例程。
代碼3-8 使用應(yīng)用委托對(duì)象完成輸入源注冊(cè)與移除
- (void)registerSource:(RunLoopContext*)sourceInfo;
{
[sourcesToPing addObject:sourceInfo];
}
- (void)removeSource:(RunLoopContext*)sourceInfo
{
id objToRemove = nil;
for (RunLoopContext* context in sourcesToPing)
{
if ([context isEqual:sourceInfo])
{
objToRemove = context;
break;
}
}
if (objToRemove)
{
[sourcesToPing removeObject:objToRemove];
}
}
注意:調(diào)用這些方法的回調(diào)函數(shù)代碼在之前的代碼3-4和代碼3-6中展示。
向輸入源發(fā)出信號(hào)
在向輸入源傳遞數(shù)據(jù)之后,客戶端必須向源發(fā)起信號(hào)并喚醒其run loop。向源發(fā)起信號(hào)的目的是讓run loop知道源已經(jīng)準(zhǔn)備好處理了。還有一個(gè)原因就是線程可能在發(fā)起信號(hào)時(shí)處理睡眠狀態(tài),你應(yīng)該總是顯式地喚醒run loop。如果沒能完成這樣的操作,可能會(huì)導(dǎo)致輸入源延遲處理。
代碼3-9展示了RunLoop的fireCommandsOnRunLoop方法??蛻舳嗽谳斎朐醋龊脺?zhǔn)備處理緩沖區(qū)命令時(shí)調(diào)用該方法。
代碼3-9 喚醒run loop
- (void)fireCommandsOnRunLoop:(CFRunLoopRef)runloop
{
CFRunLoopSourceSignal(runLoopSource);
CFRunLoopWakeUp(runloop);
}
注意:你不能通過(guò)自定義輸入源嘗試處理
SIGHUP或其他類型的進(jìn)程級(jí)信號(hào)。Core Foundation中喚醒run loop的函數(shù)并不安全且不應(yīng)該在應(yīng)用的信號(hào)處理例程中使用。
配置定時(shí)源
為創(chuàng)建定時(shí)器源,所有你需要做的就是創(chuàng)建一個(gè)定時(shí)器對(duì)象并完成它在run loop上的調(diào)用。在Cocoa中,可以使用NSTimer類創(chuàng)建新的定時(shí)器對(duì)象,在Core Foundation中則使用CFRunLoopTimerRef類型。本質(zhì)上,NSTimer類是Core Foundation上提供的一種更加便利功能的擴(kuò)展,如在同一個(gè)方法上創(chuàng)建并調(diào)度定時(shí)器的能力。
在Cocoa中,你可以用以下的類方法同時(shí)完成定時(shí)器的創(chuàng)建和調(diào)度:
scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:scheduledTimerWithTimeInterval:invocation:repeats:
這些方法會(huì)創(chuàng)建定時(shí)器并將其添加到默認(rèn)模式下的run loop中。(NSDefaultRunLoopMode)你也可以按照需求手動(dòng)地調(diào)度定時(shí)器,這需要使用NSRunLoop的addTimer:forMode:方法來(lái)創(chuàng)建NSTimer對(duì)象并將其添加到run loop。這兩種技術(shù)都能夠完成基本的操作但給你對(duì)于定時(shí)器不同層級(jí)的控制。例如,如果創(chuàng)建定時(shí)器并手動(dòng)地添加到run loop,則可以使用除默認(rèn)模式外的其他模式。代碼3-10展示了如何使用這兩種技術(shù)來(lái)創(chuàng)建定時(shí)器。第一個(gè)定時(shí)器有1秒的創(chuàng)建延遲并在之后每隔0.1秒觸發(fā)一次。第二個(gè)定時(shí)器在初始化后的0.2秒觸發(fā)并在之后每隔0.2秒觸發(fā)一次。
代碼3-10 使用NSTimer創(chuàng)建并調(diào)度定時(shí)器
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
interval:0.1
target:self
selector:@selector(myDoFireTimer1:)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
target:self
selector:@selector(myDoFireTimer2:)
userInfo:nil
repeats:YES];
代碼3-11展示了使用Core Foundation函數(shù)對(duì)定時(shí)器完成配置的代碼。盡管該示例在上下文并不傳遞任何用戶定義的信息,你可以使用該上下文為定時(shí)器傳遞任何自定義的數(shù)據(jù)。
代碼3-11 使用Core Foundation創(chuàng)建并調(diào)度定時(shí)器
CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
&myCFTimerCallback, &context);
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);
配置基于端口的輸入源
Cocoa和Core Foundation同時(shí)提供了用于線程或進(jìn)程間通信的基于端口的對(duì)象。下面部分將向你展示如何使用不同類型的端口來(lái)實(shí)現(xiàn)端口通信。
配置NSMachPort對(duì)象
為了使用NSMachPort對(duì)象建立本地連接,你需要?jiǎng)?chuàng)建一個(gè)端口對(duì)象并添加到主要線程的run loop中。當(dāng)啟動(dòng)輔助線程后,向該線程的入口點(diǎn)函數(shù)傳遞相同的端口對(duì)象。輔助線程就可以使用這個(gè)對(duì)象來(lái)向主要線程發(fā)送消息。
主線程代碼實(shí)現(xiàn)
代碼3-12展示了如何啟動(dòng)輔助的工作線程的主線程代碼。由于Cocoa框架實(shí)現(xiàn)了許多配置run loop端口的中介步驟,launchThread方法明顯比Core Foundation中等價(jià)的函數(shù)(代碼3-17)短;然而它們兩者行為近乎等同。唯一的區(qū)別在于該方法直接發(fā)送NSPort對(duì)象,而不是向工作線程發(fā)送本地端口的名字。
代碼3-12 主線程的啟動(dòng)方法
- (void)launchThread
{
NSPort* myPort = [NSMachPort port];
if (myPort)
{
// This class handles incoming port messages.
[myPort setDelegate:self];
// Install the port as an input source on the current run loop.
[[NSRunLoop currentRunLoop] addPort:myPort
forMode:NSDefaultRunLoopMode];
// Detach the thread. Let the worker release the port.
[NSThread detachNewThreadSelector:@selector(LaunchThreadWithPort:)
toTarget:[MyWorkerClass class]
withObject:myPort];
}
}
為了實(shí)現(xiàn)線程間雙邊通信的信道,你應(yīng)該想要工作線程在向主線程發(fā)送的檢入信息中包含它自己的本地端口。接收到該檢入信息能夠讓你的主線程知道輔助線程的啟動(dòng)狀況以及讓你向其發(fā)送額外的信息。
代碼3-13展示了主線程中的handlePortMessage:方法。該方法在數(shù)據(jù)到達(dá)本地端口時(shí)調(diào)用。當(dāng)檢入信息到達(dá)時(shí),該方法為輔助線程從端口信息中獲取端口并將其存儲(chǔ)起來(lái)以供使用。
代碼3-13 處理Mach端口消息
#define kCheckinMessage 100
// Handle responses from the worker thread.
- (void)handlePortMessage:(NSPortMessage *)portMessage
{
unsigned int message = [portMessage msgid];
NSPort* distantPort = nil;
if (message == kCheckinMessage)
{
// Get the worker thread’s communications port.
distantPort = [portMessage sendPort];
// Retain and save the worker port for later use.
[self storeDistantPort:distantPort];
}
else
{
// Handle other messages.
}
}
輔助線程代碼實(shí)現(xiàn)
對(duì)于輔助工作線程,你必須為配置該線程并使用指定端口來(lái)和主線程完成信息的回復(fù)。
代碼3-14展示了該工作線程的設(shè)置代碼。在為線程創(chuàng)建自動(dòng)釋放池后,該方法創(chuàng)建了一個(gè)工作者對(duì)象并驅(qū)動(dòng)線程的執(zhí)行。工作對(duì)象的sendCheckinMessage:方法(代碼3-15展示)創(chuàng)建了一個(gè)工作線程的本地端口并發(fā)送檢入消息給主線程。
代碼3-14 啟動(dòng)使用Mach端口的輔助線程
+(void)LaunchThreadWithPort:(id)inData
{
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
// Set up the connection between this thread and the main thread.
NSPort* distantPort = (NSPort*)inData;
MyWorkerClass* workerObj = [[self alloc] init];
[workerObj sendCheckinMessage:distantPort];
[distantPort release];
// Let the run loop process things.
do
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
}
while (![workerObj shouldExit]);
[workerObj release];
[pool release];
}
當(dāng)使用NSMachPort時(shí),本地及遠(yuǎn)程的對(duì)象可以使用同樣的端口對(duì)象進(jìn)行線程間的通信。換句話講,由線程創(chuàng)建的本地端口對(duì)象可以成為其他線程的遠(yuǎn)程端口。
代碼3-15展示了輔助線程中的檢入例程。該方法設(shè)置了一個(gè)用于單邊通信的本地端口,并在之后向主線程回送了檢入信息。該方法使用從LaunchThreadWithPort:方法接收到的端口對(duì)象作為消息的目標(biāo)對(duì)象。
代碼3-15 使用Mach端口發(fā)送檢入信息
// Worker thread check-in method
- (void)sendCheckinMessage:(NSPort*)outPort
{
// Retain and save the remote port for future use.
[self setRemotePort:outPort];
// Create and configure the worker thread port.
NSPort* myPort = [NSMachPort port];
[myPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:myPort forMode:NSDefaultRunLoopMode];
// Create the check-in message.
NSPortMessage* messageObj = [[NSPortMessage alloc] initWithSendPort:outPort
receivePort:myPort
components:nil];
if (messageObj)
{
// Finish configuring the message and send it immediately.
[messageObj setMsgId:setMsgid:kCheckinMessage];
[messageObj sendBeforeDate:[NSDate date]];
}
}
配置NSMessagePort對(duì)象
為創(chuàng)建基于NSMessagePort對(duì)象的本地連接,你不能在線程間簡(jiǎn)單地傳遞端口對(duì)象。遠(yuǎn)程消息端口根據(jù)名稱獲得。Cocoa中需要本地端口注冊(cè)指定的名稱并將名稱傳遞給遠(yuǎn)端的線程以便其獲得用于通信的合適的端口對(duì)象。代碼3-16展示了消息端口的創(chuàng)建與注冊(cè)流程。
代碼3-16 注冊(cè)一個(gè)消息端口
NSPort* localPort = [[NSMessagePort alloc] init];
// Configure the object and add it to the current run loop.
[localPort setDelegate:self];
[[NSRunLoop currentRunLoop] addPort:localPort forMode:NSDefaultRunLoopMode];
// Register the port using a specific name. The name must be unique.
NSString* localPortName = [NSString stringWithFormat:@"MyPortName"];
[[NSMessagePortNameServer sharedInstance] registerPort:localPort
name:localPortName];
Core Foundation配置基于端口輸入源
本部分將展示如何使用Core Foundation在主線程和工作線程之間設(shè)置一個(gè)雙邊通信的信道。
代碼3-17展示了主線程啟動(dòng)工作線程的調(diào)用代碼。該段代碼首先創(chuàng)建了一個(gè)CFMessagePortRef類型來(lái)監(jiān)聽工作線程上的消息。由于工作線程需要端口的名稱來(lái)建立連接,所以工作線程的入口點(diǎn)函數(shù)需要傳入字符串值。端口名稱在當(dāng)前用戶的環(huán)境下必須是唯一的;否則可能會(huì)遇到?jīng)_突。
代碼3-17 為新線程綁定Core Foundation消息端口
#define kThreadStackSize (8 *4096)
OSStatus MySpawnThread()
{
// Create a local port for receiving responses.
CFStringRef myPortName;
CFMessagePortRef myPort;
CFRunLoopSourceRef rlSource;
CFMessagePortContext context = {0, NULL, NULL, NULL, NULL};
Boolean shouldFreeInfo;
// Create a string with the port name.
myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.myapp.MainThread"));
// Create the port.
myPort = CFMessagePortCreateLocal(NULL, myPortName,
&MainThreadResponseHandler,
&context,
&shouldFreeInfo);
if (myPort != NULL)
{
// The port was successfully created.
// Now create a run loop source for it.
rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (rlSource)
{
// Add the source to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
// Once installed, these can be freed.
CFRelease(myPort);
CFRelease(rlSource);
}
}
// Create the thread and continue processing.
MPTaskID taskID;
return(MPCreateTask(&ServerThreadEntryPoint, (void*)myPortName,
kThreadStackSize, NULL, NULL, NULL, 0, &taskID));
}
在端口安裝完成和線程啟動(dòng)之后,主線程可以繼續(xù)它的常規(guī)執(zhí)行以等待線程的檢入。當(dāng)檢入消息到達(dá)時(shí),它會(huì)被分發(fā)到主線程的MainThreadResponseHandler函數(shù),在代碼3-18中顯示。該函數(shù)從工作線程中提取端口的名稱并創(chuàng)建好將來(lái)通信的渠道。
代碼3-18 接收檢入消息
#define kCheckinMessage 100
// Main thread port message handler
CFDataRef MainThreadResponseHandler(CFMessagePortRef local,
SInt32 msgid,
CFDataRef data,
void* info)
{
if (msgid == kCheckinMessage)
{
CFMessagePortRef messagePort;
CFStringRef threadPortName;
CFIndex bufferLength = CFDataGetLength(data);
UInt8* buffer = CFAllocatorAllocate(NULL, bufferLength, 0);
CFDataGetBytes(data, CFRangeMake(0, bufferLength), buffer);
threadPortName = CFStringCreateWithBytes (NULL, buffer, bufferLength, kCFStringEncodingASCII, FALSE);
// You must obtain a remote message port by name.
messagePort = CFMessagePortCreateRemote(NULL, (CFStringRef)threadPortName);
if (messagePort)
{
// Retain and save the thread’s comm port for future reference.
AddPortToListOfActiveThreads(messagePort);
// Since the port is retained by the previous function, release
// it here.
CFRelease(messagePort);
}
// Clean up.
CFRelease(threadPortName);
CFAllocatorDeallocate(NULL, buffer);
}
else
{
// Process other messages.
}
return NULL;
}
完成主線程的配置后,唯一剩下的事情就是對(duì)新創(chuàng)建工作線程創(chuàng)建它自己的端口并檢入。代碼3-19展示了工作線程的入口函數(shù)。該函數(shù)從主線程中提取到端口名并使用它來(lái)創(chuàng)建回連主線程的端口。該函數(shù)然后創(chuàng)建了自己的安裝在run loop上的一個(gè)本地端口,并向主線程發(fā)送一條包含本地端口名的檢入消息。
代碼3-19 創(chuàng)建線程結(jié)構(gòu)
OSStatus ServerThreadEntryPoint(void* param)
{
// Create the remote port to the main thread.
CFMessagePortRef mainThreadPort;
CFStringRef portName = (CFStringRef)param;
mainThreadPort = CFMessagePortCreateRemote(NULL, portName);
// Free the string that was passed in param.
CFRelease(portName);
// Create a port for the worker thread.
CFStringRef myPortName = CFStringCreateWithFormat(NULL, NULL, CFSTR("com.MyApp.Thread-%d"), MPCurrentTaskID());
// Store the port in this thread’s context info for later reference.
CFMessagePortContext context = {0, mainThreadPort, NULL, NULL, NULL};
Boolean shouldFreeInfo;
Boolean shouldAbort = TRUE;
CFMessagePortRef myPort = CFMessagePortCreateLocal(NULL,
myPortName,
&ProcessClientRequest,
&context,
&shouldFreeInfo);
if (shouldFreeInfo)
{
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
CFRunLoopSourceRef rlSource = CFMessagePortCreateRunLoopSource(NULL, myPort, 0);
if (!rlSource)
{
// Couldn't create a local port, so kill the thread.
MPExit(0);
}
// Add the source to the current run loop.
CFRunLoopAddSource(CFRunLoopGetCurrent(), rlSource, kCFRunLoopDefaultMode);
// Once installed, these can be freed.
CFRelease(myPort);
CFRelease(rlSource);
// Package up the port name and send the check-in message.
CFDataRef returnData = nil;
CFDataRef outData;
CFIndex stringLength = CFStringGetLength(myPortName);
UInt8* buffer = CFAllocatorAllocate(NULL, stringLength, 0);
CFStringGetBytes(myPortName, CFRangeMake(0,stringLength),
kCFStringEncodingASCII, 0, FALSE, buffer, stringLength, NULL);
outData = CFDataCreate(NULL, buffer, stringLength);
CFMessagePortSendRequest(mainThreadPort, kCheckinMessage, outData, 0.1, 0.0, NULL, NULL);
// Clean up thread data structures.
CFRelease(outData);
CFAllocatorDeallocate(NULL, buffer);
// Enter the run loop.
CFRunLoopRun();
}
一旦它進(jìn)入run loop,所有將來(lái)發(fā)向該線程端口的事件都將由ProcessClientRequest函數(shù)處理。該函數(shù)的實(shí)現(xiàn)依賴于線程的具體工作且不在這里做展示。
線程同步
在應(yīng)用程序中多線程的存在下,多線程的執(zhí)行產(chǎn)生了安全訪問(wèn)資源的潛在問(wèn)題。兩個(gè)線程可能不經(jīng)意間就會(huì)修改相同的資源。例如,一個(gè)線程可能覆蓋另一個(gè)線程的更改,或者將應(yīng)用程序置于一個(gè)未知的和潛在的無(wú)效狀態(tài)。如果你比較幸運(yùn),損壞的資源可能會(huì)導(dǎo)致明顯的性能問(wèn)題或崩潰,這比較容易跟蹤和修復(fù)。但是如果你比較不幸,這導(dǎo)致的微小錯(cuò)誤并不能即時(shí)被察覺或錯(cuò)誤需要代碼修改多處才能解決。
說(shuō)到線程安全,一個(gè)好的設(shè)計(jì)才是最好的保護(hù)方式。避免資源共享和減少線程之間的交互,使這些線程間不太那么容易相互干擾。然而一個(gè)完全避免干擾的設(shè)計(jì)是不可能的。在線程必須進(jìn)行交互的前提下,你需要使用同步工具來(lái)保證它們之間的交互是安全的。
OS X和iOS提供眾多的同步工具供你使用,工具從提供互斥訪問(wèn)到事件隊(duì)列。下面的章節(jié)將介紹這些工具以及如何在代碼中使用它們,以解決程序資源安全訪問(wèn)的問(wèn)題。
同步工具
為了防止線程超出預(yù)期的修改數(shù)據(jù),可以對(duì)應(yīng)用的設(shè)計(jì)做出修改或者使用同步工具避免同步問(wèn)題。雖然完全避免同步問(wèn)題是可取的,但它并不總是可能的。下面的章節(jié)描述了可供你使用的同步工具的基本類別。
原子操作
原子操作是作用于簡(jiǎn)單數(shù)據(jù)類型上的簡(jiǎn)單同步形式。其優(yōu)勢(shì)在于它不阻塞相互競(jìng)爭(zhēng)的線程。對(duì)于簡(jiǎn)單的操作,譬如增加計(jì)數(shù)器變量,它比鎖的性能更加優(yōu)越。
OS X和iOS包括了多個(gè)用于32位及64位基本數(shù)學(xué)運(yùn)算和邏輯運(yùn)算的操作。在這些操作中屬于原子性的有compare-and-swap、test-and-set,以及test-and-clear。請(qǐng)查看/usr/include/libkern/OSAtomic.h頭文件或查看atomic的man幫助頁(yè)獲取原子操作支持的列表。
內(nèi)存屏障與Volatile變量
為達(dá)到最優(yōu)性能,編譯器時(shí)常記錄匯編級(jí)別的指令以使處理器能盡可能滿載地處理流水線指令。作為該優(yōu)化的一部分,編譯器會(huì)記錄那些不會(huì)造成錯(cuò)誤數(shù)據(jù)的主內(nèi)存訪問(wèn)指令。不幸的是,編譯器不能總是檢測(cè)到所有的內(nèi)存獨(dú)立操作。如果看似獨(dú)立的變量在實(shí)際上卻相互影響,編譯器就會(huì)以錯(cuò)誤的順序?qū)ζ涓拢a(chǎn)生潛在的錯(cuò)誤結(jié)果。
內(nèi)存屏障作為一種非阻塞式的同步工具,其用于確保內(nèi)存操作處于正確順序。內(nèi)存屏障的角色近乎于“柵欄”,它會(huì)強(qiáng)制處理器完成任何位于它之前載入和存儲(chǔ)的操作,而將其后的操作阻擋在外。內(nèi)存屏障典型地應(yīng)用于確保線程上內(nèi)存操作(操作結(jié)果對(duì)其他線程可見)總是按照預(yù)定順序執(zhí)行。如果在剛才的情形下缺少了內(nèi)存屏障,這將導(dǎo)致其他線程得到不可預(yù)計(jì)的結(jié)果。(譯者注:這里說(shuō)的情形是讀寫者問(wèn)題)為使用內(nèi)存屏障,你可以在代碼合適的位置簡(jiǎn)單地調(diào)用OSMemoryBarrier函數(shù)。
Volatile變量應(yīng)用于對(duì)于單獨(dú)變量的內(nèi)存限制。編譯器優(yōu)化代碼時(shí)常通過(guò)將變量的值載入寄存器中。對(duì)于本地變量,這通常沒有什么問(wèn)題。如果一個(gè)變量對(duì)于其他線程可見,那么這樣的優(yōu)化也許會(huì)對(duì)其他線程對(duì)于該變量值修改的觀察造成影響。對(duì)變量使用volatile關(guān)鍵字可以強(qiáng)制編譯器每次使用變量時(shí)從內(nèi)存中載入。你可以在變量可能任何時(shí)候被外部修改且編譯器不能檢測(cè)到時(shí)考慮聲明變量為volatile。
鎖
鎖作為最為常用的同步工具。你可以使用鎖來(lái)保護(hù)代碼的臨界區(qū),臨界區(qū)僅能在同一時(shí)刻被單個(gè)線程訪問(wèn)。例如,臨界區(qū)在同一時(shí)刻可能會(huì)操作一個(gè)特殊的數(shù)據(jù)結(jié)構(gòu)或者占用只支持一個(gè)用戶使用的資源。在該區(qū)域加入鎖,你就可以排除其他線程造成的修改和對(duì)代碼正確性造成影響。
表4-1列舉了程序猿常用的鎖方式。OS X和iOS提供了其中大多數(shù)鎖的實(shí)現(xiàn),但并不是全部。對(duì)于不支持的鎖類型,描述字段會(huì)解釋其未在該平臺(tái)直接實(shí)現(xiàn)的原因。
表4-1 鎖類型
| 鎖類型 | 描述 |
|---|---|
| 互斥鎖(Mutex) | 互斥排他(或互斥)鎖作為資源的保護(hù)屏障。互斥鎖是一種信號(hào)量類型并在同一時(shí)刻只為一個(gè)線程授權(quán)訪問(wèn)資源。如果使用了互斥鎖然而其他線程試圖請(qǐng)求它,那個(gè)線程會(huì)阻塞直到互斥鎖被它原先的持有者釋放。如果多個(gè)線程競(jìng)爭(zhēng)同一個(gè)互斥鎖,只有其中一個(gè)在同一時(shí)刻才有權(quán)擁有它。 |
| 遞歸鎖(Recursive lock) | 遞歸鎖是互斥鎖的一個(gè)變種。遞歸鎖允許同一個(gè)線程在其釋放前多次請(qǐng)求它。其他的線程則會(huì)阻塞直到鎖的持有者以和請(qǐng)求次數(shù)相同的釋放次數(shù)釋放該鎖。遞歸鎖主要用于遞歸操作,但也可以用于多個(gè)方法間獨(dú)立地請(qǐng)求該鎖。 |
| 讀寫鎖(Read-write lock) | 讀寫鎖可以被視作共享的排他鎖。該類型的鎖典型地應(yīng)用于大批量的操作以及被保護(hù)數(shù)據(jù)讀操作頻繁而寫操作稀少的場(chǎng)景中。正常操作情況下,大量的讀者可以同時(shí)訪問(wèn)數(shù)據(jù)。然而當(dāng)線程想寫數(shù)據(jù)時(shí),它直到讀者釋放鎖前都會(huì)阻塞,只有請(qǐng)求到該鎖時(shí)才能進(jìn)行數(shù)據(jù)更新的操作。當(dāng)寫入線程等待鎖時(shí),新的讀者線程會(huì)阻塞直到寫線程完成。系統(tǒng)僅支持POSIX線程使用讀寫鎖。 |
| 分布式鎖(Distributed lock) | 分布式鎖提供進(jìn)程級(jí)別的互斥訪問(wèn)。不像真正的互斥。分布式鎖不阻塞進(jìn)程的運(yùn)行。它僅報(bào)告鎖很忙并讓進(jìn)程決定如何執(zhí)行。 |
| 自旋鎖(Spin lock) | 自旋鎖在條件滿足前會(huì)不斷地輪詢獲得鎖。自旋鎖常用于預(yù)計(jì)等待鎖的時(shí)間短的多處理器系統(tǒng)中。在這樣的前提下,輪詢得到鎖通常比阻塞線程更加高效,因?yàn)楹笳呱婕暗骄€程狀態(tài)的切換和線程數(shù)據(jù)的更新。由于其輪詢的特性,系統(tǒng)不直接提供自旋鎖的實(shí)現(xiàn),但你可以在特定環(huán)境中簡(jiǎn)單地實(shí)現(xiàn)。(譯者注:已經(jīng)不建議最新系統(tǒng)中使用OSSpinLock,了解更多信息請(qǐng)移步這里) |
| 雙重檢查鎖(Double-checked lock) | 雙重檢查鎖為減少持有鎖帶來(lái)的資源消耗,它總是在持有鎖前先測(cè)試鎖的臨界條件。由于雙重檢查鎖有潛在的安全問(wèn)題,系統(tǒng)并不顯式地提供支持且不鼓勵(lì)使用它。 |
注意:大多數(shù)類型的鎖也包含了內(nèi)存屏障區(qū),以確保進(jìn)入臨界區(qū)前完成先前需要完成的指令。
條件量
條件量作為另一種信號(hào)量類型以允許線程在某種條件滿足時(shí)相互通知。條件量常用于預(yù)示資源的可用性或者確保任務(wù)以特定順序執(zhí)行。當(dāng)線程在測(cè)試條件時(shí),它會(huì)一直阻塞直到條件為真。同樣地,直到其他線程顯式地修改條件前也會(huì)保持阻塞狀態(tài)。條件量與互斥鎖的區(qū)別在于多個(gè)線程能夠同時(shí)訪問(wèn)鎖。條件量更像一個(gè)守門員一樣,它根據(jù)某些特定的條件讓不同的線程通過(guò)這道“門”。
一種可能使用條件量的情形是管理一系列掛起的事件。事件隊(duì)列需要使用一個(gè)條件變量來(lái)通知隊(duì)列中有等待事件的線程。如果事件到達(dá),隊(duì)列會(huì)正確地喚起條件量。如果線程仍在等待,它會(huì)因?yàn)樾枰獙⑹录湃腙?duì)列并處理而被喚醒。如果兩個(gè)事件幾乎同時(shí)進(jìn)入隊(duì)列,隊(duì)列會(huì)通知條件量?jī)纱尾酒饍蓚€(gè)線程。
系統(tǒng)以幾種不同的技術(shù)為條件量提供支持。條件量的實(shí)現(xiàn)需要仔細(xì)的代碼編寫,所以你需要在編寫自己的代碼前查看后面的示例代碼。
Perform Selector
Cocoa應(yīng)用有以同步形式向單個(gè)線程傳遞信息的便利方式。NSObject類聲明了在應(yīng)用活動(dòng)線程上執(zhí)行選擇器的一些方法。這些方法使你的線程異步地傳遞信息并保證在目標(biāo)線程上同步地執(zhí)行。例如,你可能使用這種方式向應(yīng)用的主線程或者指定線程傳遞分布式運(yùn)算的結(jié)果。每次執(zhí)行選擇器的請(qǐng)求都被存儲(chǔ)在線程run loop的事件隊(duì)列中,并且請(qǐng)求會(huì)按照他們被收到時(shí)的順序線性地執(zhí)行。
同步消耗與性能
同步助你確保代碼的正確性,但那樣做確實(shí)損耗不少性能。即便在無(wú)沖突的情況下,同步工具都不是首選。鎖和原子操作通常涉及到內(nèi)存屏障和內(nèi)核級(jí)同步過(guò)程的使用來(lái)確保代碼受到正確保護(hù)。如果出現(xiàn)了鎖的沖突,你的線程會(huì)阻塞并經(jīng)歷更嚴(yán)重的延遲。
表4-2列舉了使用互斥和原子操作的近似消耗。這些數(shù)據(jù)作為從幾千份樣本中采集的平均值。由于考慮了線程的創(chuàng)建時(shí)間,互斥鎖獲取時(shí)間會(huì)根據(jù)處理器的負(fù)載、計(jì)算機(jī)速度、系統(tǒng)內(nèi)存和程序內(nèi)存而千差萬(wàn)別。
表4-2 互斥和原子操作資源消耗
| 指標(biāo) | 近似消耗 | 注釋 |
|---|---|---|
| 互斥鎖獲取時(shí)間 | 接近0.2微秒 | 這是在無(wú)沖突的情況下鎖的獲取時(shí)間。如果鎖被另一個(gè)線程持有,獲取時(shí)間還會(huì)更多。該數(shù)據(jù)由平臺(tái)上(基于Intel處理器的iMac/2GHz雙核處理器/1G內(nèi)存/OS X 10.5系統(tǒng))分析平均值和中位值測(cè)定的數(shù)據(jù)決定。 |
| 原子性的compare-and-swap | 接近0.05微秒 | 這是在無(wú)沖突的情況下執(zhí)行compare-and-swap操作的花費(fèi)時(shí)間。該數(shù)據(jù)由平臺(tái)上(基于Intel處理器的iMac/2GHz雙核處理器/1G內(nèi)存/OS X 10.5系統(tǒng))分析平均值和中位值測(cè)定的數(shù)據(jù)決定。 |
當(dāng)你設(shè)計(jì)并發(fā)任務(wù)時(shí),正確性才通常是最為重要的考慮因素,但是你也應(yīng)該考慮性能因素。代碼在多線程的方式下正確執(zhí)行,但是卻比單線程執(zhí)行更慢,這就難說(shuō)是一種改進(jìn)了。
如果你正在對(duì)一個(gè)現(xiàn)有的單線程應(yīng)用程序進(jìn)行改造,你應(yīng)該經(jīng)常設(shè)置一系列關(guān)鍵任務(wù)的性能基線測(cè)量值。在添加附加的線程后,你應(yīng)該對(duì)于相同的任務(wù)收集新的數(shù)據(jù)并比較單線程和多線程的性能差異。如果在調(diào)整代碼后,線程并沒有提高性能,你可能希望重新考慮你的具體實(shí)現(xiàn)或使用線程的方式問(wèn)題。
更多關(guān)于性能和數(shù)據(jù)收集工具的信息,請(qǐng)看[譯]性能概述。
線程安全與信號(hào)量
當(dāng)遇到多線程應(yīng)用,沒有什么比處理信號(hào)量更讓人恐懼和困惑的了。信號(hào)量是一種低等級(jí)的BSD機(jī)制以便能用于傳輸給信息進(jìn)程或以某種方式處理信息。一些程序使用信號(hào)量來(lái)檢測(cè)特定事件,比如子進(jìn)程的死亡。系統(tǒng)會(huì)使用信號(hào)量來(lái)終止失去控制的進(jìn)程以及傳遞其他類型的信息。
信號(hào)量的問(wèn)題并不在它們做的是什么,而是應(yīng)用有多個(gè)線程時(shí)它們的行為。在單線程應(yīng)用中,所有的信號(hào)量處理回調(diào)都在主線程上。在多線程應(yīng)用中,不依賴于任何硬件錯(cuò)誤的(如非法指令)的信號(hào)被傳遞到任何一個(gè)線程的合適時(shí)機(jī)下運(yùn)行。如果多個(gè)線程同時(shí)運(yùn)行,信號(hào)量會(huì)被傳遞到系統(tǒng)所選的任何一個(gè)線程。換句話說(shuō),信號(hào)量可以被傳遞到應(yīng)用中的任何線程。
在應(yīng)用程序中實(shí)現(xiàn)信號(hào)處理回調(diào)的首要規(guī)則是避免線程處理該信號(hào)的假設(shè)。如果某個(gè)特定的線程需要處理給定的信號(hào),則需要編寫在信號(hào)到達(dá)時(shí)通知該線程的一些方法。你不能只假設(shè)線程中信號(hào)處理回調(diào)的安裝會(huì)讓該信號(hào)被傳遞到這個(gè)線程。
獲取更多關(guān)于信號(hào)量和信號(hào)量回調(diào)安裝的信息,請(qǐng)查看signal和sigaction的man幫助頁(yè)面。