Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Categories

C++ Thread Memory States/References

EshazearEshazear Member Posts: 2
Hey,

I've been busy with C++ for many years now, and this particular problem has always been eluding me.
I'm using Visual C++ 6.0, with Service packs 5&6 installed, as well as processor pack 5.
My machine is running Windows XP SP2, and hyperthreading is enabled.


To Illustrate my problem I've created a simple test app. :
In a multi-threaded C++ Application, I have a Parent Dialog, which creates(runs) 2 independent threads.

I have assigned each of the 2 threads, intensive, continuous computational work within a while loop. (The loop criteria is met and breaks when the parent sets a member value requesting them to exit)

This Test : starting and running both threads simultaneously and then shutting them down again, works perfectly, in debug.
However, once I compile and run the project in release, neither of the threads seem to register that the the loop criteria variable has changed.

------------------------------------------------------
Thread Code :
------------------------------------------------------
CTestThread1Dlg* parent = (CTestThread1Dlg*) param;
parent->m_Thread1Running = true;
parent->m_Thread1Starting = false;

while (parent->m_RunThread1)
{
parent->m_Th1Value++;
}

parent->m_Thread1Running = false;
_endthread();
------------------------------------------------------

But this only happens when the thread is using all the computational power that it can get (no interruptions, ie. conditionals, Sleeps, etc).
If I were to introduce a "Sleep(15)" right after the increment, then the thread would immediately register a change in "m_RunThread1" and exit, the moment the parent changes "m_RunThread1".

With the given code above(Thread Code), in release, the thread does not register and break the while loop when I call code from the parent, such as :
------------------------------------------------------
Parent Code :
------------------------------------------------------
m_RunThread1 = false;
Beep(450, 50);
Beep(450, 50);
while (m_Thread1Running || m_Thread1Starting)
Sleep(100);
AfxMessageBox("Thread1 has exited");
------------------------------------------------------

I added the 2 beeps so that I also have audio confirmation that "m_RunThread1", was indeed set - and I hear the beeps, but the threads keep going.

Do the threads have a type of duplicate memory state of all the members that they access, (which are only updated once the thread's CPU time has elapsed, and it is removed from the stack for the next thread)?

That does not make logical sense to me, but it is the only reason that I could think of, explaining why the parents' "parent->m_RunThread1" is not 'updated' until the thread's execution cycle is interrupted...

Has anyone else experienced this behavior?
I've currently pinned down the problem within a huge multi-threaded application, and unless I can identify how to solve it in this simple test case, there is no hope of fixing it :/

Any info. would be appreciated, thanks.

Comments

  • manucpmanucp Member Posts: 34
    Hi, I have experienced the same problem too. Think of this: While you are trying to make a thread aware of a change introduced by another thread, both the OS and the CPU are trying to do just the opposite. So, they are trying to save the state of a thread before switching to another one (every timeslice) and restoring it back when the first thread comes alive again. And there are several cache levels in the play.

    Suppose a thread increments a variable and is switched out by the system. ANother thread increments the variable and switches out. The first thread can find the variable incremented by two when it expected a single increment. People at Intel and Microsoft has been dedicating a lot of time to keeps things isolated.

    You have to introduce some level of sinchronization: use interlocked variables or just send a message to tell the thread it has to terminate or maybe use a synchronization object (event, for example). The system tends to flush the state of variables to memory when you uses synchro objects.

    ALso, take in account that use of MFC threading functions and the C run-time ones intermixed becomes a mess quickly.

    : Hey,
    :
    : I've been busy with C++ for many years now, and this particular
    : problem has always been eluding me.
    : I'm using Visual C++ 6.0, with Service packs 5&6 installed, as well
    : as processor pack 5.
    : My machine is running Windows XP SP2, and hyperthreading is enabled.
    :
    :
    : To Illustrate my problem I've created a simple test app. :
    : In a multi-threaded C++ Application, I have a Parent Dialog, which
    : creates(runs) 2 independent threads.
    :
    : I have assigned each of the 2 threads, intensive, continuous
    : computational work within a while loop. (The loop criteria is met
    : and breaks when the parent sets a member value requesting them to
    : exit)
    :
    : This Test : starting and running both threads simultaneously and
    : then shutting them down again, works perfectly, in debug.
    : However, once I compile and run the project in release, neither of
    : the threads seem to register that the the loop criteria variable has
    : changed.
    :
    : ------------------------------------------------------
    : Thread Code :
    : ------------------------------------------------------
    : CTestThread1Dlg* parent = (CTestThread1Dlg*) param;
    : parent->m_Thread1Running = true;
    : parent->m_Thread1Starting = false;
    :
    : while (parent->m_RunThread1)
    : {
    : parent->m_Th1Value++;
    : }
    :
    : parent->m_Thread1Running = false;
    : _endthread();
    : ------------------------------------------------------
    :
    : But this only happens when the thread is using all the computational
    : power that it can get (no interruptions, ie. conditionals, Sleeps,
    : etc).
    : If I were to introduce a "Sleep(15)" right after the increment, then
    : the thread would immediately register a change in "m_RunThread1" and
    : exit, the moment the parent changes "m_RunThread1".
    :
    : With the given code above(Thread Code), in release, the thread does
    : not register and break the while loop when I call code from the
    : parent, such as :
    : ------------------------------------------------------
    : Parent Code :
    : ------------------------------------------------------
    : m_RunThread1 = false;
    : Beep(450, 50);
    : Beep(450, 50);
    : while (m_Thread1Running || m_Thread1Starting)
    : Sleep(100);
    : AfxMessageBox("Thread1 has exited");
    : ------------------------------------------------------
    :
    : I added the 2 beeps so that I also have audio confirmation that
    : "m_RunThread1", was indeed set - and I hear the beeps, but the
    : threads keep going.
    :
    : Do the threads have a type of duplicate memory state of all the
    : members that they access, (which are only updated once the thread's
    : CPU time has elapsed, and it is removed from the stack for the next
    : thread)?
    :
    : That does not make logical sense to me, but it is the only reason
    : that I could think of, explaining why the parents'
    : "parent->m_RunThread1" is not 'updated' until the thread's execution
    : cycle is interrupted...
    :
    : Has anyone else experienced this behavior?
    : I've currently pinned down the problem within a huge multi-threaded
    : application, and unless I can identify how to solve it in this
    : simple test case, there is no hope of fixing it :/
    :
    : Any info. would be appreciated, thanks.
    :
    :
  • LundinLundin Member Posts: 3,711
    You can get odd problems if you don't set variables that are shared between two threads to "volatile". The compiler might decide to optimize a variable, since it can't grasp the meaning of the code: for the optimizer it might look as if a variable isn't going to be used, and therefore it removes it from the final program. It has no idea that two threads will be running at once, affecting the same variable.

    It is also good practice to protect [italic]all[/italic] shared variables by mutecies, and not just the ones that are written to by more than one thread. Because the writing thread will most likely need more than one CPU instruction to write to the variable, and may be interrupted during the write. Depending on the nature of the data, this may or may not cause a bug.

    Regarding calculation-intensive threads: it is good practice to add a Sleep(0) here and there in the code. This will allow a context switch in case a thread of higher or equal priority is waiting for execution. (Just note that if the thread calling Sleep(0) has higher priority than the one waiting to run, Sleep(0) will do nothing.)
  • manucpmanucp Member Posts: 34
    Yep, I forgot to mention the "volatile" keyword. And also that the Intel compiler behaves somehow better that the Microsoft one.

    The updates to memory occurs in a minimum block of 32 bits (64 bits for &4-bit cpus). And it is not agood idea to create a lot of synchro objects. I hink it is better to encapsulate several interrelated variables in a class and use a critical section to lock and unlock access to the whole group.
    :
    You can get odd problems if you don't set variables that are shared
    : between two threads to "volatile". The compiler might decide to
    : optimize a variable, since it can't grasp the meaning of the code:
    : for the optimizer it might look as if a variable isn't going to be
    : used, and therefore it removes it from the final program. It has no
    : idea that two threads will be running at once, affecting the same
    : variable.
    :
    : It is also good practice to protect [italic]all[/italic] shared
    : variables by mutecies, and not just the ones that are written to by
    : more than one thread. Because the writing thread will most likely
    : need more than one CPU instruction to write to the variable, and may
    : be interrupted during the write. Depending on the nature of the
    : data, this may or may not cause a bug.
    :
    : Regarding calculation-intensive threads: it is good practice to add
    : a Sleep(0) here and there in the code. This will allow a context
    : switch in case a thread of higher or equal priority is waiting for
    : execution. (Just note that if the thread calling Sleep(0) has higher
    : priority than the one waiting to run, Sleep(0) will do nothing.)

  • EshazearEshazear Member Posts: 2
    Thanks guys - It seems that the volatile qualifier fixed the problem.

    I was wondering though - is it possible to declare a volatile array on the heap?
    (In my situation I'd like to have a dynamic array of counters, whose size is only decided at runtime)
    Unfortunately, the compiler doesn't seem to allow volatile objects to be allocated explicitly (ie. not on stack) - using the new keyword..


  • manucpmanucp Member Posts: 34
    I think you are misunderstanding the use of 'volatile'. It tells the compiler that an object is to be refreshed from and to memory every time the cpu uses it. Even if the previos instruction has used it. It doesn't matter which type of memory (stack or heap) you are using.

    Note this statements:

    volatile int *mynum; // content of the object pointed at is volatile
    int * volatile mynum; // the pointer itself is volatile (the address stored)

    Now make it point to your volatile data as needed


    : Thanks guys - It seems that the volatile qualifier fixed the problem.
    :
    : I was wondering though - is it possible to declare a volatile array
    : on the heap?
    : (In my situation I'd like to have a dynamic array of counters, whose
    : size is only decided at runtime)
    : Unfortunately, the compiler doesn't seem to allow volatile objects
    : to be allocated explicitly (ie. not on stack) - using the new
    : keyword..
    :
    :
    :
  • LundinLundin Member Posts: 3,711
    : The updates to memory occurs in a minimum block of 32 bits (64 bits
    : for &4-bit cpus). And it is not agood idea to create a lot of
    : synchro objects. I hink it is better to encapsulate several
    : interrelated variables in a class and use a critical section to lock
    : and unlock access to the whole group.


    The problem with critical sections is that they lock out all threads, and not just the one using the variables. Nor can you wait for them efficiently with WaitForSingleObject() etc. I'd advise to use the mutex object instead.

    Apart from that, it is indeed a good advice to encapsulate the shared variables in a class, since it would force the caller to access them in a thread-safe way.
  • manucpmanucp Member Posts: 34

    : The problem with critical sections is that they lock out all
    : threads, and not just the one using the variables. Nor can you wait
    : for them efficiently with WaitForSingleObject() etc. I'd advise to
    : use the mutex object instead.
    :
    : Apart from that, it is indeed a good advice to encapsulate the
    : shared variables in a class, since it would force the caller to
    : access them in a thread-safe way.

    I suppose that it depends in the sort of thread or process we are talking about. The 'dispatcher' sort of thread who receives some sort of input and does things in response is more efficiently synchronized with mutex (and the other synchro objects) because you can catch another sort of events: Messages, IOCompletion, APCs. But I think that the 'worker' sort of thread who needs to access a resource in an orderly manner and at very precise times is more efficient if you use critical sections (no need to add wait loops else ): it has to do what it needs to do when it needs it after all. It is necessary to take in account the number of threads trying to lock the object, of course, or you get a pretty bottle neck.

    Different situations need different solutions: A hammer is good for nails but a screwdriver not and it doesn't mean that the second is a bad tool :-)
  • LundinLundin Member Posts: 3,711
    A global mutex that is used by all threads is the very same thing as a critical section, so why not use it everywhere? If the thread needs the data at a give time, you just use WaitForSingleObject() with a specified timeout. But then, Windows is no RTOS and you can't expect to get things done in time.
Sign In or Register to comment.