2010-05-05 73 views
2

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

在我看来,许多信号灯或锁存在,因为没有已知的替代方案(实施者,),但你知道互斥体似乎是一个要求任何模式的?

以某种方式,我要求候选人为现实世界中并发软件的标准HARD问题集合。

回答

5

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

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

不过,如果你正在使用可以容忍一些“松动”的数据是什么,有几种技术,而不在每次访问使用排它锁的线程之间共享数据。

例如,如果您有一个链接的数据列表,并且您不会因在列表遍历中多次查看同一个节点而使您对该链接列表感到不安,并且在没有看到插入的情况下不会不高兴在插入(或类似的工件)之后立即执行,您可以使用原子指针交换来执行列表插入和删除,而无需在插入或删除操作周围实施全面的互斥锁。另一个例子:如果你有一个数组或列表对象,它主要是由线程读取的,并且只是偶尔由主线程更新,你可以通过维护列表的两个副本来实现无锁更新:一个是“活的“,其他线程可以读取,另一个是”离线“,您可以在自己的线程隐私中写入。要执行更新,请将“实时”列表的内容复制到“脱机”列表中,执行更新到脱机列表,然后使用原子指针交换将脱机列表指针交换到实时列表指针。然后,您需要一些机制让读者从现在的离线列表中“流失”。在垃圾收集系统中,您可以将引用释放到脱机列表中 - 当最后一位消费者完成后,它将被GC'd。在非GC系统中,您可以使用引用计数来跟踪有多少读者仍在使用该列表。对于这个例子,只有一个线程被指定为列表更新器是理想的。如果需要多个更新程序,则需要对更新操作进行锁定,但只能对更新程序进行序列化 - 不会对列表中的读者造成锁定并影响性能。

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

例子:

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

大多数现实世界的并发软件在某种程度上都有某种形式的同步需求。通常情况下,更好的书面软件将会花费很大的努力来减少所需的锁定数量,但在某些时候它仍然是必需的。

例如,我经常做模拟发生某种形式的聚合操作。通常情况下,在模拟阶段本身(即:使用线程本地状态数据等)可以阻止锁定,但实际的聚合部分通常最终需要某种形式的锁定。

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

+0

这听起来很合理,请问为什么你不能有多个队列进入你的聚合器去取出这些锁的最后一个? – 2010-05-05 17:02:25

+0

@Richard:您可能会 - 但将数据抽入共享的无锁队列,然后再串行处理队列的开销高于同步所需的减少锁定。 – 2010-05-05 17:05:33

+0

@Richard:如果使用得当,锁定本身并不一定是坏的。有时候锁定是比替代方案更好的选择,但只有通过分析才能确定。 – 2010-05-05 17:06:13