一、概念與類型
對于GCD來說,所有的執行都放到隊列中(queue),隊列的特點是FIFO(先提交的先執行)。
GCD的隊列分為幾種,主隊列(main),全局隊列(global),用戶創建隊列(create)
對于全局隊列,默認有四個,分為四個優先級
#define DISPATCH_QUEUE_PRIORITY_HIGH 2
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0
#define DISPATCH_QUEUE_PRIORITY_LOW (-2)
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
DISPATCH_QUEUE_PRIORITY_HIGH :優先級最高,在default,和low之前執行
DISPATCH_QUEUE_PRIORITY_DEFAULT 默認優先級,在low之前,在high之后
DISPATCH_QUEUE_PRIORITY_LOW 在high和default后執行
DISPATCH_QUEUE_PRIORITY_BACKGROUND:提交到這個隊列的任務會在high優先級的任務和已經提交到background隊列的執行完后執行。官方文檔:(the queue is scheduled for execution after all high priority queues have been scheduled and the system runs items on a thread whose priority is set for background status.)
二、dispatch_async與dispatch_sync
- dispatch_sync
提交到隊列中同步執行 - dispatch_async
提交到隊列中異步執行,立即返回 - dispatch_barrier_sync
- dispatch_barrier_async
在隊列中,柵欄塊必須單獨執行,不能與其它塊并行。這只對隊列有意義,因為串行隊列中的塊總是按順序逐個來執行的。并發隊列中如果發現接下來要處理的塊是個柵欄塊(barrier block),那么就一直等到當前所有并發塊都執行完畢,才會單獨執行這個柵欄塊。
這是Effective Objective-C 2.0 這本書 中對dispatch_barrier_async的說明,書中有一段示例代碼,大概是這樣的:
- (instancetype)init
{
self = [super init];
if (!self) {
return nil;
}
_concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
return self;
}
- (NSString *)name
{
__block NSString *name;
dispatch_sync(_concurrentQueue, ^{
name = _name;
});
return name;
}
- (void)setName:(NSString *)name
{
dispatch_barrier_async(_concurrentQueue, ^{
_name = name;
});
}
問題來了,以上代碼能實現對name屬性的讀寫同步嗎?
按道理,是可以的。但事實并不可以,因為在這里蘋果給我們挖了一個坑。。。
原因就是 在這里 我們使用的是全局并發隊列。。
都是 并發隊列 憑啥就不一樣呢??
就是說,柵欄塊應該在自己創建的并行隊列里執行,如果是在串行隊列或是全局并行隊列中執行,那么就起不到柵欄的作用,和dispatch_async 函數效果一樣了。
所以上面代碼只要把_concurrentQueue 改成自己創建的
_concurrentQueue = dispatch_queue_create("com.people.test", DISPATCH_QUEUE_CONCURRENT);
就可以實現 讀寫的同步了。
隔離隊列
再來理解一個隔離隊列的概念,也是跟dispatch_barrier_sync和dispatch_barrier_async有關。
例如操作系統中的經典的讀者-寫者問題,簡而言之,我們可以在同一時刻有多個讀者,但同一時刻只能有一個線程可以寫入。解決方法就是利用GCD的四種 APIs
- dispatch_sync
- dispatch_async
- dispatch_barrier_sync
- dispatch_barrier_async
我們的想法是讀操作可以支持同步和異步,而寫操作也能支持異步寫入,并且必須確保是寫入的是同一個引用。GCD 的 barrier 集合 APIs 提供了解決方案:他們將會一直等到隊列中的任務清空,才會繼續執行 block。使用 barrier APIs 可以用來限制我們對字典對象的寫入,并且確保我們不會在同一時刻進行多個寫操作,以及正在寫操作時同時進行讀操作。看一段代碼:
class IdentityMap<T: Identifiable> {
var dictionary = Dictionary<String, T>()
let accessQueue = dispatch_queue_create("com.khanlou.isolation.queue", DISPATCH_QUEUE_CONCURRENT)
func object(withID ID: String) -> T? {
var result: T? = nil
dispatch_sync(accessQueue, {
result = dictionary[ID] as T?
})
return result
}
func addObject(object: T) {
dispatch_barrier_async(accessQueue, {
dictionary[object.ID] = object
})
}
}
dispatch_sync 將會分發 block 到我們的隔離隊列上然后等待其執行完畢。通過這種方式,我們就實現了同步讀操作(如果我們搞成異步讀取,getter 方法就需要一個 completion block)。因為 accessQueue 是并發隊列,這些同步讀取操作可以并發執行,也就是允許同時讀。
dispatch_barrier_async 將分發 block 到隔離隊列上,async 異步部分意味著會立即返回,并不會等待 block 執行完畢。這對性能是有好處的,但是在一個寫操作后立即執行一個讀操作會導致讀到一個半成品的數據(因為可能寫操作還未完成就開始讀了)。
dispatch_barrier_async 中的 barrier 部分意味著:只要 barrier block 進入隊列,并不會立即執行,而是會等待該隊列其他 block 執行完畢后再執行。所以在這點上就保證了我們的 barrier block 每次都只有它自己在執行。而所有在他之后提交的 block 也會一直等待這個 barrier block 執行完再執行。
傳入 dispatch_barrier_async() 函數的 queue,必須是用 dispatch_queue_create 創建的并發 queue。如果是串行 queue 或者是 global concurrent queues,這個函數就會變成 dispatch_async() 了