37

I've used volatile where I'm not sure it is necessary. I was pretty sure a lock would be overkill in my situation. Reading this thread (Eric Lippert comment) make me anxious on my usage of volatile: When should the volatile keyword be used in c# ?

I used volatile because my variable is use in a Multithreaded context where this variable could be accessed/modified concurrently, but where I can loose an addition without any hurts (see code).

I added "volatile" to make sure that there is no miss alignment occurring: reading only 32 bits of the variable and the other 32 bits on another fetch which can be broken in 2 by a write in the middle from another thread.

Does my previous assumption (previous statement) can really happen of not ? If not, does "volatile" usage is still necessary (Option properties modifications could happen in any thread).

After reading the 2 first answers. I would like to insists on the fact that the way the code is written, it is not important if due to concurrency we miss an increment (want to increment from 2 threads but the result is only incremented by one due to concurrency) if at least the variable '_actualVersion' is incremented.

As reference, this is the part of code where I'm using it. It is to report save action (write to disk) only while the application is idle.

public abstract class OptionsBase : NotifyPropertyChangedBase
{
    private string _path;

    volatile private int _savedVersion = 0;
    volatile private int _actualVersion = 0;

    // ******************************************************************
    void OptionsBase_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        _actualVersion++;
        Application.Current.Dispatcher.BeginInvoke(new Action(InternalSave), DispatcherPriority.ApplicationIdle);
    }

    // ******************************************************************
    private void InternalSave()
    {
        if (_actualVersion != _savedVersion)
        {
            _savedVersion = _actualVersion;
            Save();
        }
    }

    // ******************************************************************
    /// <summary>
    /// Save Options
    /// </summary>
    private void Save()
    {
        using (XmlTextWriter writer = new XmlTextWriter(_path, null))
        {
            writer.Formatting = Formatting.Indented;
            XmlSerializer x = new XmlSerializer(this.GetType());

            x.Serialize(writer, this);
            writer.Close();
        }
    }
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
Eric Ouellet
  • 10,996
  • 11
  • 84
  • 119
  • Not what you asked (hence added as comment), but I would probably move the Save() method call to above the '_savedVersion = _actualVersion' line. That way, if Save() throws an exception, the _savedVersion variable won't get incorrectly updated. – Baldrick Oct 15 '13 at 13:43
  • Why are saving from the event `Dispatcher` thread?! – Ahmed KRAIEM Oct 15 '13 at 13:48
  • @Baldrick, I can't do what you are saying (moving) because of multithreaded implication (I can miss a version change if I do so). But you are partly right where I should prevent my code from exception. Thanks ! – Eric Ouellet Oct 15 '13 at 14:06
  • @Ahmed, good question. In most cases my OptionBase class will be modififed from UI thread and many properties will be modified at once. In that way, I can assure to save only once for every property modifications. I wanted to abstract the load and save from the interface (hide that to user). It should stay easier to use. Options can be modified also one by one anywhere in code and usually in UI thread. I prefer to ensure not to manage save all the time. – Eric Ouellet Oct 15 '13 at 14:23
  • 1
    @EricOuellet - this answer I found was best to get your head around how they all work together - https://stackoverflow.com/questions/154551/volatile-vs-interlocked-vs-lock – Gregory William Bryant Dec 07 '20 at 15:40
  • @GregoryWilliamBryant, Thanks a lot. It is very kind of you. You are right, Zach Saw is the first and only one great answer I really appreciate about "Volatile". – Eric Ouellet Dec 09 '20 at 19:16

5 Answers5

66

I've used volatile where I'm not sure it is necessary.

Let me be very clear on this point:

If you are not 100% clear on what volatile means in C# then do not use it. It is a sharp tool that is meant to be used by experts only. If you cannot describe what all the possible reorderings of memory accesses are allowed by a weak memory model architecture when two threads are reading and writing two different volatile fields then you do not know enough to use volatile safely and you will make mistakes, as you have done here, and write a program that is extremely brittle.

I was pretty sure a lock would be overkill in my situation

First off, the best solution is to simply not go there. If you don't write multithreaded code that tries to share memory then you don't have to worry about locking, which is hard to get correct.

If you must write multithreaded code that shares memory, then the best practice is to always use locks. Locks are almost never overkill. The price of an uncontended lock is on the order of ten nanoseconds. Are you really telling me that ten extra nanoseconds will make a difference to your user? If so, then you have a very, very fast program and a user with unusually high standards.

The price of a contended lock is of course arbitrarily high if the code inside the lock is expensive. Do not do expensive work inside a lock, so that the probability of contention is low.

Only when you have a demonstrated performance problem with locks that cannot be solved by removing contention should you even begin to consider a low-lock solution.

I added "volatile" to make sure that there is no misalignment occurring: reading only 32 bits of the variable and the other 32 bits on another fetch which can be broken in two by a write in the middle from another thread.

This sentence tells me that you need to stop writing multithreaded code right now. Multithreaded code, particularly low-lock code, is for experts only. You have to understand how the system actually works before you start writing multithreaded code again. Get a good book on the subject and study hard.

Your sentence is nonsensical because:

First off, integers already are only 32 bits.

Second, int accesses are guaranteed by the specification to be atomic! If you want atomicity, you've already got it.

Third, yes, it is true that volatile accesses are always atomic, but that is not because C# makes all volatile accesses into atomic accesses! Rather, C# makes it illegal to put volatile on a field unless the field is already atomic.

Fourth, the purpose of volatile is to prevent the C# compiler, jitter and CPU from making certain optimizations that would change the meaning of your program in a weak memory model. Volatile in particular does not make ++ atomic. (I work for a company that makes static analyzers; I will use your code as a test case for our "incorrect non-atomic operation on volatile field" checker. It is very helpful to me to get real-world code that is full of realistic mistakes; we want to make sure that we are actually finding the bugs that people write, so thanks for posting this.)

Looking at your actual code: volatile is, as Hans pointed out, totally inadequate to make your code correct. The best thing to do is what I said before: do not allow these methods to be called on any thread other than the main thread. That the counter logic is wrong should be the least of your worries. What makes the serialization thread safe if code on another thread is modifying the fields of the object while it is being serialized? That is the problem you should be worried about first.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Thanks Eric. I do not have the intention to continue to use 'volatile' (until I know exactly what it mean) but I will continue to write MT code, although you seems to think I should not ;-) ! I still wonder if a 64 bits integer write or fetch is atomic on 32bits and 64bits (misaligned on 64bits) system ? – Eric Ouellet Oct 15 '13 at 17:48
  • @EricOuellet: According to the documentation for [Interlocked.Read](http://msdn.microsoft.com/en-us/library/system.threading.interlocked.read.aspx): "On 32-bit systems, 64-bit read operations are not atomic unless performed using Read." 64-bit writes, also, are not atomic on 32-bit systems. – Jim Mischel Oct 15 '13 at 18:29
  • @Jim, Thanks a lots! I didn't knew that. I'm really happy to know. – Eric Ouellet Oct 16 '13 at 13:32
  • 2
    @EricOuellet: The C# *language* guarantees only that reads and writes of 32 bit integers (or smaller), bools, single-precision floats, references and pointers are atomic. (When aligned.) The *runtime* might make the guarantee that 64 bit doubles and integer reads and writes are atomic on 64 bit operating systems, but that is not the *language* making that guarantee, that's the *runtime*. – Eric Lippert Oct 16 '13 at 13:50
  • @Eric, Thanks! Yes I should read more books. I'm very amaze about that. To my opinion there is major performance issue about it (very low per operation) but multiply by the number of operation on them become significant. I question myself if those guarantees are not somewhat overkill ? Those protections are useful only on MT while portion of code where access to doubles and/or integer in MT context should be protected by locks in most cases. But I will find my answers in good books :-) !!! Thanks a lot for taking time to answered me, I appreciate. – Eric Ouellet Oct 16 '13 at 17:34
  • 22
    @EricLippert Great post! A bit of a condescending tone at times though ([paraphrase] I will use your code at my job as an example of what dumb people do when coding). We really didn't need that. – estebro Dec 11 '14 at 21:05
  • 3
    @estebro: No condescension is intended; I am genuinely pleased when smart people post the plausible-but-broken code they've written. It helps me and my colleagues to write analyzers that find the broken code, thereby making mission-critical code that affects you more likely to be correct. – Eric Lippert Dec 12 '14 at 16:26
  • 2
    @estebro There's nothing condescending in Eric Lippert's answer as far as I see it. The above code is very broken and is a good example of teaching how not to write multithreaded code. – xxbbcc Mar 30 '16 at 18:00
  • 4
    @EricLippert, although meta to the answer, I would agree with estebro that it came across condescending. I feel if you edited your answer, it could be more succinct and maybe more informative. – Gregory William Bryant Dec 07 '20 at 15:10
  • @GregoryWilliamBryant: FYI I don't respond to complaints about tone except to say that I don't respond to complaints about tone. Once. If you have a germane critique of the accuracy of the answer, I'd be happy to consider it. – Eric Lippert Dec 07 '20 at 18:05
  • long story short: `volatile` shouldn't be used, since it's waste of time to introduce developer's time consuming optimization, where difference in performance between `lock` and `volatile` based solution is approx. few nanoseconds. – Bartłomiej Stasiak May 20 '22 at 00:54
  • @BartłomiejStasiak: I wouldn't go quite that far but I agree with the general idea of your comment. I would rather say that volatile is primarily useful as a primitive operation for *developers who build new concurrency mechanisms*, and should be used by developers who are already experts in concurrency. Developers writing ordinary multithreaded business logic should not attempt to eke out a few extra nanoseconds by trading locks for volatiles without thoroughly understanding the consequences. – Eric Lippert May 20 '22 at 20:08
16

Volatile is woefully inadequate to make this code safe. You can use low-level locking with Interlocked.Increment() and Interlocked.CompareExchange() but there's very little reason to assume that Save() is thread-safe. It sure looks like it tries to save an object that's being modified by a worker thread.

Using lock is very strongly indicated here, not just to protect the version numbers but also to prevent the object from changing while it is being serialized. The corrupted saves you'll get from not doing this are entirely too infrequent to ever have a shot a debugging the problem.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • I'm partly agree. Enough agree to modify my code in accordance to what you say. But I would have survived to a non-coherent save but what makes me really afraid is to have an exception during my save... which I can also write code to prevent from, but lock become a clear candidate here. Thanks ! – Eric Ouellet Oct 15 '13 at 14:29
3

According to Joe Albahari's excellent post on threads and locks which is from his equally excellent book C# 5.0 In A Nutshell, he says here that even when the volatile keyword is used, that a write statement followed by a read statement can be reordered.

Further down, he says that MSDN documentation on this topic is incorrect and suggests that there is a strong case to be made for avoiding the volatile keyword altogether. He points out that even if you happen to understand the subtleties involved, will other developers down the line also understand it?

So, using a lock is not only more "correct", it is also more understandable, offers ease of adding a new feature that adds multiple update statements to the code in an atomic fashion - which neither volatile nor a fence class like MemoryBarrier can do, is very fast, and is much easier to maintain since there is much less chance of introducing a subtle bug by less experienced developers down the line.

Bob Bryan
  • 3,687
  • 1
  • 32
  • 45
1

Regarding to your statement whether a variable could be split in two 32 bit fetches when using volatile, this could be a possibility if you were using something bigger than Int32.

So as long as you are using Int32 you do not have an issue with what you are stating.

However, as you have read in the suggested link volatile gives you only weak guarantees and I would prefer locks so as to be on the safe side as today's machines have more than one CPU and volatile does not guarantee that another CPU won't sweep in and do something unexpected.

EDIT

Have you considered using Interlocked.Increment?

idipous
  • 2,868
  • 3
  • 30
  • 45
  • Thanks idipous, I really like your answer. I would have prefer something a little bit more deeper. I will wait few days and if nothing else deeper happen, I will make yours an accepted. – Eric Ouellet Oct 15 '13 at 14:03
0

The comparison and assignment in your InternalSave() method would not be thread safe with or without the volatile keyword. If you would like to avoid using locks, you could use the CompareExchange() and Increment() methods in your code from the framework's Interlocked class.

  • `Interlocked.Increment` will work well for the increment, but `CompareExchange` isn't going to handle the race condition in the `InternalSave` method. Looks to me like he'll need a lock. – Jim Mischel Oct 15 '13 at 14:04
  • Race condition here is not a problem because of the way the code is written. But like Hans says, I could have incoherent saved data if I do not use lock to prevent save to occurs while I 'm modifying properties from other threads. – Eric Ouellet Oct 15 '13 at 14:31
  • @Jim, The save itself could only occurs on the UI thread (only one thread) and is safe by default. If you were talking about relation between the other code and the save, then yes you were right and I need a lock. Thanks, Eric. – Eric Ouellet Oct 15 '13 at 18:02