Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/295.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 类成员枚举是线程安全的吗?_C#_Multithreading - Fatal编程技术网

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()
,它们也可以在不锁定的情况下完成这项任务。