C#中的只读字段是否线程安全?

24

readonly在C#中的字段是线程安全的吗?

public class Foo
{
  private readonly int _someField;

  public Foo()
  {
    _someField = 0;
  }

  public Foo(int someField)
  {
    _someField = someField;
  }

  public void SomeMethod()
  {
     doSomething(_someField);
  }
}
< p >
浏览了一些帖子:

那么,如果像上面的代码一样使用readonly字段,并且构造函数比较轻量级,它是否是线程安全的? 如果_someField是引用类型(例如字符串数组)会怎样?


2
不取决于构造函数是否执行了很多工作,而是取决于构造函数是否允许实例逃逸并在此之后返回并再次更改只读字段。 - harold
1
只有引用是只读的,集合并不是。因此,如果您将列表放入只读字段中,则从列表中添加和删除元素将不具备线程安全性。 - albertjan
@the_ajp 是哪个集合?我在他发布的代码中看不到任何集合。 - Ben Robinson
1
@BenRobinson,他的问题最后一行没有任何疑问,但是他暗示了一个关于字符串数组的情况。因为我知道这与集合有关,所以我认为添加评论可能会很有帮助。 - albertjan
@Harold,感谢您的更正。是的,我的想法是不让实例逃逸。 - publicgk
3个回答

23

是的 - 您的代码在任何构造函数中都没有暴露this,因此在完全构造对象之前,其他任何代码都无法“看到”该对象。 根据.NET内存模型(截至.NET 2),每个构造函数结尾处都包括一个写入屏障(如果我没记错的话-请搜索Joe Duffy的博客文章以获取更多详细信息),因此就我所知,没有其他线程会看到“过时”的值。

个人而言,我仍然通常使用属性来将实现与API分离,但从线程安全的角度来看,这是可以接受的。


GUID不是一个例外吗,因为它有128位?如果使用64位字长,则需要进行2个缓存更新,请纠正我。 - Rohit Sharma
@RohitSharma:我不这么认为——我相信在构造函数完成之前,它保证完全可见,并且引用对调用代码可见。 - Jon Skeet
请纠正我,但是我认为对于值类型来说,语义是:它完全在单独的缓冲区中构建,然后一次性复制到分配给它的任何位置。那个副本对我来说听起来不太原子化。 - harold
@harold:如果在到达之前没有对其他任何东西可见,那么它就不需要是原子的,然后有一个写屏障,然后它就不会改变 - 因此,在外部读取的任何内容都将始终看到相同的值。 - Jon Skeet
是的,但它可能已经可见了(例如在结构体中的只读GUID字段而不是类中)。那么它就不是线程安全的,对吗? - harold
@harold:在这种情况下,我认为它超出了这个问题的范围。 - Jon Skeet

4

这取决于字段中包含的内容。

从只读字段或任何比字长小的字段(包括所有引用类型)中读取是原子操作。

但是,只读字段内部的对象可能是线程安全的,也可能不是。


1

不看你的例子,但一般而言,它取决于readonly应用于什么,例如如果字典被声明为readonly,则仍然可以更新键值对。


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