【IOS開發】《多線程編程指南》筆記

      線程是單個應用中能夠併發執行多個代碼路徑的多種技術之一。雖然更新的技術如操做對象(Operation)和Grand Central Dispatch(GCD),提供一個等價現代化和高效的基礎設施來實現多核併發,可是Mac OS 和IOS也提供一套接口來建立和管理線程。編程

第一章:關於多線程編程數據結構

      處理器已經達到瓶頸限制,因此芯片開始轉向多核,這就是爲何要多核併發。多線程

1.1 什麼是多線程併發

多線程是一個比較輕量級的方法來實現單個應用程序多個代碼執行路徑。app

在非併發程序中,只有一個執行程序,該線程開始和結束與你應用程序的main循環。一個個方法和函數的分支構成了你整個應用程序的全部行爲。與此相反,支持併發的應用程序開始能夠在須要額外的執行路徑的時候建立一個或多個線程。每一個新的執行路徑有它本身獨立於應用程序main循環的定製開始循環。在應用程序中存在多個線程提供了兩個很是重要的潛在優點。框架

  • 多個線程能夠提升應用程序的感知響應。
  • 多個線程能夠提升應用程序在多核系統上的實時性能。

多線程的問題:由於多個線程這間可能有交互,會訪問相同的數據結構。若是兩個程序試圖同時訪問相同的數據結構,那麼容易對數據進行交叉影響。異步

1.2 編程術語socket

線程(thread):用於指代獨立執行的代碼段。分佈式

進程(process):用於指代一個正在運行的可執行程序,他包含多個線程。函數

任務(task):用於指代抽象的概念,表示須要執行工做。

1.3 多線程替代方法

  • operation objects
  • Grand Central Dispatch
  • Idle-time notifications
  • Asynchronous functions
  • Timers
  • Separate processes

注意:當使用fork函數加載獨立進程的時候,你必須老是在fork後面調用exec或者相似的函數。基於 Core Foundation、Cocao 或者 Core Data 框架(不管顯式仍是隱式關聯)的應用程序隨後調用 exec 函數或者相似的函數都會導出不肯定的結果。 

1.4 線程支持

1.4.1 線程包

多線程的底層實現機制是Mach的線程。可是不多使用Mach級的線程。咱們常用的時POSIX的API或者它的衍生工具。

應用程序中使用的線程技術:

  • Cocoa threads
  • POSIX threads
  • Multiprocessing Services

線程狀態:運行(running)、就緒(ready)、阻塞(blocked)。

建立線程必須指定該線程的入口點函數(Cocoa線程爲入口點方法),函數是由你想在該線程上執行的代碼組成,但函數返回的時候,或你顯式的中斷線程的時候,線程永久中止,而且被系統回收。由於它的內存和時間消耗很大,因此在入口函數要作至關數量的工做,或創建一個運行循環容許進行常常性工做。

1.4.2 Run Loops

Run Loop是用來在線程上管理事件異步到達的基礎設施,一個run loop爲線程檢測一個或多個事件源。

1.4.3 同步工具

線程編程的危害就是多個資源爭奪,因此咱們必須儘量的使用鎖,條件,原子操做和其餘技術來同步資源的訪問。

鎖:

提供了一次只有一個線程能夠執行代碼的有效保護形式。最簡單的一種鎖互斥排他鎖(mutex)。

Cocoa提供了幾個互斥排他鎖的變種來支持不一樣的行爲類型,好比遞歸。

條件:

確保應用程序任務執行的適當順序,一個條件做爲一個看門人,阻塞給定的線程。POSIZ級別和基礎框架都直接提供了條件的支持。(若是你使用操做對象,你能夠配置你的操做對象之間的依賴關係的順序確 定任務的執行順序,這和條件提供的行爲很是類似)。 

原子操做:

能夠執行標量數據類型的數學或邏輯運算,原子操做使用特殊的硬件設施來保證變量的改變在其餘線程能夠訪問前完成。

1.4.4 線程間通訊

Mac OS X上面使用的通訊機制。(異常的消息隊列和Cocoa分佈式對象,這些也能夠在IOS上通訊)

  • Direct messaging
  • Global variables, shared memory,and objects 
  • Conditions
  • Run loop sources 
  • Ports and sockets 
  • Message queues 
  • Cocoa distributed objects 

Mac OS X iOS 經過其餘 API 接口提供了隱式的併發支持。你能夠考慮使用異步 API, GCD 方式,或操做對象來實現併發,而不是本身建立一個線程。這些技術背後爲你作 了線程相關的工做,並保證是無誤的。 此外,好比 GCD 和操做對象技術被設計用來管理線程,比經過本身的代碼根據當前的負載調整活動線程的數量更高效。  

1.5.2 保持你的線程合理的忙

1.5.3 避免共享數據結構

最簡單的方法就是給你應用程序的每一個線程一份它需求的數據的副本。

1.5.4 多線程和你的用戶界面

若是程序有圖形界面,建議在主線程中接受和界面相關的時間和初始化更新你的界面。有利於避免與處理用戶事件和窗口繪圖相關的同步問題。

1.5.5 瞭解線程退出時的行爲

若是你正在編程 Cocoa 的程序,你也能夠經過使用 applicationShouldTerminate: 的委託方法來延遲程序的中斷直到一段時間後或者完成取消。當延遲中斷的時候,你 的程序須要等待直到任何週期線程已經完成它們的任務且調用了 replyToApplicationShouldTerminate:方法。 

1.5.6 異常處理

若是你須要通知另外一個線程(好比主線程)當前線程中的一個特殊狀況,你應該 捕捉異常,並簡單地將消息發送到其餘線程告知發生了什麼事。根據你的模型和你正 在嘗試作的事情,引起異常的線程能夠繼續執行(若是可能的話),等待指示,或者 乾脆退出。

注意:Cocoa 裏面,一個 NSException 對象是一個自包含對象,一旦它被引起了,那麼它 能夠從一個線程傳遞到另一個線程。

在一些狀況下,異常處理多是自動建立的。好比,Objective-C 中的 @synchronized 包含了一個隱式的異常處理。 

1.5.7 乾淨的中端你的線程

 


 

第二章:線程管理

2.1 線程成本

2.2 建立一個線程

2.2.1 使用NSThread

使用NSThread來建立線程有兩個方法

使用 detachNewThreadSelector:toTarget:withObject:類方法來生成一個

新的線程。
建立一個新的 NSThread 對象,並調用它的 start 方法。(僅在 iOS Mac OS X v10.5 及其以後才支持) 

initWithTarget:selector:object:方法。該方法和 detachNewThreadSelector:toTarget:withObject:方法來初始化一個新的 NSThread 實例須要相同的額外開銷。然而它並無啓動一個線程。爲了啓動一個線程,你能夠 顯式調用先對象的 start 方法 

 2.2.2 使用POSIX的多線程

2.2.3 使用NSObject來生成一個線程

      在 iOS 和 Mac OS X v10.5 及其以後,全部的對象均可能生成一個新的線程,並 用它來執行它任意的方法。方法 performSelectorInBackground:withObject:新生成一個脫離的線程,使用指定的方法做爲新線程的主體入口點。 

      好比,若是你有一些對 象(使用變量 myObj 來表明),而且這些對象擁有一個你想在後臺運行的 doSomething 的方法,你可使用以下的代碼來生成一個新的線程:

[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];

調用該方法的效果和你在當前對象裏面使用 NSThread detachNewThreadSelector:toTarget:withObject:傳遞 selectore,object 做爲參數的方法同樣。 

2.2.4 使用其餘線程技術

     儘管 POSIX 例程和 NSThread 類被推薦使用來建立低級線程,可是其餘基於 C 語 言的技術在 Mac OS X 上面一樣可用。在這其中,惟一一個能夠考慮使用的是多處理 服務(Multiprocessing Services),它自己就是在 POSIX 線程上執行。多處理服務 是專門爲早期的 Mac OS 版本開發的,後來在 Mac OS X 裏面的 Carbon 應用程序上面 一樣適用。若是你有代碼真是有該技術,你能夠繼續使用它,儘管你應該把這些代碼 轉化爲 POSIX。該技術在 iOS 上面不可用。 

2.2.5 在cocoa程序中使用POSIX線程

2.3 配置線程屬性

2.3.1 配置線程的堆棧大小

 

Technology

   

Option

 

Cocoa

In iOS and Mac OS X v10.5 and later, allocate and initialize an NSThread object (do not use thedetachNewThreadSelector:toTarget:withObject: method). Before calling the

start method of the thread object, use thesetStackSize: method to specify the new

stack size.

POSIX

Create a new pthread_attr_t structure and use the pthread_attr_setstacksize

function to change the default stack size. Pass the attributes to the pthread_create

function when creating your thread.

Multiprocessing

Services

Pass the appropriate stack size value to the MPCreateTask function when you create your

thread.

2.3.2 配置線程本地存儲

    每一個線程都維護了一個鍵—值的字典,他能夠在線程裏面的任何地方被訪問。你可使用該字典保存一些信息。

2.3.3 設置線程的脫離狀態

   大部分上層的線程技術都默認建立了脫離線程(Detached thread),他們容許系統在線程完成的時候當即釋放他的數據結構。脫離線程不須要顯示和你的應用程序交互。意味着線程檢索的結果由你來決定。相比之下,系統不回收可鏈接線程(joinable thread)的資源,知道另外一個線程明確加入該線程,這個過程當中可能會阻止線程執行加入。

能夠認爲可鏈接線程相似於子線程,雖然能夠做爲獨立線程運行,可是可鏈接線程在它資源能夠唄系統回收以前必須被其餘線程鏈接,可鏈接線程提供一個顯示的方式把數據傳遞到其餘線程。能夠傳遞一個數據指針或者返回值給pthread_exit函數。其餘函數能夠經過pthread_join拿到這個數據。

重要:在應用程序退出時,脫離線程能夠當即被中斷,而可鏈接線程則不能夠。每一個可鏈接 線程必須在進程被容許能夠退出的時候被鏈接。因此當線程處於週期性工做而不容許被中斷的時 候,好比保存數據到硬盤,可鏈接線程是最佳選擇。 

若是你想要建立可鏈接線程,惟一的辦法是使用 POSIX 線程。POSIX 默認建立的 線程是可鏈接的。爲了把線程標記爲脫離的或可鏈接的,使用 pthread_attr_setdetachstate 函數來修改正在建立的線程的屬性。在線程啓動後, 你能夠經過調用 pthread_detach 函數來把線程修改成可鏈接的。 

2.3.4 設置線程的優先級

若是你想改變線程的優先級,Cocoa POSIX 都提供了一種方法來實現。對於 Cocoa 線程而言,你可使用 NSThread setThreadPriority:類方法來設置當前運 行線程的優先級。對於 POSIX 線程,你可使用 pthread_setschedparam 函數來實現。 

2.4 編寫你線程的主題入口點

2.4.1 建立一個自動釋放池

在OC框架連接的應用程序,一般每個線程必須建立至少一個自動釋放池。

若是你的應用使用內存管理模型,在你編寫線程主體入口的時候第一件事情就是建立一個自動釋放池,一樣,在線程的最後應該銷燬該自動釋放池。

2.4.2 設置異常處理

在異常發生的地方捕捉而且處理它,可是若是在你的線程裏面捕捉一個拋出的異常失敗的話可能照成你的應用程序強退,在你線程的主要入口點安裝一個try/catch模塊,能夠捕捉任何未知的異常,並提供一個合適的響應。

2.4.3 設置一個run loop

當你想編寫一個獨立運行的線程時,你有兩個選擇。第一種選擇是寫代碼做爲一個長期任務,不多甚至不中斷,線程完成的時候退出。第二種是把線程放在一個循環中,讓他動態的處理到來的任務請求。

cocoa,carbon,UIKit在你的應用程序的主線程中自動啓動了一個run loop,可是若是你要建立人和網輔助線程,你必須本身手工設置一個run lloop而且啓動他。

2.5 中斷線程

退出一個線程推薦的方法是讓它在它主體入口點正常退出,儘管Cocoa、POSIX和Mutiprocessing Services提供了直接殺死線程的方法。可是使用這些方法是強烈不鼓勵的,由於他們阻止了線程自己清理工做。可能照成內存泄露,而且其餘線程當前使用的資源可能沒有被正確清理乾淨,以後照成潛在的問題。

若是程序須要中斷一個線程,應該設計線程響應取消或退出的消息。

響應取消消息的一個方法是使用 run loop 的輸入源來接收這些消息。列表 2-3 顯示了該結構的相似代碼在你的線程的主體入口裏面是怎麼樣的(該示例顯示了主循 環部分,不包括設立一個自動釋放池或配置實際的工做步驟)。該示例在 run loop 上面安裝了一個自定義的輸入源,它能夠從其餘線程接收消息。關於更多設置輸入源 的信息,參閱「配置 Run Loop 源」。執行工做的總和的一部分後,線程運行的 run loop 來查看是否有消息抵達輸入源。若是沒有,run loop 當即退出,而且循環繼續處理 下一個數據塊。由於該處理器並無直接的訪問 exitNow 局部變量,退出條件是經過 線程的字典來傳輸的。

Listing 2-3 Checking for an exit condition during a long job

- (void)threadMainRoutine
{

BOOL moreWorkToDo = YES;
BOOL exitNow = NO;
NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
 
// Add the exitNow BOOL to the thread dictionary.
NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
[threadDict setValue:[NSNumber numberWithBool:exitNow]forKey:@"ThreadShouldExitNow"];


// Install an input source.

[self myInstallCustomInputSource];

while (moreWorkToDo && !exitNow)

{

// Do one chunk of a larger body of work here.// Change the value of the moreWorkToDo Boolean when done.// Run the run loop but timeout immediately if the input source isn't waiting to fire.
[runLoop runUntilDate:[NSDate date]];
// Check to see if an input source handler changed the exitNow value.
exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue];

}


第三章 run loop

Run loop 接收輸入事件來自兩種不一樣的來源:輸入源(input source)和定時源 (timer source)。輸入源傳遞異步事件,一般消息來自於其餘線程或程序。定時源 則傳遞同步事件,發生在特定時間或者重複的時間間隔。兩種源都使用程序的某一特 定的處理例程來處理到達的事件。 

相關文章
相關標籤/搜索