【问题标题】:Fine grained synchronization of a shared item list共享项目列表的细粒度同步
【发布时间】:2018-11-08 08:34:00
【问题描述】:

我正在使用线程安全的第三方库从历史数据库中检索数据。

典型场景的操作模式如下:

Library instance;

Result[] Process(string[] itemNames) {
    var itemsIds = instance.ReserveItems(itemNames);
    Result[] results = instance.ProcessItems(itemIds);
    instance.ReleaseItems(itemIds);
    return results;
}

Library 是一个实例化成本很高的类,因此这里将其用作单例 (instance),它可以完美地对抗多个线程。

但是,我注意到有时结果被标记为失败(“未找到项目”),当多个线程尝试执行 Process 使用共享一些常见项目的 itemNames 数组时。因为该库的文档非常糟糕,所以这是出乎意料的。

通过集中记录,我推断一个线程可以在另一个线程即将处理它的同时释放一个项目。

在给图书馆的供应商发了几封邮件后,我了解到instance 在线程之间共享一个保留项目列表,并且有必要同步调用...

取消编译库的某些部分证实了这一点:有一个类级别的 m_items 列表被 ReserveItems 和 ReleaseItems 使用。

所以我设想以下浪费:

Result[] Process(string[] itemNames) {
    lock(instance) {
        var itemsIds = instance.ReserveItems(itemNames);
        Result[] results = instance.ProcessItems(itemIds);
        instance.ReleaseItems(itemIds);
       return results;
    }
}

但对我来说似乎有点太暴力了。

由于该库在多线程处理不同项目时可以完美运行,如何执行更细粒度的同步并避免性能损失?

编辑 - 2018-11-09

我注意到库的整个ProcessItems 方法体都包含在一个lock 语句中......

因此,任何围绕此进行精细同步的尝试都是徒劳的。我最终也将我的 Process 方法主体包含在一个 lock 语句中,性能损失 - 正如预期的那样 - 根本无法察觉。

【问题讨论】:

  • 这个大锁(实例)很粗糙。您正在一次处理一组项目。这个批处理比迭代项目并在循环中调用“Reserve(singleitem); ProcessItems(singleitem); ReleaseItems(singleitem)”更快吗?如果这具有相同的性能,那么我将迭代项目并仅锁定项目,例如通过拥有当前处理项目的线程安全列表。如果这听起来不错,如果你愿意,我会用一些代码 sn-ps 发布答案。
  • 感谢您的想法,我会尝试一下!
  • 您也可以考虑使用ReaderWriterLockSlim。你可以在 ReserveItems 和 ProcessItems 上获得一个读锁,释放它并获得一个写锁来调用 ReleaseItems。

标签: c# multithreading thread-synchronization


【解决方案1】:

您可以为每个项目 ID 实施锁定。这可以采用 Dictionary<string, object> 的形式,其中值是锁定对象 (new object())。

如果您想同时在多个线程上处理相同的项目 ID 而不会在发生冲突时阻塞所有内容,您可以在字典值中跟踪更多状态来执行此操作。例如,您可以使用Dictionary<string, Lazy<Result>>。第一个需要项目 ID 的线程将初始化并直接使用惰性线程。然后其他线程可以检测到该项目 ID 上的操作正在进行中,并且也可以使用惰性线程。

【讨论】:

  • 即使这在我的情况下不再有用,我相信这是正确的道路。非常感谢您的指点!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-02-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-23
  • 2011-05-20
相关资源
最近更新 更多