首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >如何在.NET中冻结popsicle(使一个类不可变)?

如何在.NET中冻结popsicle(使一个类不可变)?
EN

Stack Overflow用户
提问于 2018-03-16 05:33:37
回答 2查看 0关注 0票数 0

我正在设计一个我希望在主线程完成配置后只读的类,即“冻结”它。Eric Lippert称这种popsicle不变性。它被冻结后,可以被多个线程同时访问以供读取。

我的问题是如何在一个线程安全的方式编写这个现实有效的,即没有试图成为不必要的聪明

尝试1:

代码语言:txt
复制
public class Foobar
{
   private Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   // Only intended to be called by main thread, so checks if class is frozen. If it is the operation is invalid.
   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }

   public Object ReadSomething()
   {
      return it;
   }
}

Eric Lippert似乎认为这将是好的在这篇文章中。我知道写有释放语义,但据我了解,这只与排序有关,并不一定意味着所有线程在写入后立即看到该值。任何人都可以确认吗?这意味着这个解决方案不是线程安全的(当然这可能不是唯一的原因)。

尝试2:

以上,但Interlocked.Exchange用于确保值实际上已发布:

代码语言:txt
复制
public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (_isFrozen == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

这里的优势在于我们确保价值在每次阅读时都不会遭受开销而发布。如果在写入_isFrozen之前没有任何读取被移动,因为Interlocked方法使用了完整的内存屏障,我想这是线程安全的。但是,谁知道编译器会做什么(并且根据C#规范的第3.10节,似乎相当多),所以我不知道这是否是线程安全的。

尝试3:

也请阅读使用Interlocked

代码语言:txt
复制
public class Foobar
{
   private Int32 _isFrozen;

   public void Freeze() { Interlocked.Exchange(ref _isFrozen, 1); }

   public void WriteValue(Object val)
   {
      if (Interlocked.CompareExchange(ref _isFrozen, 0, 0) == 1)
         throw new InvalidOperationException();

      // write ...
   }
}

绝对是线程安全的,但每次读取都必须进行比较交换似乎有点浪费。我知道这个开销可能是最小的,但我正在寻找一个相当有效的方法(尽管也许就是这样)。

尝试4:

使用volatile

代码语言:txt
复制
public class Foobar
{
   private volatile Boolean _isFrozen;

   public void Freeze() { _isFrozen = true; }

   public void WriteValue(Object val)
   {
      if (_isFrozen)
         throw new InvalidOperationException();

      // write ...
   }
}

有人宣称“ sayon​​ara易变 ”,所以我不会认为这是一个解决方案。

尝试5:

锁定一切,似乎有点矫枉过正:

代码语言:txt
复制
public class Foobar
{
   private readonly Object _syncRoot = new Object();
   private Boolean _isFrozen;

   public void Freeze() { lock(_syncRoot) _isFrozen = true; }

   public void WriteValue(Object val)
   {
      lock(_syncRoot) // as above we could include an attempt that reads *without* this lock
         if (_isFrozen)
            throw new InvalidOperationException();

      // write ...
   }
}

也似乎绝对是线程安全的,但比使用上面的Interlocked方法有更多的开销,所以我会优先考虑尝试3。

然后我至少可以再想一些(我相信还有更多):

尝试6:使用Thread.VolatileWriteThread.VolatileRead,但这些都是沉重的一面。

尝试7:使用Thread.MemoryBarrier,似乎有点太内在

尝试8:创建一个不可变的副本 - 不想这样做

总结:

  • 你会使用哪种尝试以及为什么(或者如果完全不同,你会怎么做)?(即一次发布价值的最佳方式是什么,然后再同时阅读,而相当高效却不过分“聪明”?)
  • .NET的内存模型“释放”写入语义意味着所有其他线程都看到更新(缓存一致性等)?我通常不想过多考虑这个问题,但理解起来很好。

也许我的问题并不清楚,但我特别关注上述尝试为何好或坏的原因。请注意,我在这里谈论的是一个单一写入器的情况,写入然后在任何并发读取之前冻结。我相信尝试1是好的,但我想知道为什么(例如,我想知道读取是否可以通过某种方式进行优化)。我不在乎这是否是好的设计实践,而是关于它的实际线程方面。

非常感谢所收到问题的答复,但我选择将其标记为自己的答案,因为我觉得所给出的答案不能完全回答我的问题,我不想给访问该网站的任何人留下印记答案是正确的,因为它会自动标记为这样,因为赏金到期。此外,我不认为拥有最高票数的答案被压倒性地投了票,不足以自动将其标记为答案。

我仍然倾向于尝试#1是正确的,但是,我会喜欢一些权威的答案。我知道x86有一个强大的模型,但我不想(也不应该)为特定的体系结构编写代码,毕竟这是关于.NET的好东西之一。

如果对答案有疑问,请参阅其中一种锁定方法,或许可以通过此处显示的优化来避免锁的争用。

EN

回答 2

Stack Overflow用户

发布于 2018-03-16 13:55:18

也许有些话题,但只是出于好奇:)为什么你不使用“真正的”不变性?例如,使Freeze()返回一个不可变的副本(没有“写入方法”或任何其他可能性来更改内部状态),并使用此副本而不是原始对象。甚至可以在不更改状态的情况下进行操作,并在每次写入操作时返回新的副本(使用更改后的状态)(afaik字符串类用于此操作)。“真正的不变性”本质上是线程安全的。

票数 0
EN

Stack Overflow用户

发布于 2018-03-16 15:18:40

我投了尝试5,使用锁(这个)实现。

这是做这项工作最可靠的手段。可以使用读卡器/写卡器锁,但获得很少的收益。只需要使用正常的锁。

如有必要,可以通过先检查_isFrozen然后锁定来提高“冻结”性能:

代码语言:txt
复制
void Freeze() { lock (this) _isFrozen = true; }
object ReadValue()
{
    if (_isFrozen)
        return Read();
    else
        lock (this) return Read();
}
void WriteValue(object value)
{
    lock (this)
    {
        if (_isFrozen) throw new InvalidOperationException();
        Write(value);
    }
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/-100003191

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档