真实世界并发软件中的读写示例

时间:2010-05-05 16:36:03

标签: multithreading concurrency locking semaphore

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

在我看来,存在许多信号量或锁定,因为没有已知的替代方法(对于实现者),但是你知道有哪些模式似乎需要互斥量吗?

在某种程度上,我要求考虑现实世界中并发软件的标准HARD问题集。

2 个答案:

答案 0 :(得分:5)

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

仅当您的用例要求共享数据必须始终100%准确时,才需要排他锁。这是大多数开发人员开始使用的默认设置,因为这是我们正常思考数据的方式。

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

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

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

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

现实场景示例:

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

答案 1 :(得分:2)

大多数真实世界的并发软件在某种程度上都需要某种形式的同步。通常,更好的书面软件会花费很大的精力来减少所需的锁定量,但在某些时候仍然需要它。

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

幸运的是,这会成为每个线程的锁定,而不是每个工作单元。在我的情况下,这很重要,因为我通常在数十万或数百万单位的工作上进行操作,但大多数时候,它出现在具有4-16个PE的系统上,这意味着我通常限制为相似数量的执行单位。通过使用这种类型的机制,您仍然可以锁定,但是您可以锁定数十个元素,而不是数百万个元素。