關於多線程的基礎釋義就很少作解釋了,下面引用一句百度百科上的話做爲開頭:ios
多線程是爲了同步完成多項任務,不是爲了提升運行效率,而是爲了提升資源使用效率來提升系統的效率。線程是在同一時間須要完成多項任務的時候實現的。程序員
本文涉及內容數據庫
- 多線程的目的
- 多線程的優缺點
- 爲何要用多線程
- 何時使用多線程
- iOS線程狀態
- iOS線程安全
- iOS常見多線程
- Pthread
- NSThread
- NSOperation
- Grand Central Dispatch(GCD)
隨着計算機技術的發展,編程模型也愈來愈複雜多樣化。但多線程編程模型是目前計算機系統架構的最終模型。隨着CPU主頻的不斷攀升,X86架構的硬件已經成爲瓶,在這種架構的CPU主頻最高爲4G。事實上目前3.6G主頻的CPU已經接近了頂峯。 若是不能從根本上更新當前CPU的架構(在很長一段時間內還不太可能),那麼繼續提升CPU性能的方法就是超線程CPU模式。那麼,做業系統、應用程序要發揮CPU的最大性能,就是要改變到以多線程編程模型爲主的並行處理系統和併發式應用程序。 因此,掌握多線程編程模型,不只是目前提升應用性能的手段,更是下一代編程模型的核心思想。多線程編程的目的,就是"最大限度地利用CPU資源",當某一線程的處理不須要佔用CPU而只資源打交道時,讓須要佔用CPU資源的其它線程有機會得到CPU資源。從根本上說,這就是多線程編程的最終目的。 也就是說多線程的目的不是爲了提升運行效率,而是爲了提升資源使用效率,爲了最大限度地利用CPU資源。 其實仔細想一想,若是這就是正確答案的話就不可避免的產生一個矛盾。假設一下,有一個任務須要分紅不等的小任務執行,若是按照上述結果看,最好是分紅儘量多的線程併發處理,這樣能夠最大限度的利用CPU的資源,而且能夠最短期內完成任務。可是某些狀況會出現不同的狀況,好比說這個任務是數據庫的增刪改查,爲了保證數據的正確性咱們須要進行加鎖同步等處理。任務執行時CPU還須要不斷的切換子線程進行任務處理,這樣看不光沒有提升運行效率,反而拉低了運行效率,並且大量佔用了CPU資源,浪費不少的內存空間(默認狀況下,主線程佔用1M,子線程佔用512KB),那麼到底該不應使用多線程?編程
從上述問題來看,到底該不應使用多線程,爲何要用多線程就是個問題了。爲了想清楚這個問題,接下來先了解一下多線程的優缺點:緩存
- 使用線程能夠把佔據時間長的程序中的任務放到後臺去處理
- 用戶界面能夠更加吸引人,這樣好比用戶點擊了一個按鈕去觸發某些事件的處理,能夠彈出一個進度條來顯示處理的進度
- 程序的運行速度可能加快
- 在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。在這種狀況下能夠釋放一些珍貴的資源如內存佔用等等。
- 多線程技術在IOS軟件開發中也有舉足輕重的位置。
- 線程應用的好處還有不少,就不一一說明了 -- 引自百度百科
- 若是有大量的線程,會影響性能,由於操做系統須要在它們之間切換。
- 更多的線程須要更多的內存空間。
- 線程可能會給程序帶來更多「bug」,所以要當心使用。
- 線程的停止須要考慮其對程序運行的影響。
- 一般塊模型數據是在多個線程間共享的,須要防止線程死鎖狀況的發生。 -- 引自百度百科
爲何要使用多線程,維基百科上有這樣一段話:安全
一個線程持續運行,直到該線程被一個事件擋住而製造出長時間的延遲(多是內存load/store操做,或者程序分支操做)。該延遲一般是因緩存失敗而從核心外的內存讀寫,而這動做會使用到幾百個CPU週期才能將數據回傳。與其要等待延遲的時間,線程化處理器會切換運行到另外一個已就緒的線程。只要當以前線程中的數據送達後,上一個線程就會變成已就緒的線程。這種方法來自各個線程的指令交替執行,能夠有效的掩蓋內存訪問時延,填補流水線空洞。 舉例來講: 週期 i :接收線程 A 的指令 j 週期 i+1:接收線程 A 的指令 j+1 週期 i+2:接收線程 A 的指令 j+2,而這指令緩存失敗 週期 i+3:線程調度器介入,切換到線程 B 週期 i+4:接收線程 B 的指令 k 週期 i+5:接收線程 B 的指令 k+1 在概念上,它與即時操做系統中使用的合做式多任務相似,在該任務須要爲一個事件等待一段時間的時候會主動放棄運行時段。 -- 維基百科bash
假設一下,若是你的程序是單線程,而後網絡比較慢的狀況下下載了一張圖片,個人天,用戶能夠洗洗睡了。 再假設一下,如今有三個任務須要處理。單獨一條線程處理它們分別須要五、三、3秒。若是三個CPU並行處理,那麼一共只須要5秒。相比於串行處理,節約了6秒。而同步/異步,描述的是任務之間前後順序問題。假設須要5秒的那個是保存數據的任務,而另外兩個是UI相關的任務。那麼經過異步執行第一個任務,咱們省去了5秒鐘的卡頓時間。 對於同步執行的三個任務來講,系統傾向於在同一個線程裏執行它們。由於即便開了三個線程,也得等他們分別在各自的線程中完成。並不能減小總的處理時間,反而徒增了線程切換(這就是文章開頭舉的例子) 對於異步執行的三個任務來講,系統傾向於在三個新的線程裏執行他們。由於這樣能夠最大程度的利用CPU性能,提高程序運行效率。 綜合上述而言咱們能夠得出結論,在須要同時處理IO和UI的狀況下,真正起做用的是異步,而不是多線程。能夠不用多線程(由於處理UI很是快),但不能不用異步(不然的話至少要等IO結束)。也就是說異步方法並不必定永遠在新線程裏面執行,反之亦然。 若是這樣說,那何時多線程纔會起到真正意義上的效率?何時該使用多線程進行程序開發?服務器
首先來了解一下這個概念:「多線程的使用主要是用來處理程序‘在一部分上會阻塞’,‘在另外一部分上須要持續運行’的場合」。通常是根據需求,能夠用多線程,事件觸發,callback等方法達到。可是有一些方法是隻有多線程能辦到的就只有用多線程或者多進程來完成。 舉個簡單的例子,能理解就行。假設有這樣一個程序, 1會不停的處理收到的全部TCP請求。對於每一個TCP請求作不一樣的操做。不能有遺漏 2有不少特定的請求會向一個服務器發送存儲的數據,或者是等待用戶輸入。 來看看。第1個要求很簡單。用個while循環就搞定了。但第2個特性呢。一旦在等待用戶輸入或者是鏈接服務器時,程序會「阻塞」一段時間,這一段時間內就沒法處理其餘的TCP請求了。 因此能夠利用多線程,每一個線程處理不一樣的TCP請求。這樣程序就不會「阻塞」掉了。 總之,凡事天然有利有弊,多線程帶來了高效率的同時也帶來了必定程度我不穩定性,上述內容只是在多線程自己的基礎上得出的結論,若是綜合線程安全,同步,通訊等狀況去看就會變得更加負責。 總結一下就是,當應用在前臺操做的同時還須要進行後臺的計算或邏輯判斷的狀況可使用多線程,可是須要綜合考慮使用多線程的不穩定性,儘可能避免因爲使用多線程而產生的新問題。網絡
通常來講,線程有五個狀態多線程
- 新建狀態:線程經過各類方式在被建立之初,尚未調用開始執行方法,這個時候的線程就是新建狀態;
- 就緒狀態:在新建線程被建立以後調用了開始執行方法,可是CPU並非真正的同時執行多個任務,因此要等待CPU調用,這個時候線程處於就緒狀態。處於就緒狀態的線程並不必定當即執行線程裏的代碼,線程還必須同其餘線程競爭CPU時間,只有得到CPU時間才能夠運行線程。
- 運行狀態:線程得到CPU時間,被CPU調用以後就進入運行狀態
- CPU 負責調度可調度線程池中線程的執行
- 線程執行完成以前(死亡以前),狀態可能會在就緒和運行之間來回切換
- 就緒和運行之間的狀態變化由 CPU 負責,程序員不能干預
- 阻塞狀態:所謂阻塞狀態是正在運行的線程沒有運行結束,暫時讓出CPU,這時其餘處於就緒狀態的線程就能夠得到CPU時間,進入運行狀態。
- 線程經過調用sleep方法進入睡眠狀態
- 線程調用一個在I/O上被阻塞的操做,即該操做在輸入輸出操做完成以前不會返回到它的調用者
- 線程試圖獲得一個鎖,而該鎖正被其餘線程持有;
- 線程在等待某個觸發條件
- 死亡狀態:當線程的任務結束,發生異常,或者是強制退出這三種狀況會致使線程的死亡。線程死亡後,線程對象從內存中移除。一旦線程進入死亡狀態,再次嘗試從新開啓線程,則程序會掛。
一塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源,好比多個線程訪問同一個對象、同一個變量、同一個文件和同一個方法等。所以當多個線程訪問同一塊資源時,很容易會發生數據錯誤及數據不安全等問題。所以要避免這些問題,咱們須要使用某些方式保證線程的安全,好比「線程鎖」。
這邊有一段代碼,能夠先用來測試一下:
@property (atomic, assign) int intA;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//thread A
for (int i = 0; i < 10000; i ++) {
self.intA = self.intA + 1;
NSLog(@"Thread A: %d\n", self.intA);
}
});
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//thread B
for (int i = 0; i < 10000; i ++) {
self.intA = self.intA + 1;
NSLog(@"Thread B: %d\n", self.intA);
}
});
複製代碼
即便將intA聲明爲atomic,最後的結果也不必定會是20000。緣由就是由於self.intA = self.intA + 1;不是原子操做,雖然intA的getter和setter是原子操做,但當咱們使用intA的時候,整個語句並非原子的,這行賦值的代碼至少包含讀取(load),+1(add),賦值(store)三步操做,當前線程store的時候可能其餘線程已經執行了若干次store了,致使最後的值小於預期值。這種場景就是多線程不安全的表現之一。
爲了更加安全的使用多線程,也爲了代碼能夠正確的執行,咱們須要一種保證線程安全的機制,「線程鎖」就誕生了,接下來將簡單的瞭解一下iOS中的經常使用鎖。 在網上查找了有些資料,發現大多數資料只介紹瞭如下幾種鎖:
- OSSpinLock (暫不建議使用,緣由參見這裏)
- dispatch_semaphore
- pthread_mutex
- NSLock
- NSCondition
- pthread_mutex(recursive)
- NSRecursiveLock
- NSCondition
- @synchronized
這張圖片是在網上找到的關於這七種鎖的性能測試:
固然除了這七種鎖以外iOS還提供了不少其餘的鎖,好比C語言實現的讀寫鎖(Read-write Lock),自旋鎖(Spin Lock)等 這裏就不作對鎖的解釋了,文章下邊會有連接對各類鎖在iOS中的應用作詳細解釋,還有配合iOS經常使用多線程方法寫的一些小demo,對這方面有興趣的能夠去看一下。接下來就介紹一下iOS常見的幾種多線程實現方式,由於篇幅比較長,因此寫到另外幾篇文章裏邊:
這篇文章主要是介紹Pthreads的,裏邊會有Pthreads的基礎釋義,也有經常使用API與屬性的介紹,固然也會介紹一些經常使用鎖和Pthreads的配合使用,連接在這Pthread。然而裏邊並不會針對所有的鎖作解析,只是針對某幾種鎖作釋義解析以及與Pthreads的配合使用。
而後是NSThread這個,在網上找了不少資料,看了不少文章,可是老是不太符合本身的心意。恰好公司有機會讓我參與多線程的調研,就試着總結了一下它的經常使用屬性與API,也有一些加鎖代碼。
GCD的內容太多了,到發出此連接的時候已經修改過三次了。仍是有不少知識沒有涉及到,但願之後有時間補上吧。Grand Central Dispatch,這是GCD調研結果的連接。 ####4. NSOperation 全程看着官方文檔寫的,附帶了一些應用實例,函數解析等NSOperation。
以上,就是本人對多線程的調研結果。調研期間我看到這樣一句話「咱們的目的不是研究出多麼牛逼的鎖,而是研究安全更高效的多線程方式」,固然,在沒有沒這樣牛逼的多線程實現方式的時候,仍是有必要了解一下各類鎖的優缺點的。
有志者、事竟成,破釜沉舟,百二秦關終屬楚; 苦心人、天不負,臥薪嚐膽,三千越甲可吞吳.