Why doesn't this code produce read breaks?

When viewing CLR / CLI specifications and memory models, etc. I noticed the wording around atomic reads / records according to the ECMA CLI specification :

The corresponding CLI should ensure that read and write access to correctly aligned memory cells does not exceed the size of the native word (size of type int int) is atomic when all write accesses to a location of the same size.

In particular, the phrase “correctly aligned memory” caught my attention. I wondered if I could somehow break type reads on a long64-bit system with some tricks. So I wrote the following test case:

unsafe class Program {
    const int NUM_ITERATIONS = 200000000;
    const long STARTING_VALUE = 0x100000000L + 123L;
    const int NUM_LONGS = 200;
    private static int prevLongWriteIndex = 0;

    private static long* misalignedLongPtr = (long*) GetMisalignedHeapLongs(NUM_LONGS);

    public static long SharedState {
        get {
            Thread.MemoryBarrier();
            return misalignedLongPtr[prevLongWriteIndex % NUM_LONGS];
        }
        set {
            var myIndex = Interlocked.Increment(ref prevLongWriteIndex) % NUM_LONGS;
            misalignedLongPtr[myIndex] = value;
        }
    }

    static unsafe void Main(string[] args) {
        Thread writerThread = new Thread(WriterThreadEntry);
        Thread readerThread = new Thread(ReaderThreadEntry);

        writerThread.Start();
        readerThread.Start();

        writerThread.Join();
        readerThread.Join();

        Console.WriteLine("Done");
        Console.ReadKey();
    }

    private static IntPtr GetMisalignedHeapLongs(int count) {
        const int ALIGNMENT = 7;
        IntPtr reservedMemory = Marshal.AllocHGlobal(new IntPtr(sizeof(long) * count + ALIGNMENT - 1));
        long allocationOffset = (long) reservedMemory % ALIGNMENT;
        if (allocationOffset == 0L) return reservedMemory;
        return reservedMemory + (int) (ALIGNMENT - allocationOffset);
    }

    private static void WriterThreadEntry() {
        for (int i = 0; i < NUM_ITERATIONS; ++i) {
            SharedState = STARTING_VALUE + i;
        }
    }

    private static void ReaderThreadEntry() {
        for (int i = 0; i < NUM_ITERATIONS; ++i) {
            var sharedStateLocal = SharedState;
            if (sharedStateLocal < STARTING_VALUE) Console.WriteLine("Torn read detected: " + sharedStateLocal);
        }
    }
}

, , , "Torn read detected!". ?

long , ; " " long ( - ).

, , , " " , , 30 - 200000000 .

+4
1

, . , .

  var myIndex = Interlocked.Increment(ref prevLongWriteIndex) % NUM_LONGS;

Interlocked, , . , . , . , .

- . Fix:

  var myIndex = 0;

  if (sharedStateLocal < STARTING_VALUE)

, . STARTING_VALUE . , , 1 -1, , . :

private static void WriterThreadEntry() {
    for (int i = 0; i < NUM_ITERATIONS; ++i) {
        SharedState = 1;
        SharedState = -1;
    }
}

private static void ReaderThreadEntry() {
    for (int i = 0; i < NUM_ITERATIONS; ++i) {
        var sharedStateLocal = SharedState;
        if (Math.Abs(sharedStateLocal) != 1) {
            Console.WriteLine("Torn read detected: " + sharedStateLocal);
        }
    }
}

32- . 64- , , . L1, , 32- . Fix:

private static IntPtr GetMisalignedHeapLongs(int count) {
    const int ALIGNMENT = -1;
    IntPtr reservedMemory = Marshal.AllocHGlobal(new IntPtr(sizeof(long) * count + 64 + 15));
    long cachelineStart = 64 * (((long)reservedMemory + 63) / 64);
    long misalignedAddr = cachelineStart + ALIGNMENT;
    if (misalignedAddr < (long)reservedMemory) misalignedAddr += 64;
    return new IntPtr(misalignedAddr);
}

ALIGNMENT -1 -7 64- .

+4

Source: https://habr.com/ru/post/1666819/


All Articles