C# 在 Dictionary<string, object> 的键上使用锁
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/157511/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Using lock on the key of a Dictionary<string, object>
提问by Per Hornsh?j-Schierbeck
I have a Dictionary<string, someobject>
.
我有一个Dictionary<string, someobject>
.
EDIT: It was pointed out to me, that my example was bad. My whole intention was not to update the references in a loop but to update different values based on differnt threads need to update/get the data. I changed the loop to a method.
编辑:有人向我指出,我的例子很糟糕。我的整个意图不是在循环中更新引用,而是根据需要更新/获取数据的不同线程更新不同的值。我将循环更改为方法。
I need to update items in my dictionary - one key at a time and i was wondering if there are any problems in using the lock on the .key value of my Dictionary object?
我需要更新我的字典中的项目 - 一次一个键,我想知道在使用我的 Dictionary 对象的 .key 值上的锁时是否有任何问题?
private static Dictionary<string, MatrixElement> matrixElements = new Dictionary<string, MatrixElement>();
//Pseudo-code
public static void UpdateValue(string key)
{
KeyValuePair<string, MatrixElement> keyValuePair = matrixElements[key];
lock (keyValuePair.Key)
{
keyValuePair.Value = SomeMeanMethod();
}
}
Would that hold up in court or fail? I just want each value in the dictionary to be locked independantly so locking (and updating) one value does not lock the others. Also i'm aware the locking will be holding for a long time - but the data will be invalid untill updated fully.
这会在法庭上成立还是失败?我只想独立锁定字典中的每个值,以便锁定(和更新)一个值不会锁定其他值。另外我知道锁定将保持很长时间 - 但数据将无效,直到完全更新。
采纳答案by Philip Rieck
Locking on an object that is accessible outside of the code locking it is a big risk. If any other code (anywhere) ever locks that object you could be in for some deadlocks that are hard to debug. Also note that you lock the object, not the reference, so if I gave you a dictionary, I may still hold references to the keys and lock on them - causing us to lock on the same object.
锁定在代码锁定之外可访问的对象是一个很大的风险。如果任何其他代码(任何地方)曾经锁定该对象,您可能会陷入一些难以调试的死锁。另请注意,您锁定的是object,而不是引用,因此如果我给您一本字典,我可能仍然持有对键的引用并锁定它们 - 导致我们锁定同一个对象。
Ifyou completely encapsulate the dictionary, and generate the keys yourself (they aren't ever passed in, then you may be safe.
如果您完全封装字典,并自己生成密钥(它们从未传入,那么您可能是安全的。
However, try to stick to one rule - limit the visibility of the objects you lock on to the locking code itself whenever possible.
但是,请尝试遵守一条规则 - 尽可能将您锁定的对象的可见性限制在锁定代码本身。
That's why you see this:
这就是为什么你会看到这个:
public class Something
{
private readonly object lockObj = new object();
public SomethingReentrant()
{
lock(lockObj) // Line A
{
// ...
}
}
}
rather than seeing line A above replaced by
而不是看到上面的 A 行被替换为
lock(this)
That way, a separate object is locked on, and the visibility is limited.
这样,一个单独的对象被锁定,可见性受到限制。
EditJon Skeetcorrectly observed that lockObj above should be readonly.
编辑Jon Skeet正确地观察到上面的 lockObj 应该是只读的。
回答by Sander
No, this would not work.
不,这行不通。
The reason is string interning. This means that:
原因是字符串实习。这意味着:
string a = "Something";
string b = "Something";
are both the same object! Therefore, you should never lock on strings because if some other part of the program (e.g. another instance of this same object) also wants to lock on the same string, you could accidentally create lock contention where there is no need for it; possibly even a deadlock.
都是同一个对象!因此,您永远不应该锁定字符串,因为如果程序的其他部分(例如同一对象的另一个实例)也想锁定同一个字符串,您可能会意外地在不需要它的地方创建锁争用;甚至可能陷入僵局。
Feel free to do this with non-strings, though. For best clarity, I make it a personal habit to always create a separate lock object:
不过,请随意使用非字符串来执行此操作。为清楚起见,我将始终创建单独的锁对象作为个人习惯:
class Something
{
bool threadSafeBool = true;
object threadSafeBoolLock = new object(); // Always lock this to use threadSafeBool
}
I recommend you do the same. Create a Dictionary with the lock objects for every matrix cell. Then, lock these objects when needed.
我建议你也这样做。为每个矩阵单元格创建一个带有锁对象的字典。然后,在需要时锁定这些对象。
PS. Changing the collection you are iterating over is not considered very nice. It will even throw an exception with most collection types. Try to refactor this - e.g. iterate over a list of keys, if it will always be constant, not the pairs.
附注。更改您正在迭代的集合被认为不是很好。它甚至会抛出大多数集合类型的异常。尝试重构这个 - 例如迭代键列表,如果它总是不变的,而不是对。
回答by Ray Hayes
In your example, you can not do what you want to do!
在你的例子中,你不能做你想做的事!
You will get a System.InvalidOperationExceptionwith a message of Collection was modified; enumeration operation may not execute.
你会得到一个System.InvalidOperationException和Collection 被修改的消息;枚举操作可能无法执行。
Here is an example to prove:
下面是一个例子来证明:
using System.Collections.Generic;
using System;
public class Test
{
private Int32 age = 42;
static public void Main()
{
(new Test()).TestMethod();
}
public void TestMethod()
{
Dictionary<Int32, string> myDict = new Dictionary<Int32, string>();
myDict[age] = age.ToString();
foreach(KeyValuePair<Int32, string> pair in myDict)
{
Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
++age;
Console.WriteLine("{0} : {1}", pair.Key, pair.Value);
myDict[pair.Key] = "new";
Console.WriteLine("Changed!");
}
}
}
The output would be:
输出将是:
42 : 42
42 : 42
Unhandled Exception: System.InvalidOperationException: Collection was modified; enumeration operation may not execute.
at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource)
at System.Collections.Generic.Dictionary`2.Enumerator.MoveNext()
at Test.TestMethod()
at Test.Main()
回答by babackman
I can see a few potential issues there:
我可以看到一些潜在的问题:
- strings can be shared, so you don't necessarily know who else might be locking on that key object for what other reason
- strings might notbe shared: you may be locking on one string key with the value "Key1" and some other piece of code may have a different string object that also contains the characters "Key1". To the dictionary they're the same key but as far as locking is concerned they are different objects.
- That locking won't prevent changes to the value objects themselves, i.e.
matrixElements[someKey].ChangeAllYourContents()
- 字符串可以共享,因此您不一定知道还有谁可能出于其他原因锁定该键对象
- 字符串可能不会被共享:您可能锁定了一个值为“Key1”的字符串键,而其他一些代码可能有一个不同的字符串对象,该对象也包含字符“Key1”。对于字典,它们是相同的键,但就锁定而言,它们是不同的对象。
- 该锁定不会阻止对值对象本身的更改,即
matrixElements[someKey].ChangeAllYourContents()
回答by Ilya Ryzhenkov
Note: I assume exception when modifying collection during iteration is already fixed
注意:我假设在迭代期间修改集合时的异常已经修复
Dictionary is not thread-safe collection, which means it is notsafe to modify and read collection from different threads without external synchronization. Hashtable is (was?) thread-safe for one-writer-many-readers scenario, but Dictionary has different internal data structure and doesn't inherit this guarantee.
字典是不是线程安全的集合,这意味着它是不是安全的修改和读取不同的线程收集无需外部同步。Hashtable 是(曾经?)线程安全的一个作者多读者场景,但 Dictionary 具有不同的内部数据结构,并且不继承这种保证。
This means that you cannot modify your dictionary while you accessing it for read or write from the other thread, it can just broke internal data structures. Locking on the key doesn't protect internal data structure, because while you modify that very key someone could be reading different key of your dictionary in another thread. Even if you can guarantee that all your keys are same objects (like said about string interning), this doesn't bring you on safe side. Example:
这意味着当您从另一个线程访问它以进行读取或写入时,您无法修改您的字典,它只会破坏内部数据结构。锁定键并不能保护内部数据结构,因为当您修改该键时,有人可能正在另一个线程中读取字典的不同键。即使你可以保证你所有的键都是相同的对象(就像关于字符串实习所说的那样),这也不会让你安全。例子:
- You lock the key and begin to modify dictionary
- Another thread attempts to get value for the key which happens to fall into the same bucket as locked one. This is not only when hashcodes of two objects are the same, but more frequently when hashcode%tableSize is the same.
- Both threads are accessing the same bucket (linked list of keys with same hashcode%tableSize value)
- 你锁上钥匙开始修改字典
- 另一个线程尝试获取恰好与锁定的键落入同一桶的密钥的值。这不仅在两个对象的哈希码相同时,而且在 hashcode%tableSize 相同时更常见。
- 两个线程都访问同一个存储桶(具有相同 hashcode%tableSize 值的键的链表)
If there is no such key in dictionary, first thread will start modifying the list, and the second thread will likely to read incomplete state.
如果字典中没有这样的键,第一个线程将开始修改列表,第二个线程可能会读取未完成状态。
If such key already exists, implementation details of dictionary could still modify data structure, for example move recently accessed keys to the head of the list for faster retrieval. You cannot rely on implementation details.
如果这样的键已经存在,字典的实现细节仍然可以修改数据结构,例如将最近访问的键移动到列表的头部以便更快地检索。您不能依赖实现细节。
There are many cases like that, when you will have corrupted dictionary. So you have to have external synchronization object (or use Dictionary itself, if it is not exposed to public) and lock on it during entire operation. If you need more granular locks when operation can take some long time, you can copy keys you need to update, iterate over it, lock entire dictionary during single key update (don't forget to verify key is still there) and release it to let other threads run.
有很多这样的情况,当你会损坏字典。所以你必须有外部同步对象(或者使用 Dictionary 本身,如果它没有公开)并在整个操作期间锁定它。如果您在操作可能需要很长时间时需要更细粒度的锁,您可以复制您需要更新的键,迭代它,在单键更新期间锁定整个字典(不要忘记验证键是否仍然存在)并将其释放到让其他线程运行。
回答by Ilya Ryzhenkov
If I'm not mistaken, the original intention was to lock on a single element, rather than locking the whole dictionary (like table-level lock vs. row level lock in a DB)
如果我没记错的话,最初的意图是锁定单个元素,而不是锁定整个字典(例如数据库中的表级锁与行级锁)
you can't lock on the dictionary's key as many here explained.
你不能像这里解释的那样锁定字典的键。
What you can do, is to keep an internal dictionary of lock objects, that corresponds to the actual dictionary. So when you'd want to write to YourDictionary[Key1], you'll first lock on InternalLocksDictionary[Key1] - so only a single thread will write to YourDictionary.
您可以做的是保留与实际字典相对应的锁对象的内部字典。因此,当您想写入 YourDictionary[Key1] 时,您将首先锁定 InternalLocksDictionary[Key1] - 因此只有一个线程会写入 YourDictionary。
a (not too clean) example can be found here.
一个(不太干净)的例子可以在这里找到。
回答by Mark
Just came across this and thought id share some code I wrote a few years ago where I needed to a dictionary on a key basis
刚刚遇到这个,并认为 id 分享了我几年前编写的一些代码,我需要在关键基础上使用字典
using (var lockObject = new Lock(hashedCacheID))
{
var lockedKey = lockObject.GetLock();
//now do something with the dictionary
}
the lock class
锁类
class Lock : IDisposable
{
private static readonly Dictionary<string, string> Lockedkeys = new Dictionary<string, string>();
private static readonly object CritialLock = new object();
private readonly string _key;
private bool _isLocked;
public Lock(string key)
{
_key = key;
lock (CritialLock)
{
//if the dictionary doesnt contain the key add it
if (!Lockedkeys.ContainsKey(key))
{
Lockedkeys.Add(key, String.Copy(key)); //enusre that the two objects have different references
}
}
}
public string GetLock()
{
var key = Lockedkeys[_key];
if (!_isLocked)
{
Monitor.Enter(key);
}
_isLocked = true;
return key;
}
public void Dispose()
{
var key = Lockedkeys[_key];
if (_isLocked)
{
Monitor.Exit(key);
}
_isLocked = false;
}
}