使用Interlocked.CompareExchange,下面的示例是否需要易失性和/或内存屏障?

时间:2014-01-27 16:49:05

标签: .net multithreading

请观察:

private DateTime m_lastTimeUtc;
private int m_isInProgress;
...
if (DateTime.UtcNow - m_lastTimeUtc >= m_interval)
{
    if (Interlocked.CompareExchange(ref m_isInProgress, 1, 0) == 0)
    {
        if (DateTime.UtcNow - m_lastTimeUtc >= m_interval)
        {
            m_lastTimeUtc = DateTime.UtcNow;
            // Do the work
            m_lastTimeUtc = DateTime.UtcNow;
            Interlocked.Exchange(ref m_isInProgress, 0);
        }
    }
}

此代码的预期语义如下:

  • 可以经常调用代码 - 每秒数百次。
  • 应该或多或少地定期做某些工作。如果代码没有经常调用,那么实际的时间段可能非常不规则,这是可以的。但如果经常调用它(通常是这种情况),那么工作就会定期进行。
  • 点击m_isInProgress = 1的主题将跳过工作,完全没问题。

当然,环境是多线程的。

我注意到人们正在使用volatile关键字和/或在执行类似于我在此处所做的事情时调用Interlocked.MemoryBarrier()(或者至少在我看来这样做)。如果我告诉我理解它们是如何工作的,我会说谎。

有人可以在我的代码中解释我是否需要它们以及为什么?

P.S。

我在几个地方重复相同的模式。所以我想介绍以下方法:

private static void DoWork(TimeSpan interval, ref DateTime lastTimeUtc, ref int isInProgress, Action work)
{
    if (DateTime.UtcNow - lastTimeUtc >= interval && Interlocked.CompareExchange(ref isInProgress, 1, 0) == 0 && DateTime.UtcNow - lastTimeUtc >= interval)
    {
        lastTimeUtc = DateTime.UtcNow;
        work();
        lastTimeUtc = DateTime.UtcNow;
        Interlocked.Exchange(ref isInProgress, 0);
    }
}

鉴于这种方法,我打算如何调用它:

DoWork(m_interval, ref m_lastTimeUtc, ref m_isInProgress, () =>
{
  // Do the work
});

DoWork答案是否相同?

1 个答案:

答案 0 :(得分:1)

不,您不需要使用volatile或内存障碍,因为Interlocked.CompareExchange方法隐式生成完整的栅栏,这会阻止指令重新排序和变量缓存。

我建议阅读:

  1. http://www.albahari.com/threading/part4.aspx
  2. http://igoro.com/archive/volatile-keyword-in-c-memory-model-explained/