【问题标题】:Thread safety a concern in this case?在这种情况下,线程安全是一个问题?
【发布时间】:2017-02-01 05:35:10
【问题描述】:

我是否需要在字典的以下简单 getter 和 setter 中有一些线程安全机制?

如果需要,请提供 lock 和 ConcurrentDictionary 的示例。

    public virtual void Add(IFoo foo)
    {
        dictionary.Add(foo.name, foo);
    }

    public virtual IFoo Get(string name)
    {
        if (!dictionary.TryGetValue(name, out IFoo foo)) return null;
        return foo;
    }

    public virtual bool Has(string name)
    {
        return dictionary.TryGetValue(name, out IFoo foo);
    }

    public virtual IFoo Remove(string name)
    {
        if (dictionary.TryGetValue(name, out IFoo foo))
        {
            dictionary.Remove(name);
        }
        return foo;
    }

    protected readonly IDictionary<string, IFoo> dictionary = new Dictionary<string, IFoo>();

【问题讨论】:

  • 为什么你不能用lock和ConcurrentDictionary编写你自己的例子。本网站不是代码编写服务,您需要自己付出一些努力,我们可以向您展示您犯了哪些错误。
  • 我的第一个问题是,在上述情况下真的需要线程安全吗?请给我一些指导,然后我会编辑
  • 这段代码是如何使用的?向我们展示您使用的实际多线程代码。
  • 它是一个库,具有添加、删除和功能的单元测试,我不确定它会如何使用,但它需要线程安全吗?想象它是一个服务器端应用程序,数百万个连接将同时连接到服务器,应用程序将使用这个库来管理所有用户通用的键值对。一本字典供所有用户使用,
  • 如果我将其转换为 ConcurrentDictionary,我的方法添加、获取、删除等对于服务器端的所有用户仍然是线程安全的,该字典将成为所有客户端用户的共享资源

标签: c# multithreading concurrency


【解决方案1】:

根据要求,这是一个使用锁的线程安全版本。您的 Add 方法可以调用 GetOrAddAddOrUpdate,具体取决于在发生争用时您想要“第一个获胜”还是“最后一个获胜”。

public virtual void Add(IFoo foo)
{
    AddOrUpdate(foo);   // If you want "last one wins"
    //GetOrAdd(foo);      // If you want "first one wins"
}

private virtual IFoo GetOrAdd(IFoo foo)
{
    lock(dictionary)
    {
        IFoo result;
        if (!dictionary.TryGetValue(foo.name, out result))
        {
            // Not in the dictionary; add it
            result = foo;
            dictionary.Add(foo.name, foo);
        }
        return result;
    }
}
public virtual void AddOrUpdate(IFoo foo)
{
    lock(dictionary)
    {
        // Last one wins and overwrites if there is a race-condition
        dictionary[foo.name] = foo;
    }
}

public virtual IFoo Get(string name)
{
    lock(dictionary)
    {
        IFoo foo;
        dictionary.TryGetValue(name, out foo);
        return foo; // will be null if TryGetValue returned false
    }
}

public virtual bool Has(string name)
{
    lock(dictionary)
    {
        return dictionary.ContainsKey(name);
    }
    // But there's no guarantee it's still there when this method returns
}

public virtual IFoo Remove(string name)
{
    lock (dictionary)
    {
        IFoo result;
        if (dictionary.TryGetValue(name, out result))
        {
            dictionary.Remove(name);
        }
        return result;
    }
}

// Dictionary should be private, not protected to be sure a derived
// class doesn't access it without using locks
private readonly IDictionary<string, IFoo> dictionary = new Dictionary<string, IFoo>();

这是一个带有 ConcurrentDictionary 的版本:

public virtual void Add(IFoo foo)
{
    AddOrUpdate(foo);   // If you want "last one wins"
    //GetOrAdd(foo);      // If you want "first one wins"
}

private virtual IFoo GetOrAdd(IFoo foo)
{
    return concurrentDictionary.GetOrAdd(foo.name, foo);
}
public virtual void AddOrUpdate(IFoo foo)
{
    concurrentDictionary[foo.name] = foo;
}

public virtual IFoo Get(string name)
{
    IFoo result;
    concurrentDictionary.TryGetValue(name, out result);
    return result; // will be null if TryGetValue returned false
}

public virtual bool Has(string name)
{
    return concurrentDictionary.ContainsKey(name);
    // But there's no guarantee it's still there when this method returns
}

public virtual IFoo Remove(string name)
{
    IFoo result;
    concurrentDictionary.TryRemove(name, out result);
    return result;
}

private readonly ConcurrentDictionary<string, IFoo> concurrentDictionary = new ConcurrentDictionary<string, IFoo>();

【讨论】:

  • 谢谢乔,有没有更多关于第一个获胜和最后一个获胜的解释。
  • “第一个获胜”表示如果两个线程同时调用 Add 方法,那么第一个将项目添加到字典中,而第二个则不会。 “最后一个获胜”意味着在这种情况下,第一个线程会将其项目添加到字典中,第二个线程将覆盖它。如果仍然不清楚,请阅读 ConcurrentDictionary.GetOrAdd 的 MSDN 文档。
  • But there's no guarantee it's still there when this method returns 那么你有什么建议来覆盖这个角落,在每个功能中加锁?
  • @user2727195 - 线程安全是一个复杂的主题,编写线程安全应用程序需要的不仅仅是使用线程安全组件。在这种情况下,您可能只是摆脱了“Has”方法。如果您想知道某个项目是否在某个时刻在字典中,则调用 Get 并检查是否为空。根据您的应用程序要求,另一种方法是永远不要从字典中删除项目。在这种情况下,一旦“Has”返回 true 一次,您就知道该项目将始终存在。
【解决方案2】:

如果你的字典会被不同的线程访问,那么你应该阅读以下关于字典的内容:

只要不修改集合,字典可以同时支持多个阅读器。即便如此,通过集合进行枚举本质上不是线程安全的过程。在枚举与写访问竞争的极少数情况下,必须在整个枚举期间锁定集合。要允许集合被多个线程访问以进行读写,您必须实现自己的同步。

我建议使用ConcurrentDictionary&lt;TKey, TValue&gt;作为线程安全集合,它提供原子和线程安全的方法:

所有这些操作都是原子的,并且对于 ConcurrentDictionary 类上的所有其他操作都是线程安全的。唯一的例外是接受委托的方法,即 AddOrUpdate 和 GetOrAdd。

查看此链接:https://msdn.microsoft.com/en-us/library/dd287191(v=vs.110).aspx

您可以使用lock 语句而不是使用您自己的锁定代码 如果你想使用Dictionary来实现线程同步,请查看这个链接:https://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

【讨论】:

  • "要允许集合被多个线程访问进行读写,你必须实现自己的同步。"这也适用于 ConcurrentDictionary 吗?想象一下,如果上面的字典是 ConcurrentDictionary,会在每个 add、get、remove 函数中使用 lock(this){}?
  • 锁定可以与 Dictionary 一起使用,但在 ConcurrentDictionary 的情况下,大多数方法都不需要它,唯一的例外是接受委托的方法。
  • 哦,您的编辑引起了关注,我还有一个使用 GetOrAdd 的问题,但我从未锁定它,介意看看它是否是线程安全的? stackoverflow.com/questions/41865146/… 使用代码 #2 和 #3 检查 Scott 的答案。
  • 这些方法接受一个委托,并且正如文档所述:“这些方法的委托在锁外调用,以避免在锁下执行未知代码可能出现的问题”,因此在您的方法中您必须提供锁定机制的委托地址。
  • 您能否编辑您的答案并提供代码帮助,请使用 ConcurrentDictionary,如果您可以使用代码帮助解决链接问题,那就太好了,我会接受答案。
猜你喜欢
  • 1970-01-01
  • 2011-12-15
  • 2014-04-12
  • 2023-02-23
  • 2014-03-22
  • 1970-01-01
  • 1970-01-01
  • 2022-01-23
  • 1970-01-01
相关资源
最近更新 更多