When do writes/reads affect main memory?
Asked Answered
L

4

5

When I write a value into a field, what guarantees do I get regarding when the new value will be saved in the main memory? For example, how do I know that the processor don't keep the new value in it's private cache, but updated the main memory?
Another example:

int m_foo;

void Read() // executed by thread X (on processor #0)
{
   Console.Write(m_foo);
}

void Write() // executed by thread Y (on processor #1)
{
   m_foo = 1;
}

Is there a possibility that after Write() was finished executing, some other thread executes Read() but actually will see "0" as the current value? (since perhaps the previous write to m_foo wasn't flushed yet?).
What kind of primitives (beside locks) are available to ensure the the writes were flushed?


EDIT
In the code sample I've used, the write and read are placed at different method. Doesn't Thread.MemoryBarrier only affect instruction reording that exist in the same scope?

Also, let's assume that they won't be inlined by the JIT, how can I make sure that the value written to m_foo won't be stored in a register, but to the main memory? (or when m_foo is read, it won't get an old value from the CPU cache).

Is it possible to achieve this without using locks or the 'volatile' keyword? (also, let's say I'm not using primitive types, but a WORD sized structs [so volatile cannot be applied].)

Lye answered 14/11, 2009 at 13:12 Comment(1)
If you have WORD size structs, you can convert them to a uint and you can apply volatile or any other technique discussed in the various answers.Neilson
N
3

Volatile and Interlocked have already been mentioned, you asked for primitives, one addition to the list is to use Thread.MemoryBarrier() before your writes or reads. This guarantees no reordering is done of memory writes and reads.

This is doing "by hand" what lock, Interlocked and volatile can do automatically most of the time. You could use this as a full replacement to any other technique, but it is arguably the hardest path to travel, and so says MSDN:

"It is difficult to build correct multithreaded programs by using MemoryBarrier. For most purposes, the C# lock statement, the Visual Basic SyncLock statement, and the methods of the Monitor class provide easier and less error-prone ways to synchronize memory accesses. We recommend that you use them instead of MemoryBarrier. "

How to use MemoryBarrier

A very fine example are the implementations of VolatileRead and VolatileWrite, that both internally use MemoryBarrier. The basic rule of thumb to follow is: when you read a variable, place a memory barrier after the read. When you write the value, the memory barrier must come before the write.

In case you've doubts whether this is less efficient then lock, consider that locking is nothing more then "full fencing", in that it places a memory barrier before and after the code block (ignoring Monitor for a moment). This principle is well explained in this excellent definitive article on threads, locking, volatile and memory barriers by Albahari.

From reflector:

public static void VolatileWrite(ref byte address, byte value)
{
    MemoryBarrier();
    address = value;
}

public static byte VolatileRead(ref byte address)
{
    byte num = address;
    MemoryBarrier();
    return num;
}
Neilson answered 14/11, 2009 at 13:29 Comment(4)
In the code sample I've used, the write and read are placed at different method. Doesn't Thread.MemoryBarrier only affect instruction reording that exist in the same scope? Also, let's assume that they won't be inlined by the JIT, how can I make sure that the value written to m_foo won't be stored in a register, but to the main memory? (or when m_foo is read, it won't get an old value from the CPU cache). Is it possible to achieve this without using locks or the 'volatile' keyword?Lye
Yes it is, and yes you can use MemoryBarrier for that. But whether that's wise are advisable is something else. I'll update my question with an explanation of this bit.Neilson
So practically, if I use a fence after writing the new value, it's guaranteed that the new value will moved from the register into the main memory? and if i use a fence before reading the value, then it's actually invalidating the cache, making the CPU to get the value from the main memory as well?Lye
Yes. I don't know whether it really invalidates the CPU cache, but it's guaranteed that any instructions writing to an address will be completed when you read from that address (if the read is placed after a MemoryBarrier). Here's a good additional read that explains this principle in more general terms: en.wikipedia.org/wiki/Memory_barrierNeilson
U
11

If you want to ensure it is written promptly and in-order, then mark it as volatile, or (with more pain) use Thread.VolatileRead / Thread.VolatileWrite (not an attractive option, and easy to miss one, making it useless).

volatile int m_foo;

Otherwise you have virtually no guarantees of anything (as soon as you talk multiple threads).

You might also want to look at locking (Monitor) or Interlocked, which would achieve the same effect as long as the same approach is used from all access (i.e. all lock, or all Interlocked, etc).

Uplift answered 14/11, 2009 at 13:14 Comment(6)
Volatile doesn't even really guarantee the "promptly" bit - just the "in-order". I've decided I don't properly understand volatile... the whole memory model is just too confusing. I'll only do lock-free coding when it's using another framework like PFX...Ancient
A note on volatile: this "order" is not guaranteed when a read is following a write, hence making volatile less perfect for the job as one might expect. This seems a little known fact, yet a guarantee for disaster. Among others, this is explained here: albahari.com/threading/part4.aspx#_Memory_BarriersNeilson
In other words, if you need to read-after-write, you must use a full fence and use Interlocked (uses full fences) or traditional locking.Neilson
@Abel: I was only referring to the ordering of writes.Ancient
@Jon: and I didn't see your text before I submitted. I just meant to make a general remark on using volatile ;-)Neilson
Dosen't 'volatile' (and memory fences at general) only affect instruction ordering, or does it also affect when values are "flushed" from registers into the main memory? (or, forcing the CPU to get the value from the main memory instead of it's private cache). Is it possible to tell the processor to read the value from the main memory without using the 'volatile' keyword? (let's say I have a 4 bytes struct, instead of a primitive type. What then?Lye
N
3

Volatile and Interlocked have already been mentioned, you asked for primitives, one addition to the list is to use Thread.MemoryBarrier() before your writes or reads. This guarantees no reordering is done of memory writes and reads.

This is doing "by hand" what lock, Interlocked and volatile can do automatically most of the time. You could use this as a full replacement to any other technique, but it is arguably the hardest path to travel, and so says MSDN:

"It is difficult to build correct multithreaded programs by using MemoryBarrier. For most purposes, the C# lock statement, the Visual Basic SyncLock statement, and the methods of the Monitor class provide easier and less error-prone ways to synchronize memory accesses. We recommend that you use them instead of MemoryBarrier. "

How to use MemoryBarrier

A very fine example are the implementations of VolatileRead and VolatileWrite, that both internally use MemoryBarrier. The basic rule of thumb to follow is: when you read a variable, place a memory barrier after the read. When you write the value, the memory barrier must come before the write.

In case you've doubts whether this is less efficient then lock, consider that locking is nothing more then "full fencing", in that it places a memory barrier before and after the code block (ignoring Monitor for a moment). This principle is well explained in this excellent definitive article on threads, locking, volatile and memory barriers by Albahari.

From reflector:

public static void VolatileWrite(ref byte address, byte value)
{
    MemoryBarrier();
    address = value;
}

public static byte VolatileRead(ref byte address)
{
    byte num = address;
    MemoryBarrier();
    return num;
}
Neilson answered 14/11, 2009 at 13:29 Comment(4)
In the code sample I've used, the write and read are placed at different method. Doesn't Thread.MemoryBarrier only affect instruction reording that exist in the same scope? Also, let's assume that they won't be inlined by the JIT, how can I make sure that the value written to m_foo won't be stored in a register, but to the main memory? (or when m_foo is read, it won't get an old value from the CPU cache). Is it possible to achieve this without using locks or the 'volatile' keyword?Lye
Yes it is, and yes you can use MemoryBarrier for that. But whether that's wise are advisable is something else. I'll update my question with an explanation of this bit.Neilson
So practically, if I use a fence after writing the new value, it's guaranteed that the new value will moved from the register into the main memory? and if i use a fence before reading the value, then it's actually invalidating the cache, making the CPU to get the value from the main memory as well?Lye
Yes. I don't know whether it really invalidates the CPU cache, but it's guaranteed that any instructions writing to an address will be completed when you read from that address (if the read is placed after a MemoryBarrier). Here's a good additional read that explains this principle in more general terms: en.wikipedia.org/wiki/Memory_barrierNeilson
T
2

As long as you don't use any synchronisation you have no guarantee that a thread running on one processor sees the changes made by another thread running on another processor. That's because the value could be cached in the CPU caches or in a CPU register.

Therefore you need to either mark the variable as volatile. That will create a 'Happens-Before'-realation between reads an writes.

Thruway answered 14/11, 2009 at 13:19 Comment(0)
K
2

That's not a processor cache issue. Writes are usually pass-through (writes go both to cache and main memory) and all reads will access to cache. But there is many other caches on the way (programming language, libraries, operating system, I/O buffers, etc.). The compiler can also choose to keep a variable in a processor register and to never write it to main memory (that's what the volatile operator is designed for, avoid storing value in register when it can be a memory mapped I/O).

If you have multiple processes or multiple threads and synchronisation is an issue you must do it explictly, there is many way to do it depending on the use case.

For a single threaded program, do not care, the compiler will do what it must and reads will access to what has been written.

Kowal answered 14/11, 2009 at 13:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.