【发布时间】:2011-02-16 00:49:37
【问题描述】:
我正在寻找在并发系统中需要对相同值进行读写访问的真实示例。
在我看来,存在许多信号量或锁是因为没有已知的替代方案(对于实施者而言),但是您知道任何似乎需要互斥锁的模式吗?
在某种程度上,我正在为现实世界中并发软件的标准 HARD 问题集寻找候选人。
【问题讨论】:
标签: multithreading concurrency locking semaphore
我正在寻找在并发系统中需要对相同值进行读写访问的真实示例。
在我看来,存在许多信号量或锁是因为没有已知的替代方案(对于实施者而言),但是您知道任何似乎需要互斥锁的模式吗?
在某种程度上,我正在为现实世界中并发软件的标准 HARD 问题集寻找候选人。
【问题讨论】:
标签: multithreading concurrency locking semaphore
使用什么样的锁取决于多个线程如何访问数据。如果您可以微调用例,您有时可以完全消除对排他锁的需求。
仅当您的用例要求共享数据必须始终 100% 准确时,才需要独占锁。这是大多数开发人员开始时的默认设置,因为我们通常是这样看待数据的。
但是,如果您使用数据的目的可以容忍一些“松散”,那么有几种技术可以在线程之间共享数据,而无需在每次访问时使用排他锁。
例如,如果您有一个数据链表,并且如果您使用该链表不会因为在列表遍历中多次看到同一个节点而感到不安,并且如果它没有立即看到插入也不会感到不安在插入(或类似工件)之后,您可以使用原子指针交换执行列表插入和删除,而无需围绕插入或删除操作的完全停止互斥锁。
另一个例子:如果你有一个数组或列表对象,它主要由线程读取,只是偶尔由主线程更新,你可以通过维护列表的两个副本来实现无锁更新:一个是“实时的” "其他线程可以读取,另一个是“离线”,您可以在自己的线程的隐私中写入。要执行更新,您将“活动”列表的内容复制到“离线”列表中,对离线列表执行更新,然后使用原子指针交换将离线列表指针交换为活动列表指针。然后,您将需要一些机制来让读者从现在离线列表中“流失”。在垃圾收集系统中,您可以只释放对离线列表的引用 - 当最后一个消费者完成它时,它将被 GC'd。在非 GC 系统中,您可以使用引用计数来跟踪有多少读者仍在使用该列表。对于此示例,仅将一个线程指定为列表更新程序将是理想的。如果需要多个更新程序,您将需要在更新操作周围加锁,但只对更新程序进行序列化 - 没有锁,也不会影响列表读取器的性能。
我知道的所有无锁资源共享技术都需要使用原子交换(又名 InterlockedExchange)。这通常会转换为 CPU 中的特定指令和/或硬件总线锁定(x86 汇编器中读取或写入操作码上的锁定前缀),时间很短。在多进程系统上,原子交换可能会强制其他处理器上的缓存失效(双进程 Pentium II 就是这种情况),但我认为这在当前的多核芯片上不是什么大问题。即使有这些性能警告,无锁运行也比使用完全停止的内核事件对象快得多。只需调用内核 API 函数就需要数百个时钟周期(切换到内核模式)。
真实场景示例:
【讨论】:
大多数现实世界的并发软件在某种程度上都有某种形式的同步要求。通常,编写得更好的软件会不遗余力地减少所需的锁定量,但在某些时候仍然需要它。
例如,我经常在发生某种形式的聚合操作时进行模拟。通常,在模拟阶段本身有一些方法可以防止锁定(即:使用线程本地状态数据等),但实际的聚合部分通常需要在最后使用某种形式的锁定。
幸运的是,这变成了每个线程的锁,而不是每个工作单元的锁。就我而言,这很重要,因为我通常在数十万或数百万个工作单元上进行操作,但大多数时候,它发生在具有 4-16 个 PE 的系统上,这意味着我通常限制为类似数量的执行单元。通过使用这种类型的机制,您仍然处于锁定状态,但您锁定在数十个元素之间,而不是潜在的数百万个元素之间。
【讨论】: