这是在 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
的源代码,我看不到任何地方使用任何锁, 我可以看到 TryAddInternal
和 TryUpdateInternal
.
无论如何,前面发布的方法有效,但我不明白为什么它有效,一旦我删除看似不必要的 success = false
分配它就不起作用,存在不匹配。所以我很好奇是什么让这些代表在失败后重蹈覆辙?
我的问题是:
1。如图所示使用 AddOrUpdate
是否安全,还是我应该锁定所有内容并忘记它?
2。是什么让代表在被打断后重复自己的话?跟'Compare-and-swap'有关系吗? (最好奇这个);
3。有没有 topics/concepts 你想让我检查一下以更好地了解线程安全环境?
因为 addValueFactory
和 updateValueFactory
委托由 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
请注意,如果 if
的 oldValue == 0
分支中没有明确设置 success = false
,线程 1 会认为它仍然成功售出了该商品,即使Thread 2 卖掉了最后一个,没有库存了。
因此,您问题中的技术按预期工作。
我一直在关注由 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
的源代码,我看不到任何地方使用任何锁, 我可以看到 TryAddInternal
和 TryUpdateInternal
.
无论如何,前面发布的方法有效,但我不明白为什么它有效,一旦我删除看似不必要的 success = false
分配它就不起作用,存在不匹配。所以我很好奇是什么让这些代表在失败后重蹈覆辙?
我的问题是:
1。如图所示使用 AddOrUpdate
是否安全,还是我应该锁定所有内容并忘记它?
2。是什么让代表在被打断后重复自己的话?跟'Compare-and-swap'有关系吗? (最好奇这个);
3。有没有 topics/concepts 你想让我检查一下以更好地了解线程安全环境?
因为 addValueFactory
和 updateValueFactory
委托由 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 |
请注意,如果 if
的 oldValue == 0
分支中没有明确设置 success = false
,线程 1 会认为它仍然成功售出了该商品,即使Thread 2 卖掉了最后一个,没有库存了。
因此,您问题中的技术按预期工作。