2
//my_serial_queue is a serial_dispatch_queue

dispatch_async(my_serial_queue, ^{

    //access a shared resource such as a bank account balance
    [self changeBankAccountBalance];

});

如果我提交 100 个任务,每个任务都访问并改变银行账户余额,我知道串行队列将按顺序执行每个任务,但是在使用 dispatch_async 时这些任务是否也按顺序完成?

如果我异步提交到串行队列的任务 #23 需要很长时间才能完成怎么办?任务 #24 会仅在任务 #23 完成后开始,还是任务 #24 在任务 #23 完成之前开始?如果是这样,任务#24 在开始工作时会不会出现错误的银行账户余额,从而破坏数据完整性?

谢谢!!

4

2 回答 2

8

是的,专用串行队列是同步访问多个线程之间共享的某些资源的绝妙方法。而且,是的,使用串行队列,每个任务都将等待前一个任务完成。

两个观察:

  1. 虽然这听起来像是一个非常低效的过程,但它隐含地处于任何同步技术(无论是基于队列还是基于锁的方法)的核心,其目标是最小化共享资源的并发更新。

    但在许多情况下,串行队列技术可以产生比其他常见技术(例如简单互斥锁NSLock、 或@synchronized指令)更好的性能。有关替代同步技术的讨论,请参阅线程编程指南的同步部分。有关使用队列代替锁的讨论,请参阅并发编程指南的从线程迁移部分中的消除基于锁的代码。

  2. 串行队列模式的一种变体是使用“读写器”模式,您可以在其中创建 GCD 并发队列:

    queue = dispatch_queue_create("identifier", DISPATCH_QUEUE_CONCURRENT);
    

    然后,您使用 执行读取dispatch_sync,但使用 执行写入dispatch_barrier_async。净有效是允许并发读取操作,但确保永远不会同时执行写入。

    如果您的资源允许并发读取,那么读写器模式可以提供比串行队列更多的性能增益。

因此,简而言之,虽然让任务 #24 等待任务 #23 似乎效率低下,但这是任何同步技术所固有的,在这种技术中,您努力最小化共享资源的并发更新。而且 GCD 串行队列是一种非常有效的机制,通常比许多简单的锁定机制要好。在某些情况下,读写器模式可以提供进一步的性能改进。


下面我的原始答案是对原始问题的回应,该问题令人困惑,标题为“串行调度队列如何保证并发性?” 回想起来,这只是偶然使用了错误的术语。


这是一个有趣的词语选择,“串行调度队列如何保证并发?”

队列分为三种类型,串行队列、并发队列和主队列。顾名思义,串行队列将在前一个完成之前不会开始下一个调度的块。(使用您的示例,这意味着如果任务 23 需要很长时间,则在完成之前它不会启动任务 24。)有时这很关键(例如,如果任务 24 取决于任务 23 的结果,或者如果两个任务 23和 24 正在尝试访问相同的共享资源)。

如果您希望这些不同的分派任务彼此同时运行,您可以使用并发队列(通过 获得的全局并发队列之一,或者您可以使用选项dispatch_get_global_queue创建自己的并发队列)。在并发队列中,您的许多分派任务可能同时运行。使用并发队列需要一些注意(特别是共享资源的同步),但如果实施得当,可以产生显着的性能优势。dispatch_queue_createDISPATCH_QUEUE_CONCURRENT

作为这两种方法的折衷方案,您可以使用操作队列,它可以是并发的,但您也可以通过设置来限制队列上的操作在任何给定时间并发运行的数量maxConcurrentOperationCount。您将使用它的典型场景是在执行后台网络任务时,您不希望超过五个并发网络请求。

有关详细信息,请参阅并发编程指南

于 2013-09-25T17:13:33.083 回答
5

man dispatch_queue_create说:“由分派到串行队列的块执行的所有内存写入都保证对分派到同一队列的后续块可见。” 因此,串行队列是串行访问可变状态以避免竞争条件的好方法。

使用 dispatch_async 时,这些任务是否也按顺序完成?

是的。队列决定了执行策略,而不是你如何对块进行排队。

换句话说,如果队列是串行的,异步或同步队列不会改变这一点。唯一的区别是:在继续执行程序的其余部分之前,我是否要等待这个块完成?dispatch_async=没有,dispatch_sync=是的。

如果我异步提交到串行队列的任务 #23 需要很长时间才能完成怎么办?

没有什么变化。串行队列总是等待先前出列的块 (#23) 完成,然后再出列并执行下一个块 (#24)。如果暂停队列是一个问题,您应该在块代码中实现超时。

于 2013-09-25T17:34:45.030 回答