12

我有一个主要设计为 POCO 类的类,各种线程和任务可以读取它的值,只有其他人只是偶尔更新这些值。这似乎是 ReaderWriterLockSlim 的理想方案。

问题是,在类中,如果需要线程安全的属性,如果该属性是布尔值,这是否矫枉过正?如果它是一个 int 会发生什么?约会时间?

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

……就够了吗?因为Type是bool,安全吗?如果是这样,什么时候变得不安全?字节?诠释?约会时间?

编辑
我的基本架构是拥有此类存储状态。也许有一项服务负责对此类进行写入。所有其他类都可以根据此状态数据读取并执行其逻辑。我会尽我所能确保所有数据是一致的,但如下所述,我主要关心的是数据的原子性和分割。

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

4

3 回答 3

8

根据使用方式,您可能需要标记布尔值volatile。这将需要您的财产的支持字段。

您不需要ReaderWriterLockSlim像现在这样处理这个问题,因为它小于 32 位(假设您使用的是 AutoLayout,有关详细信息,请参阅这篇文章,或者更详细地,请参阅标题为内存访问的原子性的部分在 ECMA 335 规范中)。如果您使用的类型大于此值,则需要某种形式的同步。

我会推荐:

public class MyClass
{
    private volatile bool _theValue = false;
    public bool TheValue 
    {
        get { return _theValue; } 
        set { _theValue = value; } 
    }
 }
于 2011-06-22T00:00:03.983 回答
4

你有几个选择。

  • 没做什么。
  • 使类不可变。
  • 使用普通的旧lock.
  • 将字段标记为volatile
  • 使用Interlocked方法集。

由于对 a 的读写bool保证是原子的,因此您可能不需要做任何事情。这在很大程度上取决于您的课程将被使用的方式的性质。原子性不等于线程安全。这只是其中的一方面。

理想的解决方案是让你的类不可变。不可变类通常是线程安全的,因为它们不能被其他线程修改(或者根本就不能修改)。有时这只是不可行。

我在列表中的下一个偏好是普通的旧lock. 获取和释放的开销非常小。事实上,我认为您会发现在大多数情况下 alock会胜过 a ReaderWriterLockSlim,尤其是当您所做的只是读取/写入变量时。我自己的个人测试表明,RWLS 的开销lock. 因此,除非读取操作异常长并且它们的数量大大超过写入操作,否则 RWLS 将无济于事。

如果您担心锁定开销,请务必将字段标记为volatile. 请记住,这volatile不是解决所有并发问题的灵丹妙药。它并非旨在替代lock.

于 2011-06-22T01:40:10.647 回答
4

没有类型是真正安全的!更准确地说,C# 规范向您保证读取或分配少于 4 个字节的结构类型或引用是原子的。如果您的操作系统是 64 位,则 CLR 通过确保对小于 8 字节的结构进行同样的处理来做得更好。

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

甚至像这样简单的事情:

myBool = !myBool

如果竞争线程修改 myBool 的值,可能会得到意想不到的结果。

如果您想确保不会发生类似的事情,建议使用锁。除非您确切知道自己在做什么,否则强烈建议不要使用 volatile 关键字。查看这些 博客 文章以获取更多信息。

但是,在您的示例中,该属性除了单次写入或单次读取之外不执行任何操作,锁定是无用的。但如果有任何额外的治疗,那就不会了。

于 2011-06-22T00:08:25.697 回答