C# 如何锁定字典,然后安全地将锁转移到字典中的某个值?
我正在尝试找到一种安全的方法来同步对嵌套字典的访问,其中外部字典上的操作会锁定对整个集合的任何操作。然而,一旦检索到内部字典,我想释放外部锁,只防止线程操纵内部字典 一旦外部字典中存在内部字典,就可以使用C# 如何锁定字典,然后安全地将锁转移到字典中的某个值?,c#,multithreading,synchronization,C#,Multithreading,Synchronization,我正在尝试找到一种安全的方法来同步对嵌套字典的访问,其中外部字典上的操作会锁定对整个集合的任何操作。然而,一旦检索到内部字典,我想释放外部锁,只防止线程操纵内部字典 一旦外部字典中存在内部字典,就可以使用lock关键字进行此操作,但是,我正在努力找到一种安全的方法来添加和填充内部字典,而不引入竞争条件。假设“填充”内部字典将是一项昂贵的操作。这就是为什么在外部字典的lock下执行此操作不是一个选项。当对新的内部字典执行“填充”时,其他线程必须具有对外部字典的访问权限,才能对其他内部字典执行操作
lock
关键字进行此操作,但是,我正在努力找到一种安全的方法来添加和填充内部字典,而不引入竞争条件。假设“填充”内部字典将是一项昂贵的操作。这就是为什么在外部字典的lock
下执行此操作不是一个选项。当对新的内部字典执行“填充”时,其他线程必须具有对外部字典的访问权限,才能对其他内部字典执行操作
我在下面列举了一些例子,这些例子可以更好地说明我的问题
监视器对象的经验可能会导致意外的后果李>
示例1:我认为解决问题的方法不安全。然而,这就是我对同步的理解。我更愿意使用这种方法,但是我不知道如何使用lock
关键字来实现我需要的行为
public void SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
{
Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
lock (outerDictionary)
{
// No need to add a new innerDictionary, it already exists
if (outerDictionary.ContainsKey(newKey))
{
return;
}
innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
outerDictionary.Add(newKey, innerDictionary);
}
// I want to allow other threads to have access to outerDictionary
// However, I don't want other threads working against THIS innerDictionary
lock (innerDictionary)
{
// Here lies my concern with this approach. Another thread could have
// taken the lock for innerDictionary. Doing this all under the lock
// for outerDictionary would be safe but would prevent access to other
// inner dictionaries while expensive operations are performed only
// pertaining to THIS innerDictionary
this.PopulateInnerDictionary(innerDictionary);
}
}
示例3:我认为这是示例1中演示的潜在同步问题的解决方案。但是,我不熟悉使用监视器
模式,非常感谢您的反馈
public void SyncronizationExample3(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
{
Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
bool aquiredLockForOuterDictionary = false;
bool aquiredLockForInnerDictionary = false;
try
{
Monitor.Enter(outerDictionary, ref aquiredLockForOuterDictionary);
if (outerDictionary.Contains(newKey)
{
// No need to add a new innerDictionary, it already exists
return;
}
innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
outerDictionary.Add(newKey, innerDictionary);
// This is where I "handoff" the lock to innerDictionary to alleviate my concern
// in Example 1 where another thread could steal the innerDictionary lock
Monitor.Enter(innerDictionary, ref aquiredLockForInnerDictionary);
}
finally
{
// I read that this bool pattern was preferred for .net 4+,
// however I am unsure if this is the best practice
if (aquiredLockForOuterDictionary)
{
Monitor.Exit(dictionary);
}
}
try
{
if (!aquiredLockForInnerDictionary)
{
// An exception must have occurred prior to or during the acquisition
// of this lock. Not sure how I'd handle this yet but
// I'm pretty shit out of luck.
return;
}
// Here I would perform an expensive operation against the innerDictionary
// I do not want to lock consumers form accessing other innerDictionaries
// while this computation is done.
this.PopulateInnerDictionary(innerDictionary);
}
finally
{
// I need to check this here incase an exception in the first
// try finally prevented this from being acquired
if (aquiredLockForInnerDictionary)
{
Monitor.Exit(innerDictionary);
}
}
}
public void SyncronizationExample3(Dictionary outerDictionary,TKey newKey)
{
Dictionary innerDictionary=null;
bool-aquiredLockForOuterDictionary=false;
bool-aquiredLockForInnerDictionary=false;
尝试
{
Monitor.Enter(outerDictionary,ref-aquiredLockForOuterDictionary);
if(outerDictionary.Contains)(newKey)
{
//无需添加新的innerDictionary,它已存在
返回;
}
innerDictionary=新字典();
Add(newKey,innerDictionary);
//这就是我将锁“移交”到innerDictionary的地方,以减轻我的担忧
//在示例1中,另一个线程可以窃取innerDictionary锁
Monitor.Enter(innerDictionary,ref aquiredLockForInnerDictionary);
}
最后
{
//我读到这个bool模式是.net 4+的首选模式,
//但是,我不确定这是否是最佳做法
if(针对外部词典的AquiredLock)
{
监控退出(字典);
}
}
尝试
{
如果(!aquiredLockForInnerDictionary)
{
//收购之前或期间必须发生异常
//我还不知道该怎么处理这个锁但是
//我真倒霉。
返回;
}
//在这里,我将对innerDictionary执行代价高昂的操作
//我不想锁定访问其他InnerDictionary的使用者
//当这个计算完成时。
这是一本流行词典(innerDictionary);
}
最后
{
//我需要在这里检查一下,以防第一次出现异常
//try最终阻止了这种情况的发生
if(aquiredLockForInnerDictionary)
{
Monitor.Exit(内部字典);
}
}
}
似乎您想得太多了。我唯一的问题是,您是否有可靠的方法知道是否已填充内部字典实例?我假设Count
属性的非零值就足够了,不是吗
根据该假设,您可以执行以下操作:
public Dictionary<TKey2, ReferenceTypedValues> SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
{
Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
lock (outerDictionary)
{
// No need to add a new innerDictionary if it already exists
if (!outerDictionary.TryGetValue(newKey, out innerDictionary))
{
innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
outerDictionary.Add(newKey, innerDictionary);
}
}
// Found the inner dictionary, but might be racing with another thread
// that also just found it. Lock and check whether it needs populating
lock (innerDictionary)
{
if (innerDictionary.Count == 0)
{
this.PopulateInnerDictionary(innerDictionary);
}
}
return innerDictionary;
}
public Dictionary SyncronizationExample1(Dictionary outerDictionary,TKey newKey)
{
Dictionary innerDictionary=null;
锁(外字典)
{
//如果innerDictionary已经存在,则无需添加新的innerDictionary
if(!outerDictionary.TryGetValue(newKey,out innerDictionary))
{
innerDictionary=新字典();
Add(newKey,innerDictionary);
}
}
//找到了内部字典,但可能正在与其他线程竞争
//它也刚刚找到它。锁定并检查它是否需要填充
锁(内部字典)
{
if(innerDictionary.Count==0)
{
这是一本流行词典(innerDictionary);
}
}
返回内部字典;
}
注:
- 将对象本身用作锁对象不是一个好主意,因为您可能会遇到一些其他代码具有相同的坏主意并与该代码死锁的风险。相反,请存储一个复合值(例如,
Tuple
,一个自定义命名结构,等等)它既包含用于锁定的对象
,也包含内部字典本身(当然,在字段中存储一个专用对象,用于锁定单个外部字典)
- 你的问题没有描述这些对象是如何使用的。我猜一旦填充,它们是只读的?如果是这样,上面的内容应该可以,但你应该使用类来强制执行。否则,你当然也需要为此添加同步
- 即使假设它们是只读的,您的原始代码实际上也不会返回内部字典引用
public Dictionary<TKey2, ReferenceTypedValues> SyncronizationExample1(Dictionary<TKey, Dictionary<TKey2, ReferenceTypedValues>> outerDictionary, TKey newKey)
{
Dictionary<TKey2, ReferenceTypedValues> innerDictionary = null;
lock (outerDictionary)
{
// No need to add a new innerDictionary if it already exists
if (!outerDictionary.TryGetValue(newKey, out innerDictionary))
{
innerDictionary = new Dictionary<TKey2, ReferenceTypedValues>();
outerDictionary.Add(newKey, innerDictionary);
}
}
// Found the inner dictionary, but might be racing with another thread
// that also just found it. Lock and check whether it needs populating
lock (innerDictionary)
{
if (innerDictionary.Count == 0)
{
this.PopulateInnerDictionary(innerDictionary);
}
}
return innerDictionary;
}