C# 类成员枚举是线程安全的吗?
以以下为例C# 类成员枚举是线程安全的吗?,c#,multithreading,C#,Multithreading,以以下为例 public class MyClass { private MyEnum _sharedEnumVal { get; set; } } 如果MyClass中的方法在不同的线程上运行并读取/更新了_sharedEnumVal,那么我说的锁或其他机制是否正确,以保持变量线程像其他原语一样安全,或者枚举是否特殊 谢谢我不明白为什么这个话题被否决:)。 这里有一些好的观点,也有一些坏的想法,甚至有一些人投了赞成票! 那么,让我们对这些片段进行排序 这里的问题实际上是关于原子性
public class MyClass
{
private MyEnum _sharedEnumVal { get; set; }
}
如果MyClass中的方法在不同的线程上运行并读取/更新了_sharedEnumVal,那么我说的锁或其他机制是否正确,以保持变量线程像其他原语一样安全,或者枚举是否特殊
谢谢我不明白为什么这个话题被否决:)。 这里有一些好的观点,也有一些坏的想法,甚至有一些人投了赞成票! 那么,让我们对这些片段进行排序 这里的问题实际上是关于原子性的。 如果操作是原子的,那么它本质上是线程安全的,对于某些操作(如读/写操作)以及由于给定类型的联锁类而允许的其他操作,它没有锁定 现在,.Net声明int读/写是原子的。对于所有适合32位的类型都一样,64位类型不是原子的!对象引用的读/写也是原子的 有些操作是原子操作,有些则不是,比如increment,除非调用Interlocked.increment 现在我为什么要谈论int?默认情况下,枚举的类型为int,32位,除非另有明确规定 这意味着读/写是原子的=>线程安全的 顺便说一句,保留一个裸属性通常是一个坏主意,我宁愿在属性后面使用变量并使用变量,因为有必要使用互锁方法 有许多有用的方法,原子性足以保证在没有锁定的情况下使用。例如后台线程状态。或允许后台工作人员工作的属性,直到它更改为某个预期值,为后台工作人员提供停止的信息等 此外,Interlocked类还扩展了这些场景,用于共享迭代变量等 正如Chris Hannon所指出的,简单的读/写操作可能会导致过时,因为数据不会被更新,除非特定的读/写操作将由内存屏障修饰,或者将使用联锁操作,联锁.Add for read,interlocated.CompareExchange for write,其中缓存将被更新。
多亏了克里斯,我错过了好机会 线程安全是一个棘手的问题。对枚举的更新始终是原子的。因此,即使数千个线程试图同时更新同一个枚举,也永远不会得到无效的、半更新的枚举值。该值本身将始终有效。但即使在更新枚举时,也无法保证其他线程会由于多个核心之间的缓存不一致而读取“最新”值。为了确保所有核心都同步,您需要一个内存屏障 但即使这样也不能保证线程安全,因为数据竞争仍然可能发生。假设你在课堂上有这样的逻辑:
public void DoSomething()
{
if (_sharedEnumVal == MyEnum.First) {
DoPrettyThings();
} else {
DoUglyThings();
}
}
public void UpdateValue(MyEnum newValue)
{
_sharedEnumVal = newValue;
}
您有两种不同的线程:
static MyClass threadSafeClass = new MyClass();
void ThreadOne()
{
while (true)
{
threadSafeClass.UpdateValue(MyEnum.Second);
DoSomething();
}
}
void ThreadTwo()
{
while (true)
{
threadSafeClass.UpdateValue(MyEnum.First);
DoSomething();
}
}
在这里,尽管对枚举的更新是原子性的,但两个线程将“竞相”更改和使用枚举值以实现自己的目的,并且当调用DoSomething时,无法保证枚举将具有什么值。你会得到完全出乎意料的结果。Thread2可能会导致美好的事情,ThreadOne可能会导致丑陋的事情发生,这与预期正好相反
在这种情况下,您仍然需要锁定以确保类行为的线程安全。什么使您认为它是线程安全的?下面只是一个原语。枚举只是覆盖下的整数。你需要一个锁。这里有一篇关于线程安全枚举@CraigW.的好文章,但并不总是这样。只是Int32 by defaultThread安全性是代码的属性,而不是变量。枚举的唯一保证是它是原子的,这还远远不够。访问MyClass对象的代码必须在必要时仲裁访问。当线程同时读取和写入该属性时,这是必要的。原子性本身几乎从来都不是“足够好的保证”,而且您的两个“足够好”的示例都存在过时数据的问题。线程1正在执行工作,监视共享变量X。线程2将变量X更改为预期值。线程1将永远继续工作,因为它没有充分的理由返回主内存刷新其值,而是继续依赖其过时的缓存版本。代码现在已被破坏(可能!有时!在某些情况下!),而且永远都不够“好”。您注意到了吗,我也特别提到了联锁吗?是的,简单的读/写对于stale是危险的,除非它会被内存屏障修饰,但联锁操作不是,它们会更新缓存。例如,Add可以读取变量,如果它加零,则返回变量的原始最后一个值。CompareExchange可用于设置请求的值。所以它是原子的,同时也没有陈旧的问题。仍然比锁好得多,但可以肯定的是,这一切都是关于知道自己在做什么。顺便说一句,谢谢你的好意,我错过了。好答案:-)。在这方面,我还要提到
volatile
和Interlocked.MemoryBarrier()
,它们也可以在不锁定的情况下完成这项任务。