仅在运行时知道锁定对象的理想范围时,如何锁定?

时间:2018-07-27 18:37:32

标签: c# locking

要求非常简单:我想确保多个线程不会同时修改一个对象。棘手的部分是对象来自工厂,该工厂的实现直到运行时才知道。它可能会返回一个单例,可能每次都创建一个新实例,或者可能具有共享实例池。

var thing = factory.Get(...);
lock (???) {
    // modify thing
}

我知道,锁定其他代码可能会锁定的公共可见对象并不安全,从而可能导致死锁。换句话说,我不应该lock (thing)。但是理想情况下,我想使用thing相同的,仅在运行时已知的范围来锁定某物

我想到的一种潜在解决方案是使用ConcurrentDictionary的哈希码作为键的对象thing

private static ConcurrentDictionary<int, object> _thingLocks =
    new ConcurrentDictionary<int, object>();

...

var thing = factory.Get(...);
var thingLock = _thingLocks.GetOrAdd(thing.GetHashCode(), new object())

lock (thingLock) {
    // modify thing
}

直觉上我认为这应该可行,因为a)锁本身是私有的,因此没有外部锁也可以对其进行锁定,并且b)锁实例很有可能 *是一对一的1个,带有thing个实例。但是由于这种代码很难测试,因此我想问:这是正确且适当的解决方案吗?是否有一种更好/更好的方法来锁定仅在运行时才知道的作用域?

*-如评论中所述,这不能保证,但是在不太可能发生冲突的情况下,这仅意味着不能同时修改2个thing,这虽然不理想,但却十分完美安全。

1 个答案:

答案 0 :(得分:2)

因此,您可能需要为每个类实例添加一些其他数据(锁定对象),但无法控制实现或它们的生存期。如果您可以更改实现,则可以轻松地将锁对象添加到类中以执行所需的任何操作。这建议根据对象的身份进行查找,例如Dictionary(或相关类,例如ConcurrentDictionary)。这样做的问题是,您必须管理字典的清理,因为将对象作为键会在字典内创建对它们的引用。当不再引用键时,我们需要一些清理值的方法。输入ConditionalWeakTable。它的工作方式类似于在密钥标识上进行字典查找,但对其的引用很少。请注意,它在其实现中不使用虚拟GetHashCode或Equals。它总是进行参考比较。而且ConditionalWeakTable已经被设计为线程安全的,因此可以很容易地代替ConcurrentDictionary使用:

class Thing
{
    public int Value { get; set; }
}

abstract class ThingFactory
{
    public abstract Thing GetOrCreate(string key);
}

class ThingManager
{
    private readonly ConditionalWeakTable<Thing, object> _locks;
    private readonly ThingFactory _factory;

    public ThingManager(ThingFactory factory)
    {
        _factory = factory;
        _locks = new ConditionalWeakTable<Thing, object>();
    }

    public int Increment(string key,  int incr)
    {
        var thing = _factory.GetOrCreate(key);
        var thingLock = _locks.GetOrCreateValue(thing);

        lock (thingLock)
        {
            int newValue = thing.Value + incr;
            thing.Value = newValue;
            return newValue;
        }
    }
}