【问题标题】:Do i need to lock a bool when i'm reading/writing it in two consecutive statements?当我在两个连续的语句中读/写它时,我是否需要锁定一个布尔值?
【发布时间】:2016-11-17 12:10:53
【问题描述】:

我想知道这是否是线程安全的,或者(如果不是)我如何使其安全。它从计时器中调用:

private volatile bool _isSynchronizing;

private void SynchronizeSessionCache(object state = null)
{
    if (_isSynchronizing)
    {
        Log.Warn($"Aborted synchronization of SessionCache with SessionManager because we are already synchronizing. Interval is: {SynchronizationInterval}");
        return;
    }

     _isSynchronizing = true;

    bool lockWasTaken = false;
    try
    {
        // some code  that doesn't need a lock ...
        // ...

        // lock this part
        Monitor.Enter(_lockObject, ref lockWasTaken);
        // main code ...
    } 
    finally  // omitted catch
    {
        _isSynchronizing = false; 
        if(lockWasTaken)
            Monitor.Exit(_lockObject);
    }
}

我担心的是,一个线程可能会在他的方法开始时检查_isSynchronizing,而此时它是false。然后另一个线程进入主体,因为它还没有被thread1设置为true。即使_isSynchronizingvolatile,这可能吗?如果是这样,使此方法线程安全的最佳方法是什么?

如果我理解正确volatile 不能防止这种竞争条件,只能确保变量没有被缓存,所以所有线程总是读取当前值。

【问题讨论】:

  • 是的,两个胎面都有可能通过第一次检查。但是在方法内部你有一个锁,所以你希望多个线程进入,那是什么想法?
  • @Evk:我不希望有多个线程进入。 _lockObject 也用于其他需要访问共享资源的方法和事件。这必须在同步期间锁定。绝不应该发生多个线程同时进入的情况。这就是我使用_isSynchronizing 标志的原因。
  • 那么我就在这里使用双重检查锁定。
  • _isSynchronizing 设为int,然后写入if (Interlocked.CompareExchange(ref _isSynchronizing, 1, 0) != 0)。没有锁就解决了问题。
  • volatile 确保始终在 C# 中读取最新值的想法是错误的,所以不要相信它。 C# 规范仔细描述了 volatile 的实际含义,即写入 volatile 变量的效果如何相对于其他读取和写入重新排序是有限制的。正如我多次说过的,你应该阅读这篇文章,看看你是否理解如何重新排序易失性访问:blog.coverity.com/2014/03/26/reordering-optimizations

标签: c# .net multithreading locking


【解决方案1】:

为确保线程安全,您必须对单个原子操作进行比较和分配。否则,当第一个线程执行分配时,另一个线程总是有可能通过比较。 volatile 关键字在这里没有帮助,因为它告诉编译器(和运行时)不允许任何优化,这可能会改变这个变量所涉及的读写操作的顺序。

(有关 volatile 的更多信息,您可以在 Eric Lippert 的精彩文章 this 中阅读。)

简单(稍微慢一点)的解决方案是围绕比较和分配设置一个关键部分:

lock (_someLockObject)
{
    if (_isSynchronizing)
    {
         return;
    }

   _isSynchronizing = true;    
}

但是有一个更快的解决方案,但不适用于bool 变量。对于int,您可以使用Interlocked.CompareExchange() 方法。

假设int _isSynchronizing = 0 表示false_isSynchronizing = 1 表示true

那么你可以使用这个语句:

if (Interlocked.CompareExchange(ref _isSynchronizing, 1, 0) == 1)
{
    // If the original val == 1, we're already synchronizing
    return;
}

这比使用Monitor 稍快,但没有bool 过载。

【讨论】:

  • 为了清楚起见。使用Interlocked.Decrement(ref _isSynchronizing); 或直接赋值为零而不带锁等是正确的
猜你喜欢
  • 1970-01-01
  • 2018-04-28
  • 1970-01-01
  • 1970-01-01
  • 2021-11-25
  • 1970-01-01
  • 2014-10-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多