天天看點

iOS多線程程式設計指南(附錄)術語表結束語

本附錄描述了Mac OS X和iOS上面一些關鍵的進階線程安全的架構。本附錄的資訊有可能會發生改變。

Cocoa

在Cocoa上面使用多線程的指南包括以下這些:

  • 不可改變的對象一般是線程安全的。一旦你建立了它們,你可以把這些對象線上程間安全的傳遞。另一方面,可變對象通常不是線程安全的。為了在多線程應用裡面使用可變對象,應用必須适當的同步。關于更多資訊,參閱”可變和不可變對比”。
  • 許多對象在多線程裡面不安全的使用被視為是”線程不安全的”。隻要同一時間隻有一個線程,那麼許多這些對象可以被多個線程使用。這種被稱為專門限制應用程式的主線程的對象通常被這樣調用。
  • 應用的主線程負責處理事件。盡管Application Kit在其他線程被包含在事件路徑裡面時還會繼續工作,但操作可能會被打亂順序。
  • 如果你想使用一個線程來繪畫一個視圖,把所有繪畫的代碼放在NSView的lockFocusIfCanDraw和unlockFocus方法中間。

為了在Cocoa裡面使用POSIX線程,你必須首先把Cocoa變為多線程模式。關于更多資訊,參閱“在Cocoa應用裡面使用POSIX線程”部分。

基礎架構(Fondation Framework)的線程安全

有一種誤解,認為基礎架構(Foundation framework)是線程安全的,而Application Kit是非線程安全的。不幸的是,這是一個總的概括,進而造成一點誤導。每個架構都包含了線程安全部分和非線程安全部分。以下部分介紹Foundation framework裡面的線程安全部分。

線程安全的類和函數

下面這些類和函數通常被認為是線程安全的。你可以在多個線程裡面使用它們的同一個執行個體,而無需擷取一個鎖。

  • NSArray
  • NSAssertionHandler
  • NSAttributedString
  • NSCalendarDate
  • NSCharacterSet
  • NSConditionLock
  • NSConnection
  • NSData
  • NSDate
  • NSDecimal functions
  • NSDecimalNumber
  • NSDecimalNumberHandler
  • NSDeserializer
  • NSDictionary
  • NSDistantObject
  • NSDistributedLock
  • NSDistributedNotificationCenter
  • NSException
  • NSFileManager (in Mac OS X v10.5 and later)
  • NSHost
  • NSLock
  • NSLog/NSLogv
  • NSMethodSignature
  • NSNotification
  • NSNotificationCenter
  • NSNumber
  • NSObject
  • NSPortCoder
  • NSPortMessage
  • NSPortNameServer
  • NSProtocolChecker
  • NSProxy
  • NSRecursiveLock
  • NSSet
  • NSString
  • NSThread
  • NSTimer
  • NSTimeZone
  • NSUserDefaults
  • NSValue
  • 還有對象的allocation和retain函數
  • Zone和記憶體函數

非線程安全類

以下這些類和函數通常被認為是非線程安全的。在大部分情況下,你可以在任何線程裡面使用這些類,隻要你在同一個時間隻在一個線程裡面使用它們。參考這些類對于的額外詳細資訊的文檔。

  • NSArchiver
  • NSAutoreleasePool
  • NSBundle
  • NSCalendar
  • NSCoder
  • NSCountedSet
  • NSDateFormatter
  • NSEnumerator
  • NSFileHandle
  • NSFormatter
  • NSHashTable functions
  • NSInvocation
  • NSJavaSetup functions
  • NSMapTable functions
  • NSMutableArray
  • NSMutableAttributedString
  • NSMutableCharacterSet
  • NSMutableData
  • NSMutableDictionary
  • NSMutableSet
  • NSMutableString
  • NSNotificationQueue
  • NSNumberFormatter
  • NSPipe
  • NSPort
  • NSProcessInfo
  • NSRunLoop
  • NSScanner
  • NSSerializer
  • NSTask
  • NSUnarchiver
  • NSUndoManager
  • User name and home directory functions

注意,盡管NSSerializer,NSArchiver,NSCoder和NSEnumerator對象本身是線程安全的,但是它們被放置這這裡是因為當它們封裝的對象被使用的時候,更改這些對象資料是不安全的。比如,在歸檔情況下,修改被歸檔的對象是不安全的。對于一個枚舉,任何線程修改枚舉的集合都是不安全的。

隻能用于主線程的類

以下的類必須隻能在應用的主線程類使用。

  • NSAppleScript

可變 vs 不可變

不可變對象通常是線程安全的。一旦你建立了它們,你可以把它們安全的線上程間傳遞。目前,在使用不可變對象時,你還應該記得正确使用引用計數。如果不适當的釋放了一個你沒有引用的對象,你在随後有可能造成一個異常。

可變對象通常是非線程安全的。為了在多線程應用裡面使用可變對象,應用應該使用鎖來同步通路它們(關于更多資訊,參見“原子操作”部分)。通常情況下,集合類(比如,NSMutableArray,NSMutableDictionary)是考慮多變時是非線程安全的。這意味着,如果一個或多個線程同時改變一個數組,将會發生問題。你應該線上程讀取和寫入它們的地方使用鎖包圍着。

即使一個方法要求傳回一個不可變對象,你不應該簡單的假設傳回的對象就是不可變的。依賴于方法的實作,傳回的對象有可能是可變的或着不可變的。比如,一個傳回類型是NSString的方法有可能實際上由于它的實作傳回了一個NSMutableString。如果你想要確定對象是不可變的,你應該使用不可變的拷貝。

可重入性

可重入性是可以讓同一對象或者不同對象上一個操作“調用”其他操作成為可能。保持和釋放對象就是一個有可能被忽視的”調用”的例子。

以下清單列出了Foundation framework的部分顯式的可重入對象。所有其他類可能是或可能不是可重入的,或者它們将來有可能是可重入的。對于可重入性的一個完整的分析是不可能完成的,而且該清單将會是無窮盡的。

  • Distributed Objects
  • NSConditionLock
  • NSDistributedLock
  • NSLock
  • NSLog/NSLogv
  • NSNotificationCenter
  • NSRecursiveLock
  • NSRunLoop
  • NSUserDefaults

類的初始化

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架構的線程安全

以下部分介紹了Application Kit架構的線程安全。

非線程安全類

以下這些類和函數通常是非線程安全的。大部分情況下,你可以在任何線程使用這些類,隻要你在同一時間隻有一個線程使用它們。檢視這些類的文檔來獲得更多的詳細資訊。

  • NSGraphicsContext。多資訊,參見“NSGraphicsContext 限制”。
  • NSImage.更多資訊,參見“NSImage 限制”。
  • NSResponder。
  • NSWindow和所有它的子類。更多資訊,參見“Window 限制

隻能用于主線程的類

以下的類必須隻能在應用的主線程使用。

  1. NSCell和所有它的子類。
  2. NSView和所有它的子類。更多資訊,參見“NSView 限制”。

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架構通常支援多線程,盡管需要注意一些使用注意事項。關于這些注意事項的更多資訊,參閱Core Data Programing Guide的“Multi-Threading with Core Data”部分。

Core Foundation(核心架構)

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的細節,将會幫助你寫出更優美的代碼。同步是多線程程式設計的老生常談,估計大學時候大家都基本熟悉了同步的重要性。

最後,本文在翻譯過程中發現很多地方直譯成中文比較晦澀,是以采用了意譯的方式,這不可避免的造成有一些地方可能和原文有一定的出入,是以如果你閱讀的時候發現有任何的錯誤都可以給我發郵件:xyl.layne(a)gmail.com。

最後可以關注我微網誌大家一起溝通交流學習。

微網誌位址: http://weibo.com/u/1826448972

繼續閱讀