在 GCD 中,串行队列通过异步操作 Swift 同步

Posted

技术标签:

【中文标题】在 GCD 中,串行队列通过异步操作 Swift 同步【英文标题】:In GCD are serial queue synchronous by asynchronous operation Swift 【发布时间】:2018-03-13 06:31:07 【问题描述】:

我正在使用具有 QoS 背景的串行队列

let serialQueue = DispatchQueue(label: "queue1", qos: DispatchQoS.background)

分配一个同步作业和两个异步作业:

func serialTask() 
    serialQueue.sync 
        for i in 0..<10 
            print("????", i)
        
    
    serialQueue.async 
        for i in 20..<30 
            print("⚪️", i)
        
    

    serialQueue.async 
        for i in 101..<120 
            print("????", i)
        
    

所有 3 个作业都在一个接一个地执行同步,但最后两个作业是异步的。异步作业是否在串行队列中同步。

【问题讨论】:

【参考方案1】:

让我看看我能否澄清asyncsync 之间的区别。

我将在我的示例中使用一些更改:

    我将使用 Instruments 的“兴趣点”来显示任务何时运行,而不是使用 print 语句。 (请参阅 WWDC 2019 Getting Started With Instruments。)这样我们可以以图形方式查看行为。

    我将在分派某些东西时发布一个简单的“兴趣点”事件路标 (Ⓢ),并将分派的任务包装在一个“兴趣区域”(水平条)中,以图形方式说明某个过程的持续时间。

    我会将您的 for 循环更改为 Thread.sleep(forTimeInterval: 1),模拟一些耗时的过程。如果您只是有一个快速的for 循环,事情会发生得如此之快,以至于无法辨别线程的实际情况。

所以,考虑一下:

import os.signpost

private let pointsOfInterest = OSLog(subsystem: "GCD Demo", category: .pointsOfInterest)

func tasks(on queue: DispatchQueue) 
    pointsOfInterestRange(with: "tasks(on:)") 
        os_signpost(.event, log: pointsOfInterest, name: "1") // first Ⓢ
        queue.sync  self.oneSecondProcess(with: "1") 

        os_signpost(.event, log: pointsOfInterest, name: "2") // second Ⓢ
        queue.async  self.oneSecondProcess(with: "2") 

        os_signpost(.event, log: pointsOfInterest, name: "3") // third Ⓢ
        queue.async  self.oneSecondProcess(with: "3") 
    


func oneSecondProcess(with staticString: StaticString) 
    pointsOfInterestRange(with: staticString) 
        Thread.sleep(forTimeInterval: 1)
    


func pointsOfInterestRange(with staticString: StaticString, block: () -> Void) 
    let identifier = OSSignpostID(log: pointsOfInterest)
    os_signpost(.begin, log: pointsOfInterest, name: staticString, signpostID: identifier)
    block()
    os_signpost(.end, log: pointsOfInterest, name: staticString, signpostID: identifier)

这就像您的示例,但不是print 声明,我们有路标声明,在 Instruments 的“兴趣点”工具中生成以下图形时间线:

所以,你可以看到:

    底部的tasks(on:)函数发出sync调度,第一个Ⓢ路标。

    它等待sync 任务“1”在继续之前完成,此时它发出两个后续调度,第二个和第三个Ⓢ路标(它们连续发生得如此之快以至于它们在图中重叠)。

    tasks(on:) 不会等待两个async 任务“2”和“3”完成。一旦它完成了那些async 任务的调度,它就会立即返回(因此tasks(on:) 范围会立即停止)。

    因为后台队列是串行的,所以三个分派的任务(“1”、“2”和“3”)一个接一个地依次运行。

但是,如果您将其更改为使用并发队列:

let queue = DispatchQueue(label: "...", attributes: .concurrent)

然后您可以看到两个async 任务现在相对于彼此同时运行:

这一次,task(on:) 调度 sync 调用,等待它完成,然后,只有当 sync 调用完成后,seriesOfTasks 才能继续调度两个 async 调用(在此情况下,不等待那些分派的任务完成)。

如您所见,asyncsync 的行为是不同的。使用sync,调用线程将等待分派的任务完成,但使用async,它不会。


从上面可以得出两个主要结论:

    syncasync 的选择决定了当前线程的行为(即是否等待分派的任务)。

    而且,作为一般规则,我们通常会避免在做任何耗时的事情时从主线程调用sync(因为这最终会阻塞主线程)。

    串行队列还是并发队列的选择决定了您分派的工作的行为,即它是否可以相对于该队列上的其他任务同时运行,或者它们是否会一个接一个地连续运行。

【讨论】:

【参考方案2】:

同步和异步执行与底层队列无关。同步执行意味着调用线程必须等到块完成。因此,第二个块在第一个块完成后入队。异步意味着调用者不得等待块的完成。因此,第三个块在前面的serialQueue.async 语句之后直接入队,而第二个块仍在运行甚至等待执行。

在你的函数serialTask() 结束时,保证第一个块被执行。第二个和第三个块入队,但不确定它们是否已执行、正在运行甚至等待执行。由于您使用的是串行队列,因此可以确定第二个块在第三个块之前执行。

您可以通过添加来检查最后两个块的异步执行

serialQueue.async 
    print "sleep"
    sleep(10);
    print "awake"

async 的两次调用之前,您将观察到以下内容:

    第一个块将立即执行,sleep 将立即打印。 serialTask() 的执行时间大大少于 10 秒。 awake 仅在 10 秒后输出(惊喜,惊喜)。 第二个和第三个块也在 10 秒后执行,在 serialTask() 结束之后很长时间。

这两个块的延迟执行意味着异步。如果您将第一个(同步)块移动到 serialTask() 的末尾,则会发生以下情况:

    sleep 将立即打印出来。 serialTask() 的执行大约需要 10 秒。在执行完最后的同步块后结束。 awake 仅在 10 秒后输出(惊喜,惊喜)。 在打印出awake 之后,也会执行第二个和第三个块。

【讨论】:

好的,但是最后两个异步任务没有执行异步,行为与同步相同。 @salmansiddiqui:这两个块异步执行的。你从你不幸的例子中推断出它是普遍有效的。这会导致你得出错误的结论。请查看我的编辑。 这里你已经添加了睡眠,但行为是一样的,它会等待一段时间并执行。 @salmansiddiqui 这个答案非常准确,clemens 提供了证明它的工具。我说了很多同样的话,但效果要差得多,也没有证据。我赞成这个。 @Smartcat 如何将延迟执行视为异步【参考方案3】:

所以同步任务立即运行,然后当它完成时,您将两个异步任务添加到该队列,*然后您的方法返回。 *然后,这两个任务将按顺序处理,只要该队列空闲

因此,如果在调用此方法之前该队列中有任何待处理的异步任务,这些任务将在您的两个异步任务之前运行。

【讨论】:

以上是关于在 GCD 中,串行队列通过异步操作 Swift 同步的主要内容,如果未能解决你的问题,请参考以下文章

IOS多线程知识总结/队列概念/GCD/串行/并行/同步/异步

iOS多线程知识总结/队列概念/GCD/串行/并行/同步/异步

GCD 异步串行队列 - 可以限制队列大小?

GCD 串行队列调度异步和同步

GCD使用 串行并行队列 与 同步异步执行的各种组合 及要点分析

ios多线程同步异步、串行并行队列、死锁