SDWebimage相關知識點1-- 線程
阿新 • • 發佈:2018-03-30
共享資源 val 擁有 ood object 當前 字典 完全 int 一
線程(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(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 的調用。在我們的例子中,這個調用的參數是這樣的:
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;
_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];
}
}
// init方法中
// 頭文件
dispatch_barrier_async和dispatch_barrier_sync
1.比較 * 共同點:他之前的任務會在他之前完成,他之後的任務會等他在執行完後執行 * 不同點:dispatch_barrier_async後面的任務不會等他執行完再 被添加進 隊列;dispatch_barrier_sync後面的任務會等他再執行完以後再添加 進隊列 * 任務是 先添加進隊列,但是並不是一添加進去就開始執行SDWebimage相關知識點1-- 線程