简体   繁体   English

C#中的易失性字段

[英]Volatile fields in C#

From the specification 10.5.3 Volatile fields: 从规范10.5.3挥发性字段:


The type of a volatile field must be one of the following: volatile字段的类型必须是以下之一:

  • A reference-type. 引用类型。

  • The type byte, sbyte, short, ushort, int, uint, char, float, bool, System.IntPtr, or System.UIntPtr. 类型byte,sbyte,short,ushort,int,uint,char,float,bool,System.IntPtr或System.UIntPtr。

  • An enum-type having an enum base type of byte, sbyte, short, ushort, int, or uint. 具有枚举基类型byte,sbyte,short,ushort,int或uint的枚举类型。


First I want to confirm my understanding is correct: I guess the above types can be volatile because they are stored as a 4-bytes unit in memory(for reference types because of its address), which guarantees the read/write operation is atomic. 首先,我想确认我的理解是正确的:我猜上面的类型可能是易失性的,因为它们作为4字节单元存储在内存中(因为它的地址对于引用类型),这保证了读/写操作是原子的。 A double/long/etc type can't be volatile because they are not atomic reading/writing since they are more than 4 bytes in memory. double / long / etc类型不能是volatile,因为它们不是原子读/写,因为它们在内存中超过4个字节。 Is my understanding correct? 我的理解是否正确?

And the second, if the first guess is correct, why a user defined struct with only one int field in it(or something similar, 4 bytes is ok) can't be volatile? 第二个,如果第一个猜测是正确的,为什么用户定义的结构只有一个int字段(或类似的东西,4个字节是可以的)不能是volatile? Theoretically it's atomic right? 理论上它是原子的吗? Or it's not allowed simply because that all user defined structs(which is possibly more than 4 bytes) are not allowed to volatile by design ? 或者仅仅是因为所有用户定义的结构(可能超过4个字节)不允许设计的易失性?

Basically, usage of the volatile keyword can sometimes be misleading. 基本上,使用volatile关键字有时会产生误导。 Its purpose is to allow that the latest value (or actually, an eventually fresh enough value) 1 of the respective member is returned when accessed by any thread. 其目的是允许在任何线程访问时返回相应成员的最新值 (或实际上,最终足够新鲜的值) 1

In fact, this is true to value types only 2 . 实际上,这仅适用于值类型 2 Reference type members are represented in memory as the pointers to a location in the heap where the object is actually stored. 引用类型成员在内存中表示为指向堆中实际存储对象的位置的指针。 So, when used on a reference type, volatile ensures you only get the fresh value of the reference (the pointer) to the object, not the object itself. 因此,当在引用类型上使用时, volatile确保您只获取对象的引用(指针)的新值,而不是对象本身。

If you have a volatile List<String> myVolatileList which is modified by multiple threads by having elements added or removed, and if you expect it to be safely accessing the latest modification of the list, you are actually wrong . 如果你有一个volatile List<String> myVolatileList ,它由多个线程通过添加或删除元素来修改 ,并且如果你希望它安全地访问列表的最新修改,那么你实际上是错的 In fact, you are prone to the same issues as if the volatile keyword was not there -- race conditions and/or having the object instance corrupted -- it does not assist you in this case, neither it provides you with any thread safety. 事实上,你容易出现同样的问题,就好像volatile不存在 - 竞争条件和/或对象实例已损坏 - 在这种情况下它不会帮助你,它既不会为你提供任何线程安全性。

If, however, the list itself is not modified by the different threads, but rather, each thread would only assign a different instance to the field (meaning the list is behaving like an immutable object), then you are fine. 但是,如果列表本身没有被不同的线程修改,而是每个线程只会为该字段分配一个不同的实例(意味着列表的行为类似于不可变对象),那么你没事。 Here is an example: 这是一个例子:

public class HasVolatileReferenceType
{
    public volatile List<int> MyVolatileMember;
}

The following usage is correct with respect to multi-threading, as each thread would replace the MyVolatileMember pointer. 以下用法对于多线程是正确的,因为每个线程都将替换MyVolatileMember指针。 Here, volatile ensures that the other threads will see the latest list instance stored in the MyVolatileMember field. 这里, volatile确保其他线程将看到存储在MyVolatileMember字段中的最新列表实例。

HasVolatileReferenceTypeexample = new HasVolatileReferenceType();
// instead of modifying `example.MyVolatileMember`
// we are replacing it with a new list. This is OK with volatile.
example.MyVolatileMember = example.MyVolatileMember
     .Where(x => x > 42).ToList();

In contrast, the below code is error prone, because it directly modifies the list. 相反,下面的代码容易出错,因为它直接修改了列表。 If this code is executed simultaneously with multiple threads, the list may become corrupted, or behave in an inconsistent manner. 如果此代码与多个线程同时执行,则列表可能会损坏,或者行为方式不一致。

example.MyVolatileMember.RemoveAll(x => x <= 42);

Let us return to value types for a while. 让我们暂时回到值类型。 In .NET all value types are actually reassigned when they are modified, they are safe to be used with the volatile keyword - see the code: 在.NET中,所有值类型在修改时实际上都被重新分配 ,它们可以安全地与volatile关键字一起使用 - 请参阅代码:

public class HasVolatileValueType
{
    public volatile int MyVolatileMember;
}

// usage
HasVolatileValueType example = new HasVolatileValueType();
example.MyVolatileMember = 42;

1 The notion of lates value here is a little misleading, as noted by Eric Lippert in the comments section . 1正如Eric Lippert评论部分所指出的,这里的lates值的概念有点误导。 In fact latest here means that the .NET runtime will attempt (no guarantees here) to prevent writes to volatile members to happen in-between read operations whenever it deems it is possible. 事实上,这里的最新意味着.NET运行时将尝试 (此处不保证)防止在读取操作之间发生对易失性成员的写入,只要它认为可能。 This would contribute to different threads reading a fresh value of the volatile member, as their read operations would probably be ordered after a write operation to the member. 这将有助于不同的线程读取volatile成员的新值,因为它们的读操作可能在对成员的写操作之后被排序。 But there is more to count on probability here. 但是在这里还有更多值得信赖的概率。

2 In general, volatile is OK to be used on any immutable object, since modifications always imply reassignment of the field with a different value. 2通常, volatile可以在任何不可变对象上使用,因为修改总是意味着使用不同的值重新分配字段。 The following code is also a correct example of the use of the volatile keyword: 以下代码也是使用volatile关键字的正确示例:

public class HasVolatileImmutableType
{
    public volatile string MyVolatileMember; 
}

// usage
HasVolatileImmutableType example = new HasVolatileImmutableType();
example.MyVolatileMember = "immutable";
// string is reference type, but is *immutable*, 
// so we need to reasign the modification result it in order 
// to work with the new value later
example.MyVolatileMember = example.MyVolatileMember.SubString(2);

I'd recommend you to take a look at this article . 我建议你看一下这篇文章 It thoroughly explains the usage of the volatile keyword, the way it actually works and the possible consequences to using it. 它彻底解释了volatile关键字的用法,实际工作方式以及使用它的可能后果。

So, I suppose you propose the following point to be added: 所以,我想你提出以下几点要补充:

  • A value type consisting only of one field which can be legally marked volatile. 一种值类型,仅包含一个可以合法标记为volatile的字段。

First, fields are usually private, so in external code, nothing should depend on a presence of a certain field. 首先,字段通常是私有的,因此在外部代码中,任何东西都不应该依赖于某个字段的存在。 Even though the compiler has no issue accessing private fields, it is not such a good idea to restrict a certain feature based on something the programmer has no proper means to affect or inspect. 尽管编译器在访问私有字段时没有问题,但根据程序员没有适当的方法来影响或检查某些特性并不是一个好主意。

Since a field is usually a part of the internal implementation of a type, it can be changed at any time in a referenced assembly, but this could make a piece of C# code that used the type illegal. 由于字段通常是类型内部实现的一部分,因此可以在引用的程序集中随时更改它,但这可能会使得使用该类型的C#代码非法。

This theoretical and practical reason means that the only feasible way would be to introduce a volatile modifier for value types that would ensure that point specified above holds. 这个理论和实践的原因意味着唯一可行的方法是为值类型引入一个volatile修饰符,以确保上面指定的点成立。 However, since the only group of types that would benefit from this modifier are value types with a single field, this feature probably wasn't very high on the list. 但是,由于唯一可以从此修饰符中受益的类型组是具有单个字段的值类型,因此列表中的此功能可能不是很高。

This is an educated guess at the answer... please don't shoot me down too much if I am wrong! 这是一个有根据的猜测答案...如果我错了,请不要把我击倒太多!

The documentation for volatile states: 易失性状态的文档

The volatile modifier is usually used for a field that is accessed by multiple threads without using the lock statement to serialize access. volatile修饰符通常用于多个线程访问的字段,而不使用lock语句来序列化访问。

This implies that part of the design intent for volatile fields is to implement lock-free multithreaded access. 这意味着volatile字段的部分设计意图是实现无锁多线程访问。

A member of a struct can be updated independently of the other members. 结构的成员可以独立于其他成员进行更新。 So in order to write the new struct value where only part of it has been changed, the old value must be read. 因此,为了编写只有部分更改的新结构值,必须读取旧值。 Writing is therefore not guaranteed to require a single memory operation. 因此,不能保证写入需要单个存储器操作。 This means that in order to update the struct reliably in a multithreaded environment, some kind of locking or other thread synchronization is required. 这意味着为了在多线程环境中可靠地更新结构,需要某种锁定或其他线程同步。 Updating multiple members from several threads without synchronization could soon lead to counter-intuitive, if not technically corrupt, results: to make a struct volatile would be to mark a non-atomic object as atomically updateable. 在没有同步的情况下从多个线程更新多个成员很快就会导致反直觉(如果不是技术上的损坏)结果:使结构易失性将标记非原子对象作为原子可更新。

Additionally, only some structs could be volatile - those of size 4 bytes. 此外,只有一些结构可能是易失性的 - 大小为4字节。 The code that determines the size - the struct definition - could be in a completely separate part of the program to that which defines a field as volatile. 确定大小的代码 - 结构定义 - 可以在程序的完全独立的部分中,也可以将字段定义为volatile。 This could be confusing as there would be unintended consequences of updating the definition of a struct. 这可能会令人困惑,因为更新结构定义会产生意想不到的后果。

So, whereas it would be technically possible to allow some structs to be volatile, the caveats for correct usage would be sufficiently complex that the disadvantages would outweigh the benefits. 因此,尽管技术上可以允许一些结构易变,但正确使用的警告将足够复杂,以至于缺点将超过其益处。

My recommendation for a workaround would be to store your 4-byte struct as a 4-byte base type and implement static conversion methods to use each time you want to use the field. 我建议的解决方法是将4字节结构存储为4字节基本类型,并实现静态转换方法,以便每次使用该字段时使用。

To address the second part of your question, I would support the language designers decision based on two points: 为了解决问题的第二部分,我会基于两点支持语言设计者的决定:

KISS - Keep It Simple Simon - It would make the spec more complex and implementations hard to have this feature. 亲吻 - 保持简单西蒙 - 它会使规范更加复杂,并且很难实现此功能。 All language features start at minus 100 points , is adding the ability to have a small minority of struts volatile really worth 101 points? 所有语言功能都从零下100点开始,增加了少数struts volatile能否真正值得101分的能力?

Compatibility - questions of serialization aside - Usually adding a new field to a type [class, struct] is a safe backwards source compatible move. 兼容性 - 除了序列化的问题 - 通常向类型[class,struct]添加新字段是一种安全的向后源兼容移动。 If you adding a field should not break anyones compile. 如果你添加一个字段不应该破坏anyones编译。 If the behavior of structs changed when adding a field this would break this. 如果在添加字段时结构的行为发生了变化,则会破坏它。

I think it is because a struct is a value type, which is not one of the types listed in the specs. 我认为这是因为结构是一种值类型,它不是规范中列出的类型之一。 It is interesting to note that reference types can be a volatile field. 有趣的是,引用类型可以是易失性字段。 So it can be accomplished with a user-defined class. 所以它可以通过用户定义的类来完成。 This may disprove your theory that the above types are volatile because they can be stored in 4 bytes (or maybe not). 这可能反驳了您的理论,即上述类型是易失性的,因为它们可以存储在4个字节(或者可能不存储)中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM