Concurrency and Synchronization, страница 2

UINT ComputeThreadProc(LPVOID pParam)

{  volatile int nTemp;

for(g_nCount = 0; g_nCount < g_nMaxCount ;  

::InterlockedIncrement((long*) &g_nCount)) 

{ for(nTemp = 0; nTemp < 10000; nTemp++)

{ ;   // use up CPU cycles

}

}

::PostMessage((HWND) pParam, 

WM_THREADFINISHED, 0,0);

g_nCount = 0;

return 0;

}

There are some points to explain about this code.

·  What is InterlockedIncrement  and why do we use it?

·  What is volatile and why do we use it?

·  Why PostMessage instead of SendMessage?

·  How do we define and handle a user-defined message?

·  How is pParam going to be equal to the view window's handle?

·  Why use the view window's handle instead of a pointer to the view window as a CWnd object?

We will take these questions one at a time.

·  What is InterlockedIncrement  and why do we use it?

The worker threads should be terminated from within--there is no

terminate member of the CWinThread class.   When the master thread wants to terminate the worker thread,  it does so indirectly, by setting g_nCount to a value greater than g_nMaxCount.  That should cause the worker thread's main loop to complete and the thread to terminate naturally.

Suppose we just use  g_nCount++   to increment g_nCount.  What actually happens in the assembly code generated by g_nCount++ is that the value of  g_nCount is loaded into a register, then the register is incremented, then the contents of the register are stored in g_nCount

Suppose the worker thread is interrupted after the value, say 41, is loaded into the register.   The main thread wants to terminate the worker,  and therefore it changes the value of g_nCount  to a value greater than g_nMaxCount.   The worker thread gets control again and stores the incremented value, 42, back in g_nMaxCount.   The attempt to terminate the thread has failed.   This bug would not be reproducible and would not happen a very high percentage of the time, but if this code ran on a web server, with one thread per client, it would happen often enough to make plenty of trouble.

InterlockedIncrement is there just to solve this problem.


·  What is volatile and why do we use it?

The key word volatile tells the compiler that this value is subject to modification by other threads or processes, and therefore should not be stored in a register, and code using it should not be optimized, but left as written.   In this case, its purpose is to prevent a modern optimizing compiler from deleting the do-nothing loop of 10000 steps.   It has nothing to do with thread safety in this example.   Since optimizations are not applied in the debug version of your MFC program, if you did not use volatile, you wouldn't notice anything until/unless you compiled the release version.


·  Why PostMessage instead of SendMessage?

PostMessage will put the message in the application message queue, and return immediately.  The application's message pump will call the handler when the message comes to the head of the queue.

SendMessage will call the message handler immediately, and not return until the message handler is finished.  That is, IF the message handler is in the same thread.   But in this case, it is in a different thread.  There are several complicated pages in Jeff Richter's book Advanced Windows Programming about how SendMessage works when sending a message to a different thread.  The most important point is that the thread will not be interrupted and forced to process the message immediately, if it is doing something else.  Instead, the sending thread will be forced to wait until the other thread is ready to process messages.  For that reason PostMessage is better, unless it makes no sense to go on until the message has been processed.

Also, even within the same thread, you shouldn't call message handlers while a modal dialog is up--messages should wait until the dialog has been terminated.   Besides, there's no reason for the worker thread to give up control now.  It should go ahead and terminate.   The message is supposed to tell the master that the worker thread HAS TERMINATED, not "is about to terminate".