合并 Interlocked.Increment 和 Volatile.Write
Combining Interlocked.Increment and Volatile.Write
我一直在查看 System.Reactive (here) 的源代码,它把我带到了这个地方,后面有一个 Volatile.Read
通过 Interlocked.CompareExchange
,在同一个变量上:
if (Volatile.Read(ref _runDrainOnce) == 0
&& Interlocked.CompareExchange(ref _runDrainOnce, 1, 0) == 0)
{
//do something
}
正如我所读,其逻辑是“如果 runDrainOnce 为 0,并且在我将其更改为 1 之前它为零”。这里有什么微妙之处吗?为什么第一次检查不是多余的?
(更不可思议的是,同一个函数中有一个lock
和一个Monitor.Pulse
,这是打赌的结果吗?:))
整个函数:
private void Schedule()
{
// Schedule the suspending drain once
if (Volatile.Read(ref _runDrainOnce) == 0
&& Interlocked.CompareExchange(ref _runDrainOnce, 1, 0) == 0)
{
_drainTask.Disposable = _scheduler.ScheduleLongRunning(this, DrainLongRunning);
}
// Indicate more work is to be done by the drain loop
if (Interlocked.Increment(ref _wip) == 1L)
{
// resume the drain loop waiting on the guard
lock (_suspendGuard)
{
Monitor.Pulse(_suspendGuard);
}
}
}
接下来是完全推测的可能性。实际上,我会说 没有明确的基准测试 ,它可能 无关紧要 ,我们可以简单地失去 Volatile.Read
测试,甚至只使用 lock
。如果 是 明确的基准测试,我希望对此有评论提示(或链接)。
我们可以从命名 (_runDrainOnce
) 中推断出我们只希望它 成功 一次,如果某件事只会成功一次,我们真的不会' 需要成功案例是超级最优的 - 所以:在成功路径中进行冗余测试:不是一个大问题。相比之下,让我们推测 失败 场景被调用 很多次 ,所以它失败 just 进行 acquire-fenced 读取(不尝试写入)可能 有益。
Schedule
代码由 everything 调用 - 参见 OnCompleted
、OnError
、OnNext
等 - 据推测目的只是确保调度 尽可能高效地开始 - 所以它不会触及 Intelocked
超过必要的(一旦成功,可能有一些时间表明
失败,如果最初存在高线程竞争)
您没有明确询问,但是 lock
/Pulse
是使用 Monitor
让空闲工作循环等待接收工作的常见模式;你需要唤醒它 if 它可能处于空闲状态,这是当计数 为 零并且现在为 non-zero (因此 Interlocked.Increment
).
我一直在查看 System.Reactive (here) 的源代码,它把我带到了这个地方,后面有一个 Volatile.Read
通过 Interlocked.CompareExchange
,在同一个变量上:
if (Volatile.Read(ref _runDrainOnce) == 0
&& Interlocked.CompareExchange(ref _runDrainOnce, 1, 0) == 0)
{
//do something
}
正如我所读,其逻辑是“如果 runDrainOnce 为 0,并且在我将其更改为 1 之前它为零”。这里有什么微妙之处吗?为什么第一次检查不是多余的?
(更不可思议的是,同一个函数中有一个lock
和一个Monitor.Pulse
,这是打赌的结果吗?:))
整个函数:
private void Schedule()
{
// Schedule the suspending drain once
if (Volatile.Read(ref _runDrainOnce) == 0
&& Interlocked.CompareExchange(ref _runDrainOnce, 1, 0) == 0)
{
_drainTask.Disposable = _scheduler.ScheduleLongRunning(this, DrainLongRunning);
}
// Indicate more work is to be done by the drain loop
if (Interlocked.Increment(ref _wip) == 1L)
{
// resume the drain loop waiting on the guard
lock (_suspendGuard)
{
Monitor.Pulse(_suspendGuard);
}
}
}
接下来是完全推测的可能性。实际上,我会说 没有明确的基准测试 ,它可能 无关紧要 ,我们可以简单地失去 Volatile.Read
测试,甚至只使用 lock
。如果 是 明确的基准测试,我希望对此有评论提示(或链接)。
我们可以从命名 (_runDrainOnce
) 中推断出我们只希望它 成功 一次,如果某件事只会成功一次,我们真的不会' 需要成功案例是超级最优的 - 所以:在成功路径中进行冗余测试:不是一个大问题。相比之下,让我们推测 失败 场景被调用 很多次 ,所以它失败 just 进行 acquire-fenced 读取(不尝试写入)可能 有益。
Schedule
代码由 everything 调用 - 参见 OnCompleted
、OnError
、OnNext
等 - 据推测目的只是确保调度 尽可能高效地开始 - 所以它不会触及 Intelocked
超过必要的(一旦成功,可能有一些时间表明
失败,如果最初存在高线程竞争)
您没有明确询问,但是 lock
/Pulse
是使用 Monitor
让空闲工作循环等待接收工作的常见模式;你需要唤醒它 if 它可能处于空闲状态,这是当计数 为 零并且现在为 non-zero (因此 Interlocked.Increment
).