一
線程(thread)是組成進程的子單元,操做系統的調度器能夠對線程進行單獨的調度。實際上,全部的併發編程 API 都是構建於線程之上的 —— 包括 GCD 和操做隊列(operation queues)。
多線程能夠在單核 CPU 上同時(或者至少看做同時)運行。操做系統將小的時間片分配給每個線程,這樣就可以讓用戶感受到有多個任務在同時進行。若是 CPU 是多核的,那麼線程就能夠真正的以併發方式被執行,從而減小了完成某項操做所須要的總時間。
NSThread 是 Objective-C 對 pthread 的一個封裝。經過封裝
直接使用線程可能會引起的一個問題是,若是你的代碼和所基於的框架代碼都建立本身的線程時,那麼活動的線程數量有可能以指數級增加。這在大型工程中是一個常見問題。例如,在 8 核 CPU 中,你建立了 8 個線程來徹底發揮 CPU 性能。然而在這些線程中你的代碼所調用的框架代碼也作了一樣事情(由於它並不知道你已經建立的這些線程),這樣會很快產生成成百上千的線程。代碼的每一個部分自身都沒有問題,然而最後卻仍是致使了問題。使用線程並非沒有代價的,每一個線程都會消耗一些內存和內核資源。
二
GCD Grand Central Dispatch
經過 GCD,開發者不用再直接跟線程打交道了,只須要向隊列中添加代碼塊便可,GCD 在後端管理着一個
線程池。GCD 不只決定着你的代碼塊將在哪一個線程被執行,它還根據可用的系統資源對這些線程進行管理。這樣能夠將開發者從線程管理的工做中解放出來,經過集中的管理線程,來緩解大量線程被建立的問題。
GCD 帶來的另外一個重要改變是,做爲開發者能夠將工做考慮爲一個隊列,而不是一堆線程,這種並行的抽象模型更容易掌握和使用。
在絕大多數狀況下使用默認的優先級隊列就能夠了。若是執行的任務須要訪問一些共享的資源,那麼在不一樣優先級的隊列中調度這些任務很快就會形成不可預期的行爲。這樣可能會引發程序的徹底掛起,由於低優先級的任務阻塞了高優先級任務,使它不能被執行。
三
操做隊列(operation queue)是由 GCD 提供的一個隊列模型的 Cocoa 抽象。GCD 提供了更加底層的控制,而操做隊列則在 GCD 之上實現了一些方便的功能,這些功能對於 app 的開發者來講一般是最好最安全的選擇。
NSOperationQueue 有兩種不一樣類型的隊列:主隊列和自定義隊列。主隊列運行在主線程之上,而自定義隊列在後臺執行。在兩種類型中,這些隊列所處理的任務都使用 NSOperation 的子類來表述。
你能夠經過重寫 main 或者 start 方法 來定義本身的 operations 。前一種方法很是簡單,開發者不須要管理一些狀態屬性(例如 isExecuting 和 isFinished),當 main 方法返回的時候,這個 operation 就結束了。這種方式使用起來很是簡單,可是靈活性相對重寫 start 來講要少一些。
若是你但願擁有更多的控制權,以及在一個操做中能夠執行異步任務,那麼就重寫 start 方法:
注意:這種狀況下,你必須手動管理操做的狀態。 爲了讓操做隊列可以捕獲到操做的改變,須要將狀態的屬性以配合 KVO 的方式進行實現。若是你不使用它們默認的 setter 來進行設置的話,你就須要在合適的時候發送合適的 KVO 消息。
爲了能使用操做隊列所提供的取消功能,你須要在長時間操做中時不時地檢查 isCancelled 屬性:
當你定義好 operation 類以後,就能夠很容易的將一個 operation 添加到隊列中:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
YourOperation *operation = [[YourOperation alloc] init];
[queue addOperation:operation];
另外,你也能夠將 block 添加到操做隊列中。這有時候會很是的方便,好比你但願在主隊列中調度一個一次性任務:
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// 代碼...
}];
雖然經過這種的方式在隊列中添加操做會很是方便,可是定義你本身的 NSOperation 子類會在調試時頗有幫助。若是你重寫 operation 的description 方法,就能夠很容易的標示出在某個隊列中當前被調度的全部操做 。
除了提供基本的調度操做或 block 外,操做隊列還提供了在 GCD 中不太容易處理好的特性的功能。例如,你能夠經過 maxConcurrentOperationCount 屬性來控制一個特定隊列中能夠有多少個操做參與併發執行。將其設置爲 1 的話,你將獲得一個串行隊列,這在以隔離爲目的的時候會頗有用。
另外還有一個方便的功能就是根據隊列中 operation 的優先級對其進行排序,這不一樣於 GCD 的隊列優先級,它隻影響當前隊列中全部被調度的 operation 的執行前後。若是你須要進一步在除了 5 個標準的優先級之外對 operation 的執行順序進行控制的話,還能夠在 operation 之間指定依賴關係,以下:
[intermediateOperation addDependency:operation1];
[intermediateOperation addDependency:operation2];
[finishedOperation addDependency:intermediateOperation];
這些簡單的代碼能夠確保 operation1 和 operation2 在 intermediateOperation 以前執行. 對於須要明確的執行順序時,操做依賴是很是強大的一個機制。它可讓你建立一些操做組,並確保這些操做組在依賴它們的操做被執行以前執行,或者在併發隊列中以串行的方式執行操做。
互斥鎖
互斥訪問的意思就是同一時刻,只容許一個線程訪問某個特定資源。爲了保證這一點,每一個但願訪問共享資源的線程,首先須要得到一個共享資源的
互斥鎖,一旦某個線程對資源完成了操做,就釋放掉這個互斥鎖,這樣別的線程就有機會訪問該共享資源了。
屬性atomic 表示每次訪問該屬性都會進行隱式的加鎖和解鎖操做
在這裏有一個東西須要進行權衡:獲取和釋放鎖所是要帶來開銷的,所以你須要確保你不會頻繁地進入和退出
臨界區段(好比獲取和釋放鎖)。同時,若是你獲取鎖以後要執行一大段代碼,這將帶來鎖競爭的風險:其它線程可能必須等待獲取資源鎖而沒法工做。這並非一項容易解決的任務。
互斥鎖解決了競態條件的問題,但很不幸同時這也引入了一些
其餘問題,其中一個就是
死鎖。當多個線程在相互等待着對方的結束時,就會發生死鎖,這時程序可能會被卡住。
你在線程之間共享的資源越多,你使用的鎖也就越多,同時程序被死鎖的機率也會變大。這也是爲何咱們須要儘可能減小線程間資源共享,並確保共享的資源儘可能簡單的緣由之一
優先級翻轉問題 、
優先級反轉是指程序在運行時低優先級的任務阻塞了高優先級的任務,有效的反轉了任務的優先級。
使用不一樣優先級的多個隊列聽起來雖然不錯,但畢竟是紙上談兵。它將讓原本就複雜的並行編程變得更加複雜和不可預見。若是你在編程中,遇到高優先級的任務忽然沒理由地卡住了,可能你會想起本文,以及那個美國宇航局的工程師也遇到過的被稱爲優先級反轉的問題。
UIKit 不是線程安全的,推薦只訪問主線程,而且甚至是繪圖方法他們都沒有明確地表示保證線程安全。
在後臺使用 UIKit 對象的的危險之處在於「內存回收問題」。要求 UI 對象應該在主線程中被回收,由於在它們的 dealloc方法被調用回收的時候,可能會去改變 view 的結構關係,而如咱們所知,這種操做應該放在主線程來進行。
NSArry 這樣不可變類是線程安全的
NSMutableArray 是線程不安全的
NSCache 使用一個可變的字典來存儲不可變數據,它不只會對訪問加鎖,更甚至在低內存狀況下會清空本身的內容。
四
原子屬性
一個非原子的 setter 看起來是這個樣子的:
- (void)setUserName:(NSString *)userName {
if (userName != _userName) {
[userName retain];
[_userName release];
_userName = userName;
}
}
要是 setUserName: 被併發調用的話會形成麻煩。咱們可能會釋放 _userName 兩次,這回使內存錯誤,而且致使難以發現的 bug。
objc_setProperty_non_gc(self, _cmd,
(ptrdiff_t)(&_userName) - (ptrdiff_t)(self), userName, NO, NO);`
objc_setProperty 調用的是以下方法:
static inline void reallySetProperty(id self, SEL _cmd, id newValue,
ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
id oldValue;
id *slot = (id*) ((char*)self + offset);
if (copy) {
newValue = [newValue copyWithZone:NULL];
} else if (mutableCopy) {
newValue = [newValue mutableCopyWithZone:NULL];
} else {
if (*slot == newValue) return;
newValue = objc_retain(newValue);
}
if (!atomic) {
oldValue = *slot;
*slot = newValue;
} else {
spin_lock_t *slotlock = &PropertyLocks[GOODHASH(slot)];
_spin_lock(slotlock);
oldValue = *slot;
*slot = newValue;
_spin_unlock(slotlock);
}
objc_release(oldValue);
}
除開方法名字頗有趣之外,其實方法實際作的事情很是直接,它使用了在 PropertyLocks 中的 128 個自旋鎖中的 1 個來給操做上鎖。這是一種務實和快速的方式,最糟糕的狀況下,若是遇到了哈希碰撞,那麼 setter 須要等待另外一個和它無關的 setter 完成以後再進行工做。
@synchonized(self) 更適合使用在你 須要確保在發生錯誤時代碼不會死鎖,而是拋出異常的時候。
對於那些確定應該線程安全的代碼(一個好例子是負責緩存的類)來講,一個不錯的設計是使用併發的 dispatch_queue 做爲讀/寫鎖,而且確保只鎖着那些真的須要被鎖住的部分,以此來最大化性能。一旦你使用多個隊列來給不一樣的部分上鎖的話,整件事情很快就會變得難以控制了。
@property (nonatomic, strong) NSMutableSet *delegates;
// init方法中
_delegateQueue = dispatch_queue_create("com.PSPDFKit.cacheDelegateQueue",
DISPATCH_QUEUE_CONCURRENT);
- (void)addDelegate:(id<PSPDFCacheDelegate>)delegate {
dispatch_barrier_async(_delegateQueue, ^{
[self.delegates addObject:delegate];
});
}
除非 addDelegate: 或者 removeDelegate: 每秒要被調用上千次,不然咱們可使用一個相對簡潔的實現方式:
// 頭文件
@property (atomic, copy) NSSet *delegates;
- (void)addDelegate:(id<PSPDFCacheDelegate>)delegate {
@synchronized(self) {
self.delegates = [self.delegates setByAddingObject:delegate];
}
}
dispatch_barrier_async和dispatch_barrier_sync
1.比較
* 共同點:他以前的任務會在他以前完成,他以後的任務會等他在執行完後執行
* 不一樣點:dispatch_barrier_async後面的任務不會等他執行完再 被添加進 隊列;dispatch_barrier_sync後面的任務會等他再執行完之後再添加 進隊列
* 任務是 先添加進隊列,可是並非一添加進去就開始執行