在这些简单的情况下使用c#锁有用吗?



在多线程环境中,锁定线程敏感资源非常重要。我经常假设集合之类的是线程不安全的,这取决于MS文档,但是简单类型也是线程敏感的吗?

让我们举个例子。锁定int属性访问是否有用,例如

public int SomeProperty
{
get
{
lock (_lock)
{
return _value;
}
}
}

或者是一个普通的getter就足够了,即

public int SomeProperty => _value;

在我的理解中,一个简单的字段读取是线程安全的,但我仍然在网上和一些代码库中看到第一个例子。

第二个问题,单行指令中的值是顺序读取还是同时读取?换句话说,当我执行

时是否需要锁定
public TimeSpan GetSomeExampleValue()
{
lock (_lock)
{
return _dateTime1 - _dateTime2;
}
}

或者直接写

public TimeSpan GetSomeExampleValue()
{
return _dateTime1 - _dateTime2;
}

注意:这里的所有内容都基于有效锁定;lockint(在第一个例子中是lock (_value))是一个非常糟糕的事情并且提供零保护;别这样!编译器可能已经对你大喊大叫了(你应该只锁定引用类型,否则它每次都装箱,给你一个不同的对象,因此每次都有不同的锁)。

据我所知,一个简单的字段读取是线程安全的,

实际情况要复杂得多。首先,您需要定义线程安全!您可能指的是三种不同的场景:

  1. 避免"torn"Values——一个不一致的单一值,在写操作时读取,并且有一个混乱的状态,(通常)一半来自前后值,一半来自另一个——创建第三个虚幻值,逻辑上不存在;这里,我们需要考虑"原子性"。c#语言定义了永远是原子安全的类型,包括int和引用,但是不包括DateTime;因此,int示例在这里是安全的,但DateTime值不是(然而,在实践中,在64位进程中,您应该可以使用64位结构体,但是:c#规范不能保证这一点-它是CLR规范中提到的)
  2. 避免多个值之间的不一致状态;再考虑一下DateTime的值——即使我们不删除任何东西,我们也可以从更新的一边获得_dateTime1,从另一边获得_dateTime2,这意味着我们返回的TimeSpan是一个虚幻的值,它不代表两个字段
  3. 的任何一致的逻辑状态。
  4. 内部CPU优化,如乱序读/写,这意味着我们得到非常非常奇怪的结果
  5. 现在

  • 在没有lock的情况下,int仍然对撕裂免疫,但对其他撕裂敏感;DateTime对这三种
  • 都很敏感
  • lock,两者都完全防范所有三个

您可能也希望将不变性视为简化逻辑的一种方式,但是请记住,readonly是一个虚例——更多的是指南而不是实际的规则

最新更新