I will pose your question:
If I guarantee that two threads will never execute in parallel , do I still need to make my list variable volatile ?
You do not have two threads, you have three: one thread that launches the other two. It always works in parallel with other threads, and it uses a common flag to communicate with them. Given that the code you submitted is not necessary to mark the list as volatile .
But in the case of two threads and two threads only , which somehow run one after another without interference from the third (i.e. reading from a shared variable), making a volatile list would be enough to ensure that two threads always see one the same data.
For two threads that do not work simultaneously in order to view the list in a consistent state (in other words, updated), they should always work with the latest version of what is in memory. This means that when the stream begins to use the list, it should read from the list after previous entries have been set.
This implies memory barriers. The thread needs to acquire a barrier before using the list, and a separation barrier after executing with it. Using Thread.MemoryBarrier , you cannot precisely control the semantics of barriers, you always get full barriers (release and acquire what is stronger than we need), but the end result is the same.
So, if you can guarantee that threads will never run in parallel, the C # memory model can guarantee that the following works as expected:
private List<int> _list; public void Process() { try { Thread.MemoryBarrier();
Please note that the list is not volatile . Because it is not necessary: ββbarriers are needed.
Now the point is that the ECMA C # Language Specification says (emphasis mine):
17.4.3 Flying fields
Reading a volatile field is called volatile reading. Intermittent reading has "acquire semantics"; that is, it is guaranteed to occur before any memory references that occur after it in a sequence of commands.
A variable field record is called volatile write. A volatile entry has "release semantics"; that is, it is guaranteed to happen after any memory references before a write command in a sequence of commands.
(Thanks to R. Martigno Fernandez for finding the relevant paragraph in the standard!)
In other words, reading from the volatile field has the same semantics as the receive barrier, and writing to the volatile field has the same semantics as the release barrier. This means that, taking into account your premise, the following stanza of the code behaves identically 1 to the previous one:
private volatile List<int> _list; public void Process() { try {
And this is enough to ensure that until both threads are executed in parallel, they will always see the list in a consistent state.
(1) : Both examples are not strictly identical, the first offers more reliable guarantees, but in this particular case these strong guarantees are not required.