5

Take a look at the example below:

struct MyStruct
{
     private volatile int _field;
     public void Set(int v) => _field = v;
}

class MyClass
{
    private MyStruct _myStruct;
    // is it same as _myStruct.Set(0)?
    public void SomeMethod() => _myStruct = new MyStruct();
}

Basically the question is whether reassigning the whole struct with a volatile field inside uses the same volatile semantics as assigning the field directly.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
Vlad
  • 3,001
  • 1
  • 22
  • 52
  • Volatile only ensures that the compiler/cpu will read/write the value from/to memory (bypassing some optimizations like register caching). It has no effect on thread safety whatsoever. What you are really asking here is: is copying a volatile field thread safe? No, there is no such guarantee. It depends on how you use the field in other places. – freakish Feb 04 '22 at 07:59
  • @freakish: Doesn't C# `volatile` imply atomicity and acquire/release memory ordering wrt. surrounding memory accesses? [An answer from 2011](https://stackoverflow.com/questions/6526432/volatile-with-release-acquire-semantics/6526509#6526509) quotes the C# 4 language spec re: acquire / release, so unless that's changed, yes `volatile` does have more effect on thread safety than what you describe (which sounds like ISO C++ semantics for `volatile` where (on a cache-coherent machine, i.e. always) it's like atomic with memory_order_relaxed, except without any guarantee of even being atomic.) – Peter Cordes Feb 04 '22 at 09:41
  • @PeterCordes volatile itself doesn't imply atomicity. It is however true that the C# standard only allows volatile on types for which C# guarantes atomic reads/writes. So I suppose you are right. Still, being thread safe is a lot more than only atomic on single read/write. – freakish Feb 04 '22 at 10:47
  • @freakish: Well sure, thread safety is a property of an algorithm. Although if we compare to C++, two simultaneous accesses to the same `int` is undefined behaviour if they're not both reads, so you can say that it's definitely *not* thread-safe to do that, while simultaneous writes of an atomic any memory order, even `mo_relaxed`, are safe. Whether it's part of a useful thread-safe algorithm or not depends on the surrounding code, but as far as just writing a variable, C++ `atomic` or C# `volatile` makes that safe. If you want to define "thread-safe" as a higher-level concept, sure I guess. – Peter Cordes Feb 04 '22 at 10:58

1 Answers1

5

The volatile prefix is implemented using a modreq compiler attribute, and is only enforced by the compiler if access is done directly through the field that is marked volatile.

ECMA-335 specification does not actually have any volatile attribute for fields as such, and the CLR does not enforce volatile access to such a field, so it is up to the compiler to issue a volatile. prefix in the generated IL. It only does this if you access the field directly.

You can see this easily by decompiling the following code

public class C {
    static s s1;
    static s s2;
    public void M() {
        s1 = new s();
        s2 = s1;
        s2.i = s1.i;
    }
}
struct s{
     public volatile int i;   
}
.class public auto ansi beforefieldinit C
    extends [System.Private.CoreLib]System.Object
{
    // Fields
    .field private static valuetype s s1
    .field private static valuetype s s2

    // Methods
    .method public hidebysig 
        instance void M () cil managed 
    {
        // Method begins at RVA 0x2050
        // Code size 47 (0x2f)
        .maxstack 8

        IL_0000: nop
        IL_0001: ldsflda valuetype s C::s1
        IL_0006: initobj s
        IL_000c: ldsfld valuetype s C::s1
        IL_0011: stsfld valuetype s C::s2
        IL_0016: ldsflda valuetype s C::s2
        IL_001b: ldsflda valuetype s C::s1
        IL_0020: volatile.
        IL_0022: ldfld int32 modreq([System.Private.CoreLib]System.Runtime.CompilerServices.IsVolatile) s::i
        IL_0027: volatile.
        IL_0029: stfld int32 modreq([System.Private.CoreLib]System.Runtime.CompilerServices.IsVolatile) s::i
        IL_002e: ret
    } // end of method C::M

    .method public hidebysig specialname rtspecialname 
        instance void .ctor () cil managed 
    {
        // Method begins at RVA 0x2080
        // Code size 8 (0x8)
        .maxstack 8

        IL_0000: ldarg.0
        IL_0001: call instance void [System.Private.CoreLib]System.Object::.ctor()
        IL_0006: nop
        IL_0007: ret
    } // end of method C::.ctor

} // end of class C

.class private sequential ansi sealed beforefieldinit s
    extends [System.Private.CoreLib]System.ValueType
{
    // Fields
    .field public int32 modreq([System.Private.CoreLib]System.Runtime.CompilerServices.IsVolatile) i

} // end of class s

Sharplab link

Charlieface
  • 52,284
  • 6
  • 19
  • 43