【问题标题】:Thread safety when writin to two .net dictionary in same method以相同方法写入两个 .net 字典时的线程安全
【发布时间】:2011-11-03 01:10:37
【问题描述】:

我被要求在类线程中制作字典安全。

我的第一个建议是实现一个线程安全的字典,.net 开发者社区已经在这方面进行了研究,但被拒绝了。

代码是这样的:

class Example()
{
    Dictionary<string, string> dic1;
    Dictionary<string, string> dic2;
     public void Example()
     {
        dic1 = new Dictionary<string,string>(10);
        dic2 = new Dictionary<string,string>(10);
     }

     public string Method1(string param1)
     {
            if(dic1.ContainsKey(param1))
            {
                return dic1[param1];
            }

            if(IsValidParam(param1))
            {
                dic1.Add(param1, param1);
                return param1;
            }

            try
            {   
                var params = GetValidParams(param1);
                if(params.Count > 0)
                {
                    foreach(var param in params)
                    {
                        if(!isValirParam(param)
                            continue;

                        dic1.Add(param1, param);

                        if(!dic2.ContainsKey(param1))
                        {
                            dic2.Add(param, param1);
                        }

                        return param;
                    }
                }
                else
                {
                    dic2.Add(param1, param1);
                    return param1;
                }
            }
            catch(Exception ex)
            {
                .....
            }

            return param1;
     }
}

这只是对同一方法内的两个字典具有读写访问权限的众多方法之一。

我正在考虑重构并在每个 Add 和 return 中使用“ReaderWriterLockSlim”,但我不知道这是否会使这个线程安全。

您知道如何解决这个问题吗?我愿意接受建议...

提前感谢您的宝贵时间

【问题讨论】:

  • 看看ConcurrentDictionary。也许它会满足您的需求。
  • 由于对一个字典的操作依赖于另一个字典中的值测试,ConcurrentDictionary 有帮助,但它本身似乎不够。
  • 我现在坚持使用框架 3.5,我们要到明年才能继续使用 4.0 =\抱歉更不用提了
  • 想象线程 A 执行该方法,而 param1 不在 dic1 中(因此跳过第一次返回)。在做任何其他事情之前,线程 B 运行相同的代码,但 param1 仍然不在 dic1 中。这是您需要解决的问题:两个线程都通过此方法运行,线程 A 比线程 B 稍早一点,因此线程 B 对早期测试中的 param1 的假设在线程 B 进行测试和它作用于测试结果。
  • 我同意 Tridus 的回答,尽管如果你的类有很多方法,有些方法会调用其他方法,它可能会变得复杂。将两个字典视为一个事物,识别针对该事物的原子操作(例如 test+read+write 是一个原子操作),并将这些操作包装在一个锁中。一个很好的免费资源:albahari.com/threading

标签: .net multithreading thread-safety dictionary


【解决方案1】:

在这种情况下,确保您获得预期结果的最简单方法是使用lock

class Example()
{
    Dictionary<string, string> dic1;
    Dictionary<string, string> dic2;
    private Object syncRoot;
     public void Example()
     {
        dic1 = new Dictionary<string,string>(10);
        dic2 = new Dictionary<string,string>(10);
        syncRoot = new Object();
     }

     public string Method1(string param1)
     {
         lock(syncRoot) {
            if(dic1.ContainsKey(param1))
            {
                return dic1[param1];
            }

            if(IsValidParam(param1))
            {
                dic1.Add(param1, param1);
                return param1;
            }

            try
            {   
                var params = GetValidParams(param1);
                if(params.Count > 0)
                {
                    foreach(var param in params)
                    {
                        if(!isValirParam(param)
                            continue;

                        dic1.Add(param1, param);

                        if(!dic2.ContainsKey(param1))
                        {
                            dic2.Add(param, param1);
                        }

                        return param;
                    }
                }
                else
                {
                    dic2.Add(param1, param1);
                    return param1;
                }
            }
            catch(Exception ex)
            {
                .....
            }

            return param1;
          }
     }
}

请注意,这会使事情变慢(锁有一些开销,特别是您不会有两个线程同时在锁块内执行任何操作),但它确保运行此方法的 Thread2 不能在 Thread1 测试一个值和尝试使用该测试的结果做某事之间进行更改。它也不需要 .net 4,因此您可以使用它。

edit - 还值得一提的是,如果您有任何其他方法可以修改任一字典,您将希望以相同的方式锁定它们。这里的关键是,在任何给定的时间只有一个线程可以搞乱一些东西。

【讨论】:

  • 会阻止调用:var params = GetValidParams(param1); 吗?
  • @YeomansLeo 仅当 GetValidParams 再次调用 Method1 时。这是一个锁定东西变得棘手的领域,你想比我更有选择性地使用它(而不是仅仅包装整个方法)。但是由于该代码不在原始示例中,因此我只想以简单的方式进行演示。 :) 从本质上讲,lock 命令表示没有其他线程可以通过 lock() 直到到达那里的人首先退出该块。它不会阻塞不相关的方法,除非那里还有一个使用相同锁定对象(在本例中为syncRoot)的锁。
  • 我应用了这个,我做了一点重构,我得到了 foreach 语句范围之外的字典修改。感谢您的所有回答!
【解决方案2】:

在 .NET 4 上已经有 ConcurrentDictionary&lt;T,V&gt;

【讨论】:

  • 在这种情况下这并不能解决问题,因为其中两个同时被操纵。仅仅让字典本身线程安全并不能使整个操作线程安全。
  • 我现在坚持使用框架 3.5,我们要到明年才能继续使用 4.0 =\抱歉更不用提了
  • @Tridus:由于操作只是将它们添加到字典中,我不确定为什么不这样做。显然,他不能将Dictionary 的每个实例都替换为ConcurrentDictionary
  • @Marc - 因为他部分基于对 dict1 的测试结果对 dict2 做某事。即使两个 dicts 本身都是线程安全的,如果另一个线程出现并修改它们,那么在对 dict1 进行测试之后但在对 dict2 执行某些操作之前中断的线程也会做不一致的事情。
【解决方案3】:

就像Tridus 所说,您几乎必须将Method1 的全部内容包装在lock 中。但是,这可能是ReaderWriterLockSlim 可能真正有帮助的一种情况。您可以对dic1 中的初始查找进行读取锁定。如果它成功了,那么你可以在不使用独占写锁的情况下退出。如果查找失败,则升级到写锁。当然,您必须进行测试,但如果预计初始查找大部分时间都会成功,那么您可以获得大量并发。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-08-03
    • 2010-10-24
    • 1970-01-01
    • 1970-01-01
    • 2010-10-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多