串行调度队列如何保证资源保护?
//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在开始工作时是否有错误的银行账户余额,从而搞砸数据完整性?
谢谢!!
man dispatch_queue_create
说:“发送到串行队列的块执行的所有内存写入都保证对分派到同一队列的后续块可见。”因此,串行队列是序列化访问可变状态以避免竞争条件的好方法。 。
使用dispatch_async时,这些任务是否也按顺序完成?
是。 队列指示执行策略,而不是如何对块进行排队。
换句话说,如果队列是串行的,则使用异步或同步进行排队不会改变该行为。 唯一的区别是:在继续执行程序的其余部分之前,我是否等待此块完成? dispatch_async
= no, dispatch_sync
= yes。
如果我异步提交到串行队列的任务#23需要很长时间才能完成,该怎么办?
没有什么变化。 在出列并执行下一个块(#24)之前,串行队列总是等待先前出列的块(#23)完成。 如果需要停止队列,则应在块代码中实现超时。
是的,专用串行队列是同步访问多个线程之间共享的某些资源的绝佳方式。 并且,是的,使用串行队列,每个任务将等待前一个任务完成。
两点意见:
-
虽然这听起来像是一个非常低效的过程,但这隐含在任何同步技术(无论是基于队列还是基于锁的方法)的核心,其目标是最小化共享资源的并发更新。
但在许多情况下,串行队列技术可以比其他常用技术(例如简单的互斥锁,
NSLock
或@synchronized
指令)产生明显更好的性能。 有关备用同步技术的讨论,请参阅“ 线程编程指南”的“ 同步”部分。 有关使用队列代替锁的讨论,请参阅“ 并发编程指南 ”中的“从线程迁移中删除 基于锁的代码 ”部分。 -
串行队列模式的一种变体是使用“读写器”模式,您可以在其中创建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_CONCURRENT
选项的dispatch_queue_create
创建自己的并发队列) 。 在并发队列中,许多已分派的任务可以并发运行。 使用并发队列需要一些小心(特别是共享资源的同步 ),但在正确实施时可以产生显着的性能优势。
作为这两种方法之间的折衷,您可以使用操作队列,它可以是并发的,但是您也可以通过设置maxConcurrentOperationCount
来限制队列中的任意操作在任何给定时间同时运行。 您将使用此function的典型方案是在执行后台网络任务时,您不需要超过五个并发网络请求。
有关更多信息,请参阅“ 并发编程指南” 。