在Cocoa上面使用多線程的指南包括如下這些:html
(1)不可改變的對象通常是線程安全的。一旦你建立了它們,你能夠把這些對象在線程間安全的傳遞。另外一方面,可變對象一般不是線程安全的。爲了在多線程應用裏面使用可變對象,應用必須適當的同步。關於更多信息,參閱」可變和不可變對比」。ios
(2)許多對象在多線程裏面不安全的使用被視爲是」線程不安全的」。只要同一時間只有一個線程,那麼許多這些對象能夠被多個線程使用。這種被稱爲專門限制應用程序的主線程的對象一般被這樣調用。編程
(3)應用的主線程負責處理事件。儘管Application Kit在其餘線程被包含在事件路徑裏面時還會繼續工做,但操做可能會被打亂順序。windows
(4)若是你想使用一個線程來繪畫一個視圖,把全部繪畫的代碼放在NSView的lockFocusIfCanDraw和unlockFocus方法中間。數組
爲了在Cocoa裏面使用POSIX線程,你必須首先把Cocoa變爲多線程模式。關於更多信息,參閱「在Cocoa應用裏面使用POSIX線程」部分。緩存
有一種誤解,認爲基礎框架(Foundation framework)是線程安全的,而Application Kit是非線程安全的。不幸的是,這是一個總的歸納,從而形成一點誤導。每一個框架都包含了線程安所有分和非線程安所有分。如下部分介紹Foundation framework裏面的線程安所有分。安全
線程安全的類和函數服務器
下面這些類和函數一般被認爲是線程安全的。你能夠在多個線程裏面使用它們的同一個實例,而無需獲取一個鎖。數據結構
非線程安全類多線程
如下這些類和函數一般被認爲是非線程安全的。在大部分狀況下,你能夠在任何線程裏面使用這些類,只要你在同一個時間只在一個線程裏面使用它們。參考這些類對於的額外詳細信息的文檔。
注意,儘管NSSerializer,NSArchiver,NSCoder和NSEnumerator對象自己是線程安全的,可是它們被放置這這裏是由於當它們封裝的對象被使用的時候,更改這些對象數據是不安全的。好比,在歸檔狀況下,修改被歸檔的對象是不安全的。對於一個枚舉,任何線程修改枚舉的集合都是不安全的。
只能用於主線程的類
如下的類必須只能在應用的主線程類使用。
可變 vs 不可變
不可變對象一般是線程安全的。一旦你建立了它們,你能夠把它們安全的在線程間傳遞。當前,在使用不可變對象時,你還應該記得正確使用引用計數。若是不適當的釋放了一個你沒有引用的對象,你在隨後有可能形成一個異常。
可變對象一般是非線程安全的。爲了在多線程應用裏面使用可變對象,應用應該使用鎖來同步訪問它們(關於更多信息,參見「原子操做」部分)。一般狀況下,集合類(好比,NSMutableArray,NSMutableDictionary)是考慮多變時是非線程安全的。這意味着,若是一個或多個線程同時改變一個數組,將會發生問題。你應該在線程讀取和寫入它們的地方使用鎖包圍着。
即便一個方法要求返回一個不可變對象,你不該該簡單的假設返回的對象就是不可變的。依賴於方法的實現,返回的對象有多是可變的或着不可變的。好比,一個返回類型是NSString的方法有可能實際上因爲它的實現返回了一個NSMutableString。若是你想要確保對象是不可變的,你應該使用不可變的拷貝。
可重入性
可重入性是可讓同一對象或者不一樣對象上一個操做「調用」其餘操做成爲可能。保持和釋放對象就是一個有可能被忽視的」調用」的例子。
如下列表列出了Foundation framework的部分顯式的可重入對象。全部其餘類多是或可能不是可重入的,或者它們未來有多是可重入的。對於可重入性的一個完整的分析是不可能完成的,並且該列表將會是無窮盡的。
類的初始化
Objective-C的運行時系統在類收到其餘任何消息以前給它發送一個initialize消息。這可讓類有機會在它被使用前設置它的運行時環境。在一個多線程應用裏面,運行時保證僅有一個線程(該線程剛好發送第一條消息給類)執行initialized方法,第二個線程阻塞直到第一個線程的initialize方法執行完成。在此期間,第一個線程能夠繼續調用其餘類上的方法。該initialize方法不該該依賴於第二個線程對這個類的調用。若是不是這樣的話,兩個線程將會形成死鎖。
自動釋放池(Autorelease Pools)
每一個線程都維護它本身的NSAutoreleasePool的棧對象。Cocoa但願在每一個當前線程的棧裏面有一個可用的自動釋放池。若是一個自動釋放池不可用,對象將不會給釋放,從而形成內存泄露。對於Application Kit的主線程一般它會自動建立並消耗一個自動釋放池,可是輔助線程(和其餘只有Foundationd的程序)在使用Cocoa前必須本身手工建立。若是你的線程是長時間運行的,那麼有可能潛在產生不少自動釋放的對象,你應該週期性的銷燬它們並建立自動釋放池(就像Application Kit對主線程那樣)。不然,自動釋放對象將會積累並形成內存大量佔用。若是你的脫離線程沒有使用Cocoa,你不須要建立一個自動釋放池。
Run Loops
每一個線程都有一個或多個run loop。然而每一個run loop和每一個線程都有它本身的輸入模式來決定run loop運行的釋放監聽那些輸入源。輸入模式定義在一個run loop上面,不會影響定義在其餘run loop的輸入模式,即便它們的名字相同。
若是你的線程是基於Application Kti的話,主線程的run loop會自動運行,可是輔助線程(和只有Foundation的應用)必須本身啓動它們的run loop。若是一個脫離線程沒有進入run loop,那麼線程在完成它們的方法執行後會當即退出。
儘管外表顯式多是線程安全的,可是NSRunLoop類是非線程安全的。你只能在擁有它們的線程裏面調用它實例的方法。
如下部分介紹了Application Kit框架的線程安全。
非線程安全類
如下這些類和函數一般是非線程安全的。大部分狀況下,你能夠在任何線程使用這些類,只要你在同一時間只有一個線程使用它們。查看這些類的文檔來得到更多的詳細信息。
只能用於主線程的類
如下的類必須只能在應用的主線程使用。
Window 限制
你能夠在輔助線程建立一個window。Application Kit確保和window相關的數據結構在主線程釋放來避免產生條件。在同時包含大量windows的應用中,window對象有可能會發生泄漏。
你也能夠在輔助線程建立modal window。在主線程運行modal loop時,Application Kit阻塞輔助線程的調用。
事件處理例程限制
應用的主線程負責處理事件。主線程阻塞在NSApplication的run方法,一般該方法被包含在main函數裏面。在Application Kit繼續工做時,若是其餘線程被包含在事件路徑,那麼操做有可能打亂順序。好比,若是兩個不一樣的線程負責關鍵事件,那麼關鍵事件有可能不是按照順序到達。經過讓主線程來處理事件,事件能夠被分配到輔助線程由它們處理。
你能夠在輔助線程裏面使用NSApplication的postEvent:atStart方法傳遞一個事件給主線程的事件隊列。然而,順序不能保證和用戶輸入的事件順序相同。應用的主線程仍然輔助處理事件隊列的事件。
繪畫限制
Application Kit在使用它的繪畫函數和類時一般是線程安全的,包括NSBezierPath和NSString類。關於使用這些類的詳細信息,在如下各部分介紹。關於繪畫的額外信息和線程能夠查看Cocoa Drawing Guide。
a) NSView限制
NSView一般是線程安全的,包含幾個異常。你應該僅在應用的主線程裏面執行對NSView的建立、銷燬、調整大小、移動和其餘操做。在其餘輔助線程裏面只要你把繪畫的代碼放在lockFocusIfCanDraw和unlockFocus方法之間也是線程安全的。
若是應用的輔助線程想要告知主線程重繪視圖,必定不能在輔助線程直接調用display,setNeedsDisplay:,setNeedsDisplayInRect:,或setViewsNeedDisplay:方法。相反,你應該給給主線程發生一個消息讓它調用這些方法,或者使用performSelectorOnMainThread:withObject:waitUntilDone:方法。
系統視圖的圖形狀態(gstates)是基於每一個線程不一樣的。使用圖形狀態能夠在單線程的應用裏面得到更好的繪畫性能,可是如今已經不是這樣了。不正確使用圖形狀態可能致使主線程的繪畫代碼更低效。
b) NSGraphicsContext 限制
NSGraphicsContext類表明了繪畫上下文,它由底層繪畫系統提供。每一個NSGraphicsContext實例都擁有它獨立的繪畫狀態:座標系統、裁剪、當前字體等。該類的實例在主線程自動建立本身的NSWindow實例。若是你在任何輔助線程執行繪畫操做,須要特定爲該線程建立一個新的NSGraphicsContext實例。
若是你在任何輔助線程執行繪畫,你必須手工的刷新繪畫調用。Cocoa不會自動更新輔助線程繪畫的內容,因此你當你完成繪畫後須要調用NSGraphicsContext的flusGrahics方法。若是你的應用程序只在主線程繪畫,你不須要刷新繪畫調用。
c) NSImage限制
線程能夠建立NSImage對象,把它繪畫到圖片緩衝區,還能夠把它傳遞給主線程來繪畫。底層的圖片緩存被全部線程共享。關於圖片和如何緩存的更多信息,參閱Ccocoa Drawing Guide。
Core Data框架一般支持多線程,儘管須要注意一些使用注意事項。關於這些注意事項的更多信息,參閱Core Data Programing Guide的「Multi-Threading with Core Data」部分。
Core Foundation是足夠線程安全的,若是你的程序注意一下的話,應該不會遇到任何線程競爭的問題。一般狀況下是線程安全的,好比當你查詢(query)、引用(retain)、釋放(release)和傳遞(pass)不可變對象時。甚至在多個線程查詢中央共享對象也是線程安全的。
像Cocoa那樣,當涉及對象或它們內容突變時,Core Foundation是非線程安全的。好比,正如你所指望的,不管修改一個可變數據或可變數組對象,仍是修改一個可變數組裏面的對象都是非線程安全的。其中一個緣由是性能,這是在這種狀況下的關鍵。此外,在該級別上實現徹底線程安全是幾乎不可能的。例如,你不能排除從集合中引用(retain)一個對象產生的沒法肯定的結果。該集合自己在被調用來引用(retain)它所包含的對象以前有可能已經被釋放了。
這些狀況下,當你的對象被多個線程訪問或修改,你的代碼應該在相應的地方使用鎖來保護它們不要被同時訪問。例如,枚舉Core Foundation數組對象的代碼,在枚舉塊代碼周圍應該使用合適的鎖來保護它免遭其餘線程修改。
應用(application)
一個顯示一個圖形用戶界面給用戶的特定樣式程序。
條件(condition)
一個用來同步資源訪問的結構。線程等待某一條件來決定是否被容許繼續運行,直到其餘線程顯式的給該條件發送信號。
臨界區(critical section)
同一時間只能不被一個線程執行的代碼。
輸入源(input source)
一個線程的異步事件源。輸入源能夠是基於端口的或手工觸發,而且必須被附加到某一個線程的run loop上面。
可鏈接的線程(join thread)
退出時資源不會被當即回收的線程。可鏈接的線程在資源被回收以前必須被顯式脫離或由其餘線程鏈接。可鏈接線程提供了一個返回值給鏈接它的線程。
主線程(main thread)
當建立進程時一塊兒建立的特定類型的線程。當程序的主線程退出,則程序即退出。
互斥鎖(mutex)
提供共享資源互斥訪問的鎖。一個互斥鎖同一時間只能被一個線程擁有。試圖獲取一個已經被其餘線程擁有的互斥鎖,會把當前線程置於休眠狀態知道該鎖被其餘線程釋放並讓當前線程得到。
操做對象(operation object)
NSOperation類的實例。操做對象封裝了和某一任務相關的代碼和數據到一個執行單元裏面。
操做隊列(operation queue)
NSOperationQueue類的實例。操做隊列管理操做對象的執行。
進程(process)
應用或程序的運行時實例。一個進程擁有獨立於分配給其餘程序的的內存空間和系統資源(包括端口權限)。進程老是包含至少一個線程(即主線程)和任意數量的額外線程。
程序(program)
能夠用來執行某些任務的代碼和資源的組合。程序不須要一個圖形用戶界面,儘管圖形應用也被稱爲程序。
遞歸鎖(recursive lock)
能夠被同一線程屢次鎖住的鎖。
Run loop(運行循環)
一個事件處理循環,在此期間事件被接收並分配給合適的處理例程。
Run loop模式(run loop mode)
與某一特定名稱相關的輸入源、定時源和run loop觀察者的集合。當運行在某一特定「模式」下,一個run loop監視和該模式相關的源和觀察者。
Run loop對象(run loop object)
NSRunLoop類或CFRunLoopRef不透明類型的實例。這些對象提供線程裏面實現事件處理循環的接口。
Run loop觀察者(run loop observer)
在run loop運行的不一樣階段時接收通知的對象。
信號量(semaphore)
一個受保護的變量,它限制共享資源的訪問。互斥鎖(mutexes)和條件(conditions)都是不一樣類型的信號量。
任務(task)
要執行的工做數量。儘管一些技術(最顯著的是Carbon 多進程服務—Carbon Multiprocessing Services)使用該術語的意義有時不一樣,可是最通用的用法是代表須要執行的工做數量的抽象概念。
線程(thread)
進程裏面的一個執行過程流。每一個線程都有它本身的棧空間,但除此以外同一進程的其餘線程共享內存。
定時源(timer source)
爲線程同步事件的源。定時器產生預約時間將要執行的一次或重複事件。
多線程編程在開發應用的時候很是有幫助。好比你能夠在後臺加載圖片,等圖片加載完成後再在主線程更新等,或者在後臺處理一些須要佔用CPU很長時間的事件(好比請求服務器,加載數據等)。要體會多線程編程的好處,還得多實戰,結合使用多種多線程技術。特別要注意Run Loop的使用,不少開發者在編寫多線程應用的時候不多關注過Run Loop。若是你仔細閱讀並掌握Run Loop的細節,將會幫助你寫出更優美的代碼。同步是多線程編程的老生常談,估計大學時候你們都基本熟悉了同步的重要性。
最後,本文在翻譯過程當中發現不少地方直譯成中文比較晦澀,因此採用了意譯的方式,這不可避免的形成有一些地方可能和原文有必定的出入,因此若是你閱讀的時候發現有任何的錯誤均可以討論指正。