【问题标题】:Real World Examples of read-write in concurrent software并发软件中读写的真实世界示例
【发布时间】:2011-02-16 00:49:37
【问题描述】:

我正在寻找在并发系统中需要对相同值进行读写访问的真实示例。

在我看来,存在许多信号量或锁是因为没有已知的替代方案(对于实施者而言),但是您知道任何似乎需要互斥锁的模式吗?

在某种程度上,我正在为现实世界中并发软件的标准 HARD 问题集寻找候选人。

【问题讨论】:

    标签: multithreading concurrency locking semaphore


    【解决方案1】:

    使用什么样的锁取决于多个线程如何访问数据。如果您可以微调用例,您有时可以完全消除对排他锁的需求。

    仅当您的用例要求共享数据必须始终 100% 准确时,才需要独占锁。这是大多数开发人员开始时的默认设置,因为我们通常是这样看待数据的。

    但是,如果您使用数据的目的可以容忍一些“松散”,那么有几种技术可以在线程之间共享数据,而无需在每次访问时使用排他锁。

    例如,如果您有一个数据链表,并且如果您使用该链表不会因为在列表遍历中多次看到同一个节点而感到不安,并且如果它没有立即看到插入也不会感到不安在插入(或类似工件)之后,您可以使用原子指针交换执行列表插入和删除,而无需围绕插入或删除操作的完全停止互斥锁。

    另一个例子:如果你有一个数组或列表对象,它主要由线程读取,只是偶尔由主线程更新,你可以通过维护列表的两个副本来实现无锁更新:一个是“实时的” "其他线程可以读取,另一个是“离线”,您可以在自己的线程的隐私中写入。要执行更新,您将“活动”列表的内容复制到“离线”列表中,对离线列表执行更新,然后使用原子指针交换将离线列表指针交换为活动列表指针。然后,您将需要一些机制来让读者从现在离线列表中“流失”。在垃圾收集系统中,您可以只释放对离线列表的引用 - 当最后一个消费者完成它时,它将被 GC'd。在非 GC 系统中,您可以使用引用计数来跟踪有多少读者仍在使用该列表。对于此示例,仅将一个线程指定为列表更新程序将是理想的。如果需要多个更新程序,您将需要在更新操作周围加锁,但只对更新程序进行序列化 - 没有锁,也不会影响列表读取器的性能。

    我知道的所有无锁资源共享技术都需要使用原子交换(又名 InterlockedExchange)。这通常会转换为 CPU 中的特定指令和/或硬件总线锁定(x86 汇编器中读取或写入操作码上的锁定前缀),时间很短。在多进程系统上,原子交换可能会强制其他处理器上的缓存失效(双进程 Pentium II 就是这种情况),但我认为这在当前的多核芯片上不是什么大问题。即使有这些性能警告,无锁运行也比使用完全停止的内核事件对象快得多。只需调用内核 API 函数就需要数百个时钟周期(切换到内核模式)。

    真实场景示例:

    1. 生产者/消费者工作流程。 Web 服务接收对数据的 http 请求,将请求放入内部队列,工作线程从队列中拉出工作项并执行工作。队列是读/写的,并且必须是线程安全的。
    2. 在所有权更改的线程之间共享数据。线程 1 分配一个对象,把它扔给线程 2 处理,再也不想看到它了。线程 2 负责处理对象。内存管理系统(malloc/free)必须是线程安全的。
    3. 文件系统。这几乎总是一个操作系统服务,并且已经完全线程安全,但值得列入列表。
    4. 引用计数。当引用数降至零时释放资源。递增/递减/测试操作必须是线程安全的。这些通常可以使用原子原语而不是完全停止的内核互斥锁来实现。

    【讨论】:

      【解决方案2】:

      大多数现实世界的并发软件在某种程度上都有某种形式的同步要求。通常,编写得更好的软件会不遗余力地减少所需的锁定量,但在某些时候仍然需要它。

      例如,我经常在发生某种形式的聚合操作时进行模拟。通常,在模拟阶段本身有一些方法可以防止锁定(即:使用线程本地状态数据等),但实际的聚合部分通常需要在最后使用某种形式的锁定。

      幸运的是,这变成了每个线程的锁,而不是每个工作单元的锁。就我而言,这很重要,因为我通常在数十万或数百万个工作单元上进行操作,但大多数时候,它发生在具有 4-16 个 PE 的系统上,这意味着我通常限制为类似数量的执行单元。通过使用这种类型的机制,您仍然处于锁定状态,但您锁定在数十个元素之间,而不是潜在的数百万个元素之间。

      【讨论】:

      • 这听起来很合理,我能问一下为什么不能让多个队列进入聚合器来取出最后一个锁吗?
      • @Richard:您可能可以 - 但是将数据泵入共享的无锁队列,然后串行处理队列的开销高于同步所需的减少锁定。
      • @Richard:如果使用得当,锁定本身并不一定是坏事。有时锁定是比其他选择更好的选择,但只有通过分析才能确定。
      • @Reed。当前的无锁线程安全队列实现(为了论证,用 Java 编写的)提供比获取和释放锁更少的开销。上下文切换本身是相当大的开销
      • @John:如果你在做一个聚合,你可以锁定并直接做,或者将元素排队,等待每个UE完成,然后处理队列做最后的工作.在并发过程之后发生了一个隐含的额外步骤。这并不是真正的额外等待——但通常,当 UE 完成时,获取锁不会阻塞其他线程,因为它们不会同时完成。这在很大程度上取决于所讨论的算法,但在许多情况下,锁定可能比事后进行处理更快,前提是每个 UE 锁定一次,而不是每个任务一次。
      猜你喜欢
      • 1970-01-01
      • 2012-05-23
      • 2010-11-23
      • 2016-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-01
      相关资源
      最近更新 更多