Is the following code MT-Safe?

From: Ken Durden (creepiecrawlies_at_hotmail.com)
Date: 06/30/04


Date: 30 Jun 2004 10:06:40 -0700

Explanation follows code example, MFC synchronization objects used
because thats what I know how to use. Some psuedo-code may be present
where coding in all the parameters wasn't meaningful.

class A
{
public:
  A() :
   _bRunning(false),
   _stopEvent(FALSE,FALSE),
   _stoppedEvent(FALSE,FALSE)
   {}

  void start()
  {
    CSingleLock( &crit, TRUE );

    AfxBeginThread(ThreadFunc, (LPVOID) this, ... );
  }

  void stop()
  {
    CSingleLock( &crit, TRUE );

    _stoppedEvent.Reset();
    _stopEvent.Set();

    ::WaitForSingleObject( _stoppedEvent, INFINITE );
  }

  void test()
  {
    CSingleLock( &crit, TRUE );

    if( _bRunning )
      stop();

    assert( !_bRunning );
  }

private:
  static UINT ThreadFunc( LPVOID pParam )
  {
    A * a = (A*) pParam;

    a->_bRunning = true;
    ::WaitForSingleObject( a->_stopEvent, INFINITE );
    a->_bRunning = false;

    a->_stoppedEvent.Set();
  }

  CCriticalSection crit;
  CEvent _stopEvent;
  CEvent _stoppedEvent;
  bool _bRunning;
};

int main()
{
  A a;
  a.start();
  a.test();
}

In this simplistic example, assume the only two threads you see are
the only two which are relevant. In this simple example, am I
guaranteed that the assert in the test function will not fire?

I'm worried that the main thread will read the value of _bRunning in
the test() function, and then when it comes time to do the assert,
simply re-use the existing value it has already read, rather than
going back to main memory to get the new value which was set by the
other thread. Is this a valid concern?

Does _bRunning need to be tagged as volatile?

My understanding from school and reading is that it does, but that
supposition blows so many holes in the way most MT programs I've seen
are written its scary. For example, here's another example.. even
simpler:

class B
{
  public:
    B() : m_pList(NULL) {}

    void add( int n )
    {
      CSingleLock( &crit, TRUE );

      if( m_pList == NULL )
        m_pList = new std::list<int>();

      m_pList->push_back( n );
    }

    int size()
    {
      CSingleLock( &crit, TRUE );

      if( m_pList == NULL )
        return 0;
      
      return m_pList->size();
    }

  private:
  CCriticalSection crit;
  std::list<int> * m_pList;
};

In this example, does m_pList need to be declared as
std::list<int> * volatile m_pList

Suppose I have a dozen threads all competing to add items to this
list, without volatile, is there a possibility that one thread will
have a "cached" value of m_pList which is out of date? If the threads
only called add(), it seems that there is no way for the NULL value of
m_pList to be cached in a register; what if the threads also called
size() prior to calling add, however, would this introduce a
possibility for the pointer value to be stored in a register?

Given the relatively small set of registers on Intel processors, how
big an issue is volatile on that platform? What effect does locality
of referencing have? If I reference the same variable 30 C lines apart
from each other does that make a difference from accessing it 2 lines
apart? Do non-inlined function-calls change this behavior?

What are the general guidelines for using volatile? Even if I've got
my locks and events all lined up perfectly, it seems I can still get
burnt without it. I've got a beefed up scenario which models class A
(the first one), I'm going to add volatile to see if I can change the
behavior, but its (dread) low-frequency intermittent problem, so I
won't know for a month if it helped probably.

Thanks alot,
-Ken



Relevant Pages

  • Is this code MT-Safe?
    ... void start ... Does _bRunning need to be tagged as volatile? ... m_pList to be cached in a register; what if the threads also called ... Do non-inlined function-calls change this behavior? ...
    (comp.lang.cpp)
  • Re: Is this code MT-Safe?
    ... > void start ... > Does _bRunning need to be tagged as volatile? ... > possibility for the pointer value to be stored in a register? ... the thread to the resource you are protecting. ...
    (comp.lang.cpp)
  • Re: Share .cpp and .h along projects
    ... You asked me to show how a volatile pointer could be used to ... volatile void* or a cast could be used). ... As you stated "A compiler that can see into all these functions will ...
    (microsoft.public.vc.language)
  • Re: [PATCH 0/24] make atomic_read() behave consistently across all architectures
    ... It doesn't mean that (volatile int*) cast is bad, ... I would agree that fixing the compiler in this case would be a good thing, ... bad register allocation. ... still quite possible to find cases where it did some optimization (in this ...
    (Linux-Kernel)
  • [PATCH 451] M68k new gcc optimizations
    ... -static inline int test_bit(int nr, const volatile unsigned long *vaddr) ... const volatile void *vaddr) ...
    (Linux-Kernel)

Loading