1

After having read this wonderful article, I starting digging around to volatile-correct some code. One consequence of volatile-correctness (as I understand it) is that methods accessed from different threads should be volatile qualified.

A simple example might be a mutex using Win32 or pthreads. We could make a class Mutex and give it a field:

#if   defined BACKEND_WIN32
    CRITICAL_SECTION _lock;
#elif defined BACKEND_POSIX
    pthread_mutex_t _lock;
#endif

The ".acquire()" method might look like:

void Mutex::acquire(void) volatile {
    #if   defined BACKEND_WIN32
        EnterCriticalSection(&_lock);
    #elif defined BACKEND_POSIX
        pthread_mutex_lock(&_lock);
    #endif
}

Trying this doesn't work. From MSVC:

error C2664: 'void EnterCriticalSection(LPCRITICAL_SECTION)' : cannot convert argument 1 from 'volatile CRITICAL_SECTION *' to 'LPCRITICAL_SECTION'

From g++:

error: invalid conversion from ‘volatile pthread_mutex_t*’ to ‘pthread_mutex_t*’ [-fpermissive]

You'll get similar problems trying to unlock _lock. Two questions:

  • My impression is that these are just artifacts of the APIs being outdated. Is this in fact the case? Did I misunderstand something?
  • The whole purpose of a lock or unlock API function is to switch between being in a critical section, therefore, is it the case that I should just use the inadequately named const_cast to cast away the volatile-ness of _lock before passing to these functions, with no ill effect?
geometrian
  • 14,775
  • 10
  • 56
  • 132
  • 4
    I believe that article is a little out of date. In modern times volatile is no longer sufficient to use between threads and we have new mechanisms like memory guards and atomics. I would guess most of the Win32 lock structures already have the proper annotations for the compiler not to break them. – woolstar Sep 20 '14 at 01:26
  • You definitely should not `const_cast` away volatileness - it's UB to access an object declared `volatile` through a non-volatile access path. That 13-year-old article, written for a language that has no concept of threads, is of limited modern relevance. – T.C. Sep 20 '14 at 03:49
  • http://stackoverflow.com/questions/4557979/when-to-use-volatile-with-multi-threading – T.C. Sep 20 '14 at 03:57

1 Answers1

1

I think the best summary of what the standard (now) intends by volatile is to be found in n3797 S7.1.6.1 /6/7

What constitutes an access to an object that has volatile-qualified type is implementation-defined. If an attempt is made to refer to an object defined with a volatile-qualified type through the use of a glvalue with a non-volatile-qualified type, the program behavior is undefined.

[ Note: volatile is a hint to the implementation to avoid aggressive optimization involving the object because the value of the object might be changed by means undetectable by an implementation. Furthermore, for some implementations, volatile might indicate that special hardware instructions are required to access the object. See 1.9 for detailed semantics. In general, the semantics of volatile are intended to be the same in C++ as they are in C. —end note ]

Sadly, this means that a standards-complying implementation is not required to do as much as that article seems to suggest. There is no obligation to store values promptly and a limited obligation to reload values that might have changed. The compiler is not free to elide code that loads a value that it has not stored, but it is free to elide code that stores a value that it never uses.

As noted in the comments, it's better to use implementation-defined APIs or atomic or a mutex. Volatile will let you down. Stroustrup says as much in the linked question.

Community
  • 1
  • 1
david.pfx
  • 10,520
  • 3
  • 30
  • 63