带有异步块的串行调度队列

Serial Dispatch Queue with Asynchronous Blocks

是否有任何理由将块添加到串行调度队列 异步而不是同步?

据我了解,串行调度队列仅在前一个任务完成执行后才开始执行队列中的下一个任务。如果是这种情况,我看不出通过提交一些块 异步 你会得到什么 - 提交的行为可能不会阻塞线程(因为它 returns 直接-away),但是直到最后一个任务完成后任务才会执行,所以在我看来你并没有真正获得任何东西。

此问题是由以下代码提示的 - 摘自有关设计模式的书籍章节。为了防止底层 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);
        });
    }
}

因为没有必要让 returnToPool() 的调用者阻塞。它也许可以继续做其他有用的工作。

调用 returnToPool() 的线程可能 不只是 使用此池。它可能还有其他可以做的事情。这些东西可以与异步提交的任务中的工作同时完成。

典型的现代计算机有多个 CPU 个核心,因此像这样的设计提高了 CPU 个核心被有效利用的机会,有用的工作更快完成。问题不在于提交到串行队列的任务是否同时运行——由于串行队列的性质,它们不能同时运行——而是其他 工作是否可以同时完成。

是的,将任务异步添加到串行队列是有原因的。这实际上非常普遍。

最常见的示例是当您在后台执行某些操作并想要更新 UI 时。您通常会将 UI 更新异步发送回主队列(这是一个串行队列)。这样后台线程就不必等待主线程执行其 UI 更新,而是可以在后台继续处理。

另一个常见示例如您所演示的那样,使用 GCD 队列同步与某些对象的交互。如果您正在处理不可变对象,则可以将这些更新异步分派到此同步队列(即为什么要让当前线程等待,而是让它继续运行)。您将同步读取(因为您显然要等到返回同步值),但写入可以异步完成。

(您实际上看到后一个示例经常使用 "reader-writer" 模式和自定义并发队列实现,其中读取在并发队列上使用 dispatch_sync 同步执行,但写入使用屏障异步执行dispatch_barrier_async。但这个想法同样适用于串行队列。)

选择同步还是异步调度与目标队列是串行还是并发无关。这只是一个问题,你是否必须阻塞当前队列,直到另一个队列完成它的任务。

关于您的代码示例代码,这是正确的。 getFromPool 应该同步调度(因为你必须等待同步队列实际 return 值),但是 returnToPool 可以安全地异步调度。显然,如果可能从主线程调用信号量,我很担心看到等待信号量的代码(因此请确保您不从主线程调用 getFromPool!),但有一个警告,这段代码应该达到预期的目的,提供此池对象的相当有效的同步,但如果池为空,则 getFromPool 将阻塞,直到有内容添加到池中。