锁定bool值的访问是必需的还是过度设计?

13

我有一个类,这个类被设计为一个POCO类,各种线程和任务可以读取其值,而其他人只偶尔更新这些值。这似乎是ReaderWriterLockSlim的理想应用场景。

问题在于,如果需要保证线程安全的属性是bool类型,是否过度使用?如果是int或DateTime类型会发生什么?

public class MyClass
{
  private bool _theValue = false;
  private ReaderWriterLockSlim _theValueLock = new ReaderWriterLockSlim();

  public bool TheValue
  {
    get
    {
      bool returnVal = false;
      try
      {
        _theValueLock.EnterReadLock();
        returnVal = _theValue;
      }
      finally
      { _theValueLock.ExitReadLock(); }
      return returnVal;
    }
    set
    {
      try
      {
        _theValueLock.EnterWriteLock();
        _theValue = value;
      }
      finally
      { _theValueLock.ExitWriteLock(); }
    }
  }
}

这段代码是否过度设计,而一个简单的解决方案会更好呢?

public bool TheValue { get; set; }

...足够吗?由于类型是bool,所以安全吗?如果是,什么时候会变得不安全?byte?int?DateTime?

编辑
我的基本架构是将此类存储状态。可能有一个服务负责对此类进行写操作。所有其他类都可以读取并根据此状态数据执行其逻辑。我会尽力确保所有数据一致,但如下所述,我的主要关注点是数据的原子性和分裂。

结论
感谢大家的回复,所有回复都很有价值。我的主要关注点是写入/读取的原子性(即担心分裂)。对于.NET平台,如果问题变量是小于4个字节的内置值类型,则读取和写入是原子性的(例如,short和int是可以的,long和double则不是)。


1
不保护它会导致非常严重的低效。如果不展示属性的使用方式,就无法得到准确的答案。仅在属性级别上进行锁定很少有效。 - Hans Passant
1
以下是与编程有关的内容。这些链接仅供我参考:链接多线程最佳实践;链接原子性、易失性等概念,感谢@brian;链接原子性的简介,感谢@reed。 - Andre
作为个人笔记,听了dnr.tv和jon skeet关于锁定bools的评论后,我查阅了这个链接http://askjonskeet.com/answer/6873994/Boolean-Property-Getter-and-Setter-Locking,基本上说,在多线程场景下,锁定它可能是一个好主意。 - Andre
3个回答

8

根据使用方式的不同,您可能需要标记布尔值为volatile。这将需要一个备份字段来支持您的属性。

您现在不需要像使用ReaderWriterLockSlim那样处理它,因为它小于32位(假设您正在使用AutoLayout,有关详细信息,请参见此文章或ECMA 335规范中标题为Memory Accesses的原子性的部分以获得最详细的信息)。如果您使用的类型大于此,则需要某种形式的同步。

我建议:

public class MyClass
{
    private volatile bool _theValue = false;
    public bool TheValue 
    {
        get { return _theValue; } 
        set { _theValue = value; } 
    }
 }

1
正如您所指出的那样,对于bool类型的访问已经是原子的了。但是volatile并不能提供任何同步或线程安全性。 - user7116
1
@sixlettervariables:没错,但是OP的解决方案只锁定了一个属性,这对于保证原子性非常有用——原始解决方案中没有同步或线程安全。在这种情况下,这是完全可以接受的替代品。 - Reed Copsey
很好。虽然他最初使用锁的解决方案可以使用RWLS提供线程安全访问。 - user7116
根据Eric的说法,volatile字段是一个不好的主意,他更喜欢使用lock:https://blogs.msdn.microsoft.com/ericlippert/2011/06/16/atomicity-volatility-and-immutability-are-different-part-three/(“我不鼓励您制作volatile字段。”) - Tim Schmelter

5

没有任何类型是真正安全的!

更准确地说,C#规范保证读取或分配小于4字节或引用的结构类型是原子的。如果您的操作系统是64位的,则CLR可以在小于8字节的结构体上做得更好。

但是,任何比赋值或读取值更复杂的操作都有可能被另一个竞争线程中断,如果您不小心的话。

即使像这样简单的操作:

myBool = !myBool

如果竞争线程修改了myBool的值,您可能会得到意外的结果。如果您希望确保不会发生这样的情况,请建议使用锁。强烈不建议使用volatile关键字,除非您确切知道自己在做什么。有关详细信息,请查看这些博客文章:这里这里这里
但是,在您的示例中,如果属性只进行一次写入或一次读取,则锁定是无用的。但如果存在任何其他处理,锁定就是必要的。

1
强调你的例子。在你的例子中,你同时进行读写操作,而myBool = true只是写操作。因此,volatile对于myBool = !myBool是不够的。 - liorafar

4
你有几个选择。
- 什么也不做。 - 使类变为不可变的。 - 使用简单的锁。 - 将字段标记为"volatile"。 - 使用"Interlocked"一组方法。
由于对布尔值的读写被保证是原子性的,所以您可能不需要进行任何操作。这将非常依赖于类的使用方式的性质。原子性并不等于线程安全。它只是其中的一个方面。
理想的解决方案是使您的类成为不可变的。不可变类通常是线程安全的,因为它们不能由其他线程(或者根本不能)修改。但有时这是不可行的。
我接下来喜欢的是简单的锁。获得和释放锁的开销非常小。实际上,我认为你会发现,在大多数情况下,锁比"ReaderWriterLockSlim"更快,特别是如果你只是读/写一个变量。我的个人测试表明,RWLS的开销约为锁的5倍。因此,除非读操作异常地长,并且它们明显超过写操作,否则RWLS将无法帮助。
如果您担心锁的开销,那么请将字段标记为"volatile"。只需记住,"volatile"不是解决所有并发问题的神奇解药。它不适用于取代锁。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接