在那些简单的情况下使用C#锁有用吗?
c#
在多线程环境中,锁定对线程敏感的资源很重要。我经常假设集合等是线程不安全的,具体取决于 MS 文档,但简单类型是否也是线程敏感的?
让我们举个例子。锁定 int 属性访问是否有用,例如
public int SomeProperty
{
get
{
lock (_lock)
{
return _value;
}
}
}
或者是一个足够普通的吸气剂,即
public int SomeProperty => _value;
据我了解,一个简单的字段读取是线程安全的,但我仍然在网上和一些代码库中看到第一个例子。
第二个问题,单行指令中的值是顺序读取还是同时读取?换句话说,我这样做时是否需要锁定
public TimeSpan GetSomeExampleValue()
{
lock (_lock)
{
return _dateTime1 - _dateTime2;
}
}
或者我可以简单地做
public TimeSpan GetSomeExampleValue()
{
return _dateTime1 - _dateTime2;
}
回答
注意:这里的一切都基于有效锁定;lock反对int(lock (_value)在第一个例子中)是一件非常糟糕的事情并且提供零保护;不要那样做!编译器可能已经在向你尖叫(你应该只锁定引用类型,否则它每次都会装箱并给你一个不同的对象,因此每次都有不同的锁定)。
据我了解,一个简单的字段读取是线程安全的,
它比那复杂得多。首先,你需要定义线程安全!您可能指的是三种不同的场景:
- 避免“撕裂”的值-而写入是发生被读取并且具有错位的状态是(通常)从所述一个半值之后/之前,另一半来自其它不一致的单个值-创建第三假想值那从来没有逻辑上存在过;在这里,我们需要考虑“原子性”——C# 语言定义的类型总是原子安全的,包括
int和引用,但不包括DateTime; 因此,int示例在这里是安全的,但DateTime值不是(但是,在实践中,在 64 位进程中,您应该可以使用高达 64 位的结构,但是:C# 规范不保证这一点 - 它虽然在 CLR 规范中提到了) - 避免多个值之间的不一致状态;再次考虑
DateTime值 - 即使我们不撕任何东西,我们也可以_dateTime1从更新的一侧和_dateTime2另一侧获取,这意味着TimeSpan我们返回的是一个幻像值,不代表两个字段的任何一致的逻辑状态 - 内部 CPU 优化,例如乱序读/写,这意味着我们会得到非常奇怪的结果
现在
- 没有
lock,int仍然不会撕裂,但容易受到其他人的影响;DateTime对这三种情况都很敏感 - 与
lock,两者都完全防范所有三个
您可能还希望将不变性视为一种简化逻辑的方式,但请记住,这readonly是一个谎言 -与其说是实际规则,不如说是指导方针。