【问题标题】:Is this reliable way to use AddOrUpdate on ConcurrentDictionary这是在 ConcurrentDictionary 上使用 AddOrUpdate 的可靠方法吗
【发布时间】:2021-04-27 13:46:20
【问题描述】:

我一直在学习由 Simon Robinson 撰写的关于并发集合的 Pluralsight 课程。

他以下列方式使用AddOrUpdate 以使其成为线程安全的:

public bool TrySellShirt(string code)
{
    bool success = false;

    _stock.AddOrUpdate(code, 
        (itemname) => { success = false; return 0; },
        (itemName, oldValue) =>
        {
            if (oldValue == 0)
            {
                success = false;
                return 0;
            }
            else
            {
                success = true;
                return oldValue - 1;
            }
        });

    if (success)
        Interlocked.Increment(ref _totalQuantitySold);

    return success;
}

所以,我知道 AddOrUpdate 并不完全是原子的,正如它在文档中所说:“ addValueFactory 和 updateValueFactory 委托在锁之外被调用,以避免在一把锁。 "

这对我来说很清楚。尚不清楚在代表中将success 设置为false 的意义何在。 AddValueFactory 参数被故意用作 lambda,因此可以设置 success = false,而不仅仅是返回 0。我有点理解/认为如果方法/lambda 被另一个线程中断(并且它可以被中断,因为它是在外部调用的锁),它将尝试重复自己,因此我们应该将任何相应值的状态设置为其初始值以干净地参与新的迭代,因此设置success = false;

同样来自文档:如果您在不同的线程上同时调用 AddOrUpdate,可能会多次调用 addValueFactory,但它的键/值对可能不会在每次调用时都添加到字典中。 p>

如果是这样的话,我一直在 source.dot.net 上查看AddOrUpdate 的源代码,我看不到任何地方正在使用任何锁,我可以看到TryAddInternalTryUpdateInternal

无论如何,前面发布的方法有效,但我不明白它为什么有效,一旦我删除看似不必要的success = false 分配,它就不起作用了,就会出现不匹配。所以我很好奇是什么让这些代表在失败后重蹈覆辙?

我的问题是:

1。如图所示使用AddOrUpdate 是否安全,或者我应该锁定所有内容并忘记它?

2。是什么让代表在被打断后重复自己?它与“比较和交换”有什么关系吗? (对这个最好奇);

3。是否有任何主题/概念让我查看以更好地了解线程安全环境?

【问题讨论】:

    标签: c# concurrency thread-safety atomic concurrentdictionary


    【解决方案1】:

    因为 addValueFactoryupdateValueFactory 委托由 ConcurrentDictionary 调用而没有任何锁,因此在 add/updateValueFactory 代码运行时,另一个线程可能会更改字典的内容。为了处理这种情况,如果调用了addValueFactory(因为字典中不存在该键),它只会在字典中不存在键still时添加返回值.同样,如果调用了updateValueFactory,它只会在当前值仍为oldValue 时更新键的值。

    如果由于另一个线程在 add/updateValueFactory 代码运行时添加/更新/删除相同的键而导致不匹配,它将简单地尝试根据字典的最新内容再次调用适当的委托(委托不会“中断”,而是字典本身再次调用它们,正在添加/更新的键的值已更改)。这解释了为什么您仍然需要在 lambda 中分配 success = false,即使 success 已初始化为 false。以下示例可能有助于可视化行为:

    初始字典状态:_stock["X"] = 1

    Step Thread 1 Thread 2
    1 Calls _stock.AddOrUpdate("X", ...)
    2 updateValueFactory invoked (oldValue = 1)
    3 Calls _stock.AddOrUpdate("X", ...)
    4 updateValueFactory invoked (oldValue = 1)
    5 Sets success = true, returns oldValue - 1 = 0
    6 Dictionary checks that the value for key "X" is still = 1 (true)
    7 Value for key "X" is updated to 0
    8 Sets success = true, returns oldValue - 1 = 0
    9 Dictionary checks that the value for key "X" is still = 1 (false)
    10 updateValueFactory invoked again (oldValue = 0)
    11 Sets success = false, returns 0
    12 Dictionary checks that the value for key "X" is still = 0 (true)
    13 Value for key "X" is updated to 0
    14 Final value of success is false Final value of success is true

    请注意,如果没有在 ifoldValue == 0 分支中显式设置 success = false,线程 1 会认为它仍然成功出售了该项目,即使不再有任何库存,因为线程2 个卖掉了最后一个。

    因此,您问题中的技术按预期工作。

    【讨论】:

    • 感谢您的澄清,1。如图所示使用 AddOrUpdate 是否安全,或者我应该锁定所有内容并忘记它?
    • @ShaKal - 是的,以这种方式使用 AddOrUpdate 似乎是安全的,只要您像示例中那样小心地实现它(确保不要省略任何 success = false 分配)。跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-07-20
    • 1970-01-01
    • 2012-09-07
    相关资源
    最近更新 更多