3

I am in doubt with using objects from multiple threads.

First of all it is no problem to protect the object against simultaneous access with std::lock_guard or others.

The next step is to declare the objects with volatile.

class A
{
   public: int val;
};

volatile A a;

But doing this ends up in having a lot of new functions with type qualifiers volatile ... int GetVal() volatile; void SetVal() volatile; ...

OK, all this works fine.

But how to access stl members, e.g. std::vector or std::map.

If I want to have a volatile std::vector<int> I run in lots of errors while stl do not define any volatile method.

From this point I searched the net and found a lot of "tricks". Mostly with the same idea in the background: Having a mutex for concurrency protection and moving the volatile away with a const_cast to make the standard interface available.

As a sample of this implementations:

template <typename T>
class PseudoPtr {
    public:
        PseudoPtr(volatile T& obj, mutex& mtx) 
            : guard(mtx), 
              pObj_(const_cast<T*>(&obj)) 
        { }   
        ~PseudoPtr() { }   
        // Pointer behaviour
        T& operator*()  {   return *pObj_;  }   
        T* operator->() {   return  pObj_;  }   

    private:
        my_guard<mutex> guard;
        T* pObj_;
        PseudoPtr(const PseudoPtr&) = delete;
        PseudoPtr& operator=(PseudoPtr &) = delete;
};

Having my example class from above without the volatile qualifiers:

 class A
 {
     ...
     int GetVal();
 };

But if I use this in the following way:

 volatile A a;
 mutex mu;

 ...
 for (;;) 
 {
     PseudoPtr ptr(a, mu); //starts locking the object
     if (ptr->GetVal())
     {
         ... do something ...
     }
 }

I will never see any changes in the object a, because the compiler could optimize the access away, because the volatile object was const_cast and so the optimizer did not know anything about the volatile behavior.

For all my own classes this is not a problem, while I could write all the volatile methods. But if I want to use a stl container, there is no way to have volatile instances from them and using it though a const_cast.

OK, the actual compiler is not so hard optimizing (gcc 4.6.1), but is there a guarantee that the compiler never do this optimization? const_cast will break the volatile and do not const_cast on volatile stl objects will not work, while stl containers have no volatile methods at all.

As I think, all the 'tricks' found in the web are buggy, because they all ignore the optimizer.

This runs in my questions:

  • I am wrong with my interpretation of volatile and optimizers?
  • Is there a "standard" working solution for using stl conatiners thread safe?

---------------------- after a few hours of reading ------------------------

First of all, yes, pthread_mutex_lock should do the following things: * makes access to memory atomic (that is the only thing I know before) * guarantee that memory becomes visible to all other threads

OK, the second was new to me! And this results in the next question: How the trick works?

The library which provides the mutex semantic must have a chance to tell * the compiler, to stop out of order execution * writes down all cached (register optimized) variables to hardware * tell the hardware to synchronize all hardware caches etc.

OK, fine! But how that works: Implementation specific to gcc: There exists a macro called barrier() which is something like

asm volatile("" ::: "memory");

The pthread library for gcc is included to glibc/nptl and each function which guarantee the visibility of data to other threads simply calls the barrier macro or call the inline assembler directly or do the things with something similar.

If there is no misunderstanding again, that's the simple thing behind the curtain.

What I have learned: volatile in combination with mutex locking is not sense full in any case.

I write that answer in hope that others run in that mystery becomes also a bit less addled.

If there are again mistakes and misunderstandings?!: Let me know!

Thanks for all your answers!

(Sorry for editing my post to add my own conclusion, but I could not add an own answer to my post)

Klaus
  • 24,205
  • 7
  • 58
  • 113

3 Answers3

4

If you are using mutexes and the likes to synchronize the access to your objects, you don't need volatile at all.

Community
  • 1
  • 1
Björn Pollex
  • 75,346
  • 28
  • 201
  • 283
  • Can you please tell my why? There is no knowledge about who is changing the variables in the object. So if there is no volatile, there is no need to look for changes. That is the idea behind volatile. And if I access vars from multiple threads the call of a mutex lock has now semantic which tells the compiler that there is a possible data change from "outside world" like another thread. – Klaus Oct 21 '11 at 09:41
  • @Klaus: There is a link in my answer. Click it, and read the question and the accepted answer there. This should explain it pretty well. – Björn Pollex Oct 21 '11 at 09:42
  • Sorry for my blindness, I could not find an answer there. A call to a mutex lock is not a barrier for the compiler to throw away all optimizer knowledge. What tells the compiler to access again the changed variable if it is not volatile. Maybe there is "something", which tells the compiler to throw all cached vars away while calling the lock, but this is very inefficient at all. And why a mutex lock will do this (how to implement this). – Klaus Oct 21 '11 at 10:01
2

The "standard" way to make data access thread-safe is by using mutexes.
Volatile just tells that this variable may change outside of your application, so the compiler cannot perform any optimization basing on assumptions about its value.

If you want to make your classes thread-safe, the only way is to carefully evaluate all members, and design mutexes in order to ensure that all data access has the desired consistency level; note that, as pointed out by Nikko, STL already make some guarantee about reading data.

Finally, if you can switch to C++11 (or C++0x), then you can use all of its threading facilities, including thread-safe containers. All newer compiler versions have some support for this standard, so unless you are forced in some legacy environment, this is an option you may want to look into.

Community
  • 1
  • 1
rob
  • 36,896
  • 2
  • 55
  • 65
  • you write: "Volatile just tells that this variable may change outside of your application". I think that is wrong! The compiler only have a few on the actual block and not the application. The optimizer is allowed to cache data, maybe in registers, and don't access the memory any more. mutex makes access atomic, but do not stop the optimizer from caching. But how to stop caching without volatile? – Klaus Oct 21 '11 at 10:13
  • 2
    Wrong. A mutex tells compiler to disregard all optimizations, and to read again from memory. This is one of the guarantees of pthread, and is also explained in depth here: http://kernel.org/doc/Documentation/volatile-considered-harmful.txt – rob Oct 21 '11 at 10:48
  • The link you post explains the usage in kernel code with special functions there. Which things in application code act also as "memory barriers" and prevent the compiler from caching anything? All library calls? Is there something special to write own functions which have memory barrier semantics? – Klaus Oct 21 '11 at 11:03
  • The "special functions" mentioned there are just about direct memory access (that is *the* use case for `volatile`). – rob Oct 21 '11 at 12:40
2

I am wrong with my interpretation of volatile and optimizers?

Yes you are wrong, you don't need volatile. It's not Java. You need to protect your shared objects with mutexes (or the newest thread facilities). As far as I know, "volatile" in C was not made with threads in mind.

Is there a "standard" working solution for using stl conatiners thread safe?

STL containers are thread independant, it means that simultaneous readings do not need to be protected by mutexes but if you are writing it must be.

Nikko
  • 4,182
  • 1
  • 26
  • 44
  • I didn't know that about the thread safety for the STL containers. Is there a link you know of. – Tom Oct 21 '11 at 09:27
  • I do not links, but you can find about it in Meyers' "Effective STL". IIRC, it was even in some very initial chapter. – rob Oct 21 '11 at 09:29