这是线程安全的权利?

只是检查… _count被安全地访问,对吧?

这两种方法都由多个线程访问。

 private int _count; public void CheckForWork() { if (_count >= MAXIMUM) return; Interlocked.Increment(ref _count); Task t = Task.Run(() => Work()); t.ContinueWith(CompletedWorkHandler); } public void CompletedWorkHandler(Task completedTask) { Interlocked.Decrement(ref _count); // Handle errors, etc... } 

不, if (_count >= MAXIMUM) return; 不是线程安全的。

编辑:你也必须locking读取,这应该在逻辑上与增量分组,所以我会重写

 private int _count; private readonly Object _locker_ = new Object(); public void CheckForWork() { lock(_locker_) { if (_count >= MAXIMUM) return; _count++; } Task.Run(() => Work()); } public void CompletedWorkHandler() { lock(_locker_) { _count--; } ... } 

这是线程安全的,对吗?

假设MAXIMUM是1,count是0,五个线程调用CheckForWork。

所有五个线程都可以validation计数小于MAXIMUM。 然后,柜台就会碰到五,五个工作就开始了。

这似乎违背了代码的意图。

而且:这个领域是不稳定的。 那么,什么机制保证任何线程都会在无记忆path上读取最新值呢? 没有什么保证! 如果条件不成立,你只能做记忆障碍。

更一般地说:你在这里做假经济。 通过使用低locking解决scheme,您可以节省无锁locking所需的十几纳秒。 就拿锁 。 你可以承受额外的十二纳秒。

更一般的情况是: 除非您是处理器架构方面的专家,并且知道CPU允许在低lockingpath上执行的所有优化,否则不要编写低locking代码 。 你不是这样的专家。 我也不是。 这就是为什么我不写低locking代码

Semaphore和SemaphoreSlim就是这样的:

 private readonly SemaphoreSlim WorkSem = new SemaphoreSlim(Maximum); public void CheckForWork() { if (!WorkSem.Wait(0)) return; Task.Run(() => Work()); } public void CompletedWorkHandler() { WorkSem.Release(); ... } 

不,你有什么是不安全的。 检查_count >= MAXIMUM是否可以与另一个线程对Interlocked.Increment的调用竞争。 这实际上很难用低锁技术来解决。 为了使这个工作正常,你需要使一系列的几个操作在不使用锁的情况下出现primefaces。 这是困难的一部分。 这里所讨论的一系列操作是:

  • 阅读_count
  • testing_count >= MAXIMUM
  • 根据上述做出决定。
  • 增加_count取决于作出的决定。

如果你不把这些步骤中的所有4个看作primefaces,那么就会出现竞争状态。 执行复杂操作而不locking的标准模式如下。

 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--; } } 

请注意,这是更简单,并且相对较less的错误。 你可能实际上发现这种方法(硬锁)实际上比我上面显示的(低锁)更快。 再次,原因是棘手的,有技术可以用来加快速度,但它超出了这个答案的范围。


1在这种情况下,ABA问题并不是真正的问题,因为逻辑不依赖于_count保持不变。 不pipe发生在两者之间的情况如何,它的价值在两个时间点都是一样的。 换句话说,这个问题可以归结为一个看起来似乎没有改变的价值,尽pipe它在现实中可能有所改变。

定义线程安全。

如果你想确保_count永远不会超过MAXIMUM,那么你就没有成功。

你应该做的就是locking这个:

 private int _count; private object locker = new object(); public void CheckForWork() { lock(locker) { if (_count >= MAXIMUM) return; _count++; } Task.Run(() => Work()); } public void CompletedWorkHandler() { lock(locker) { _count--; } ... } 

你可能也想看看The SemaphoreSlim类。

如果您不想locking或移动到信号量,则可以执行以下操作:

 if (_count >= MAXIMUM) return; // not necessary but handy as early return if(Interlocked.Increment(ref _count)>=MAXIMUM+1) { Interlocked.Decrement(ref _count);//restore old value return; } Task.Run(() => Work()); 

递增返回增加的值,你可以再次检查_count是否小于最大值,如果testing失败,那么我恢复旧值

Interesting Posts