多个线程使用锁上的锁更新共享字典的不同项

Ach*_*les 2 c# multithreading

我有一个Dictionary<string, List<MyObject>>,我需要运行一些资源密集型操作List<MyObject>.我正在试图弄清楚我是否可以为Dictionary执行资源密集型任务的每个键创建一个线程,以便每个线程更新其键的List.换句话说,多个线程同时更新字典中的不同项目?

请考虑以下简化的伪代码 -

public void MyMethod() {
    //The myDict object needs to be shared by all threads.
    Dictionary<string, List<MyObject>> myDict = new Dictionary<string, List<MyObject>>();
    //GetKeyValue() may return the same key multiple times
    foreach(var kv in GetKeyValue()) { 
        if(myDict.ContainsKey(kv.Key) { myDict[kv.Key].Add(kv.Value); }
        else { myDict.Add(kv.Key, kv.Value); }
        Task.Factory.StartNew(() => { RunSubsetSum(kv.Key, myDict); });
    }
}
//Resource intensive method
public void RunSubsetSum(string key, Dictionary<string, List<MyObject>> myDict)  { 
    //Lock on key so that no two threads run for the same key
    lock(key){
        foreach(var valueToRemove in GetRemovableObjs()) 
            myDict[kv.Key].Remove(valueToRemove);
    }
}
Run Code Online (Sandbox Code Playgroud)

基本上,这个想法是 -

  1. 没有两个线程同时为同一个键运行 - 锁定(密钥)会使它们排队(按顺序运行)吗?
  2. 所有正在运行的线程可以独立更新相同的字典 - 我希望多个线程能够同时更新同一个字典中的不同项.

我尝试了上述方法,但结果似乎不一致.我认为这是因为MyMethod()为RunSubsetSum()已经运行的键更新了Dictionary,但不知道如何在不中断其他键的循环的情况下锁定MyMethod()中的键.我想知道C#是否为这个问题提供了更简单的解决方案.有什么想法吗?

注意:我正在考虑创建一个Dictionary,以便我可以跟踪当前正在处理哪些键并更新MyMethod()以缓冲键,直到线程完成,但我想避免添加它,如果我可以避免过度复杂的逻辑.

Ser*_*rvy 5

你不应该过lock上一个string.你只是打开一个受伤的世界,主要围绕字符串实习.每次使用与另一个字符串文字在语义上相同的字符串文字时,它们都会有相同的引用(除非你将其关闭),这意味着如果你的任何字典键最终都是字符串文字,那么某些其他代码在某处在与您的代码无关的应用程序域中,否则最终可能会锁定相同的值.这可能最终导致死锁,或者两个段有时等待他们实际上不需要等待.

您应该只lock在一个对象上,您可以确定只有管理同步的一种类型才能访问.这样做意味着您可以始终只查看这一个类来分析正在进行的同步,并且您不必担心应用程序其余部分正在发生什么以确定此类的同步逻辑.

幸运的是,你已经做的有对应于每个键的对象,那你永远不会将这个类之外的List.您不需要单独的对象字典来锁定,您只需使用List.

你遇到的另一个问题是你在工作线程中获取字典的值,这是不安全的,因为你在另一个线程中修改它,虽然这可以通过简单地获取字典的值来解决在开始新线程之前,而不是之后,简单地传入stringList进入RunSubsetSum而不是Dictionary.

您还要改变List工作线程和主线程中的对象,因此您需要确保调用者在使用它之前锁定列表,以及工作者.