共享项目列表的细粒度同步

Fine grained synchronization of a shared item list

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

典型场景的运行模式如下:

Library instance;

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

Library 是一个 class,它的实例化成本很高,所以它在这里用作单例 (instance),并且它在多线程上完美工作。

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

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

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

反编译库的某些部分证实了这一点:有一个 class 级 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;
    }
}

不过我觉得有点太暴力了。

由于这个库在多个线程处理不同的项目时工作得很好,如何才能执行更细粒度的同步并避免性能损失?

EDIT - 2018-11-09

I noticed that the whole ProcessItems method body of the Library is enclosed into a lock statement...

So any attempt at fine synchronization around this is futile. I ended up enclosing my Process method body in a lock statement as well, the performance penalty is -as expected now- not perceptible at all.

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

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