我一直在学习由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可能会被多次调用,但其键/值对可能不会在每次调用时都添加到字典中
如果是这样的话,我一直在source.dot.net上检查AddOrUpdate
的源代码,我看不到任何地方使用了任何锁,我可以看到TryAddInternal
和TryUpdateInternal
。
无论如何,前面提到的方法是有效的,但我不明白为什么它有效,而且一旦我删除了看似不必要的success = false
赋值,它就不起作用了,这就是不匹配。所以我很好奇,是什么让这些代表在失败后重复自己?
我的问题是:
1。如图所示使用AddOrUpdate
安全吗?还是我应该锁定所有内容并忘记它?
2。是什么让学员在被打断后重复自己的话?它与"比较和交换"有什么关系吗?(最好奇的是这个(;
3。为了更好地理解线程安全环境,有什么主题/概念需要我检查吗?
因为addValueFactory
和updateValueFactory
委托是由ConcurrentDictionary在没有任何锁的情况下调用的,所以在运行add/updateValueFactory代码时,另一个线程可能会更改字典的内容。为了处理这种情况,如果调用了addValueFactory
(因为字典中不存在键(,它只会在字典中仍然不存在键的情况下添加返回值。类似地,如果调用了updateValueFactory
,则仅当当前值仍然是oldValue
时,才会更新键的值。
如果在add/updateValueFactory代码运行时,由于另一个线程添加/更新/删除了相同的密钥而导致不匹配,它将简单地尝试根据字典的最新内容再次调用适当的委托(委托不会被"中断",而是字典本身再次调用它们——添加/更新的键的值已经更改(。这解释了为什么即使success
被初始化为false,仍然需要在lambdas中分配success = false
。以下示例可能有助于可视化行为:
初始字典状态:_stock["X"] = 1
步骤 | 线程1 | 线程2 | |
---|---|---|---|
1 | 调用_stock.AddOrUpdate("X", ...) |
||
2 | updateValueFactory 已调用(旧值=1( | //tr>||
3 | 调用 | ||
4 | 调用updateValueFactory (旧值=1( | [/tr>||
5 | 设置 | ||
6 | 字典检查关键字";X〃;仍然=1(true( | ||
7 | 密钥的值";X〃;更新为0 |
||
8 | 设置success = true ,返回oldValue - 1 =0 |
||
9 | 字典检查关键字";X〃;仍然=1(false( | ||
10 | updateValueFactory 再次调用(旧值=0( | //tr>||
11 | 设置success = false ,返回0 |
||
12 | 字典检查关键字";X〃;仍然=0(true( | ||
13 | 键的值";X〃;更新为0 |
||
14 | success 的最终值为false |