要求非常简单:我想确保多个线程不会同时修改一个对象。棘手的部分是对象来自工厂,该工厂的实现直到运行时才知道。它可能会返回一个单例,可能每次都创建一个新实例,或者可能具有共享实例池。
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
,这虽然不理想,但却十分完美安全。
答案 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;
}
}
}