这是在 ConcurrentDictionary 上使用 AddOrUpdate 的可靠方法吗

Is this reliable way to use AddOrUpdate on ConcurrentDictionary

我一直在关注由 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。我有点 understand/think 如果 method/lambda 被另一个线程中断(并且它可以被打断,因为它是在锁外调用的),它会尝试重复自己,所以我们应该将任何对应值的状态设置为它们的初始值,以干净地进行新的迭代,因此设置 success = false;.

另外来自文档:如果您在不同的线程上同时调用 AddOrUpdate,addValueFactory 可能会被调用多次,但它的 key/value 对可能不会在每次调用时都添加到字典中。

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

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

我的问题是:

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

2。是什么让代表在被打断后重复自己的话?跟'Compare-and-swap'有关系吗? (最好奇这个);

3。有没有 topics/concepts 你想让我检查一下以更好地了解线程安全环境?

因为 addValueFactoryupdateValueFactory 委托由 ConcurrentDictionary 调用而没有任何锁,所以另一个线程可以更改字典的内容,而 add/updateValueFactory 代码是运行。为了处理这种情况,如果 addValueFactory 被调用(因为键在字典中不存在)它只会在键 still 不存在的情况下添加返回值'字典里没有。类似地,如果 updateValueFactory 被调用,它只会在当前值仍然是 oldValue.

时更新键的值

如果在 add/updateValueFactory 代码为 运行 时另一个线程 adding/updating/deleting 相同的键导致不匹配,它将简单地尝试根据字典的最新内容(代表没有被“打断”,是字典本身再次调用它们,键的值 added/updated 已经改变)。这解释了为什么您仍然需要在 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 会认为它仍然成功售出了该商品,即使Thread 2 卖掉了最后一个,没有库存了。

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