串行调度队列异步块
是否有过任何理由块添加到串行调度队列异步而不是同步?串行调度队列异步块
据我所知,串行调度队列只有在前面的任务完成执行后才开始执行队列中的下一个任务。如果是这种情况,我不能通过异步提交一些块 - 提交行为不会阻止该线程(因为它直接返回)而无法执行任务直到最后一项任务完成,所以在我看来,你并没有真正获得任何东西。
此问题由以下代码提示 - 取自设计模式的书籍章节。为了防止底层data
阵列被两个单独的线程同时修改,所有修改任务都被添加到串行调度队列中。但请注意,returnToPool
异步添加任务到此队列,而getFromPool
同步添加其任务。
class Pool<T> {
private var data = [T]();
// Create a serial dispath queue
private let arrayQ = dispatch_queue_create("arrayQ", DISPATCH_QUEUE_SERIAL);
private let semaphore:dispatch_semaphore_t;
init(items:[T]) {
data.reserveCapacity(data.count);
for item in items {
data.append(item);
}
semaphore = dispatch_semaphore_create(items.count);
}
func getFromPool() -> T? {
var result:T?;
if (dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER) == 0) {
dispatch_sync(arrayQ, {() in
result = self.data.removeAtIndex(0);
})
}
return result;
}
func returnToPool(item:T) {
dispatch_async(arrayQ, {() in
self.data.append(item);
dispatch_semaphore_signal(self.semaphore);
});
}
}
是的,你有理由为任务添加到串行队列异步。这实际上非常普遍。
最常见的例子是当您在后台执行某些操作并想要更新UI时。您经常会将该UI更新异步分派回主队列(这是一个串行队列)。这样后台线程不必等待主线程执行其UI更新,而是可以在后台进行处理。
另一个常见的例子就像您已经证明的那样,当使用GCD队列来同步与某个对象的交互时。如果您正在处理不可变对象,则可以将这些更新异步分派到此同步队列(即为何让当前线程等待,而是让它继续进行)。你会做同步读取(因为你显然会等待,直到你得到同步值),但写入可以异步完成。 (你实际上看到后一个例子经常用“读写器”模式和一个自定义并发队列实现,其中读取在dispatch_sync
的并发队列上同步执行,但是写入是通过与dispatch_barrier_async
的障碍异步执行的。想法同样适用于串行队列。)
同步v异步调度的选择与目标队列是串行还是并发无关。这只是一个问题,你是否必须阻止当前队列,直到另一个队员完成其任务。
关于你的代码示例代码,这是正确的。 getFromPool
应该同步调度(因为您必须等待同步队列实际返回值),但returnToPool
可以安全地异步调度。很明显,如果可能从主线程调用代码,我担心会看到代码等待信号量(所以请确保您不要从主线程调用getFromPool
!),但有一点需要注意的是,该代码应该达到期望的目的是提供这个池对象的合理有效的同步,但是如果一个getFromPool
会阻塞,如果池是空的,直到某些东西被添加到池中。
因为没有必要使returnToPool()
块的调用者。它也许可以继续做其他有用的工作。
调用returnToPool()
的线程大概不是只是使用这个池。它可能有其他的东西,它可以做。这些东西可以与异步提交任务中的工作同时完成。
典型的现代计算机有多个CPU内核,因此这样的设计可以提高CPU内核高效利用的可能性,并且更快地完成有用的工作。问题不在于提交到串行队列的任务是否同时工作 - 它们不能由于串行队列的性质 - 它是否可以同时完成其他工作。
与“removeFromPool”形成鲜明对比的是,为什么'getFromPool'最适合作为同步调用的解释是非常有用的。 – 2015-03-03 09:41:22