不,你所拥有的并不安全。检查是否可以与来自另一个线程_count >= MAXIMUM
的调用竞争。Interlocked.Increment
这实际上很难使用低锁定技术来解决。要使其正常工作,您需要在不使用锁的情况下使一系列操作看起来是原子的。那是困难的部分。这里有问题的一系列操作是:
- 读
_count
- 测试
_count >= MAXIMUM
- 根据以上情况做出决定。
- 增量
_count
取决于做出的决定。
如果您没有使所有这 4 个步骤看起来都是原子的,那么就会出现竞争条件。在不使用锁的情况下执行复杂操作的标准模式如下。
public static T InterlockedOperation<T>(ref T location)
{
T initial, computed;
do
{
initial = location;
computed = op(initial); // where op() represents the operation
}
while (Interlocked.CompareExchange(ref location, computed, initial) != initial);
return computed;
}
注意正在发生的事情。重复执行该操作,直到 ICX 操作确定初始值在第一次读取和尝试更改它的时间之间没有更改。这是标准模式,所有的魔法都是因为CompareExchange
(ICX) 调用而发生的。但是请注意,这并没有考虑到ABA 问题。1
你可以做什么:
因此,采用上述模式并将其合并到您的代码中会导致这种情况。
public void CheckForWork()
{
int initial, computed;
do
{
initial = _count;
computed = initial < MAXIMUM ? initial + 1 : initial;
}
while (Interlocked.CompareExchange(ref _count, computed, initial) != initial);
if (replacement > initial)
{
Task.Run(() => Work());
}
}
就个人而言,我会完全采用低锁定策略。我上面介绍的有几个问题。
- 这实际上可能比使用硬锁运行得慢。原因很难解释,超出了我的回答范围。
- 任何与上述内容的偏差都可能导致代码失败。是的,它真的那么脆弱。
- 很难理解。我的意思是看看它。这是丑陋的。
你应该做什么:
使用硬锁路线,您的代码可能看起来像这样。
private object _lock = new object();
private int _count;
public void CheckForWork()
{
lock (_lock)
{
if (_count >= MAXIMUM) return;
_count++;
}
Task.Run(() => Work());
}
public void CompletedWorkHandler()
{
lock (_lock)
{
_count--;
}
}
请注意,这要简单得多,而且更不容易出错。您实际上可能会发现这种方法(硬锁)实际上比我上面展示的(低锁)更快。同样,原因很棘手,并且有一些技术可以用来加快速度,但这超出了这个答案的范围。
1在这种情况下,ABA 问题并不是真正的问题,因为逻辑不依赖于_count
保持不变。重要的是它的值在两个时间点是相同的,无论其间发生了什么。换句话说,问题可以简化为看起来价值没有改变的问题,即使实际上它可能已经改变了。