How do I get started working with threads?

Threads are really not that difficult, and worker threads in particular are quite easy (worker threads don't have a user interface, they just do work in the background). MFC isn't really threadsafe, and rather than spend time remembering what I can and can't do with MFC and threads, I just avoid the use of MFC within threads: my C++ apps tend to have an MFC core with pure C/SDK worker threads.

Essentially a worker thread consists of a function, which is specified when the thread is created (using CreateThread for an SDK program, or AfxBeginThread for an MFC program). When the function exits, the thread is done. CreateThread looks complex, but most of its parameters can be left at the defaults.

The only mildly tricky part is ensuring that the threads get killed off if your app is closed down: this usually requires that your main thread set an "event" (see CreateEvent/SetEvent) which the worker(s) can check - if it/they see the event set they drop out of their processing loop, exit and therefore cease.

Of course if you want multiple threads accessing a single resource (such as a data structure), you'll need to read up on synchronisation using Critical Section objects. Actually getting to the shared object is trivial, as threads share the same address space. Think of it like this : processes are the unit of addressability ; threads are the unit of executability.

Check out these fragments of code, which are from a multithreaded application I wrote some time ago. The application creates a closure event thus :

m_hCloseEvent = CreateEvent (NULL, TRUE, FALSE, "AG61_BN9_RMM_CLOSE");

and this event is used when closing down the threads, as we'll see below. This is the part that starts the threads :

CWinThread * pNewThread;

...This code then appears inside a loop which sets up the threads...

m_ateThreadTable [m_iThreadCount].iPortNum = iComNum;
m_ateThreadTable [m_iThreadCount].iThreadState = 0;
m_ateThreadTable [m_iThreadCount].hStopEvent = m_hCloseEvent;
m_ateThreadTable [m_iThreadCount].hCreatorWindow = GetSafeHwnd();
m_ateThreadTable [m_iThreadCount].uFlagBits = 0;

pNewThread = AfxBeginThread (WorkerRunFunction, 
                             (LPVOID)&(m_ateThreadTable [m_iThreadCount]));

// If the new thread started ok, save it's pointer in our
// table, and clear its auto-delete flag so we can shut it
// down manually. MFC defaults to auto-deleting the thread 
// object as soon as its run function exits, which can be
// problematic for the kind of controlled shutdown I want.

if (pNewThread)
{
   m_ateThreadTable [m_iThreadCount].pThread = pNewThread;
   pNewThread->m_bAutoDelete = FALSE;
}

So you can see that the code is setting up a table of structures with data about each thread, then calling AfxBeginThread to create the thread, passing it the address of the structure which contains information it will need internally, such as the handle of the application closure event. The thread has no problem accessing this structure, because all threads share the same address space. The application's main thread then saves a pointer to the worker thread, which it can use when shutting down to ensure that all threads close when instructed using the closure event: if any threads fails to shutdown before some timeout, the app can call TerminateThread to forcibly kill it.

The worker thread run function looks like this (note the thread-local variables, which contain data specific to this thread):

static UINT __declspec( thread ) mg_uState;
static int  __declspec( thread ) mg_uFlags;

// ------------------------------------------------------------------------

UINT WorkerRunFunction (LPVOID pParam)
{
   HANDLE hComPort = 0;
   HANDLE aWaitHandles [2];
   OVERLAPPED ovlRead = {0};

   // Convert the generic void parameter pointer into a pointer
   // to the actual data type passed.

   TThreadEntry * pData = (TThreadEntry*)pParam;

   ... Parameter validation code omitted...

   // Create the event used for overlapped i/o. This is a comms thread.

   ovlRead.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL);

   // Our thread consists of a loop, waiting for either a character
   // on the serial port or closure of the app. Note that the closure 
   // event is first, since WaitForMultipleObjects gives "precedence"
   // starting from the zeroth end of its handle array.

   aWaitHandles [0] = pData->hStopEvent;
   aWaitHandles [1] = ovlRead.hEvent;

   ... Comms code (port setup) omitted...

   while (!bStopped)
   {
      ....< could additionally check the closure event here>...

      if (ReadFile (...)
      {
         // received data, store it and look at the buffer.
      }
      else
      {
         dwErr = GetLastError();
         switch (dwErr)
         {
            case ERROR_IO_PENDING :
               // Wait for either data to arrive, or this
               // thread to be terminated.
               dwErr = WaitForMultipleObjects (2, 
                                               aWaitHandles, 
                                               FALSE, 
                                               INFINITE);
               switch (dwErr)
               {
                  case WAIT_OBJECT_0: // CLOSE event
                     bStopped = TRUE;
                     break;

                  case WAIT_OBJECT_0+1: // READ event
                     GetOverlappedResult (hComPort, 
                                          &ovlRead, 
                                          &dwBytesRead, 
                                          FALSE);
                     ResetEvent (ovlRead.hEvent);
                     ... < do some processing here >...

Get the picture ? Our thread basically consists of a loop, which sleeps until either a character is received or the app closure event gets set by the main thread. So how does the main thread organise closure of its kiddies ? Like this :

void CMyMainThreadDlg::DestroyThreads ()
{
   HANDLE ahTempHandles [MAX_PORTS];
   int iHandles;

   // Now we have to wait _synchronously_ for all the threads to 
   // run to completion. Make up an array of thread handles for 
   // WaitForMultipleObjects to wait on.

   iHandles = 0;
   memset (ahTempHandles, 0, sizeof(ahTempHandles));

   for (int i=0; 
        i < MAX_PORTS; 
        i++)
   {
      if (m_ateThreadTable [i].pThread)
      {
         ahTempHandles [iHandles] = (m_ateThreadTable [i].pThread)-> m_hThread;
        iHandles++;
      }
   }

   if (iHandles > 0)
   {
      // There are threads to shut down - set the closure event.
      SetEvent (m_hCloseEvent);

      switch (WaitForMultipleObjects (iHandles, 
                                      ahTempHandles, 
                                      TRUE, 
                                      5000))
      {
         case WAIT_FAILED:
            wsprintf (m_szDebug, "WaitForMultipleObjects failed, GLE=%u",
                      GetLastError()) ;
            DebugMessage (m_szDebug);
            break;

         case WAIT_OBJECT_0:
            DebugMessage ("All COM threads closed OK");
            break;

         case WAIT_ABANDONED:
            DebugMessage ("Wait abandoned");
            break;

         case WAIT_TIMEOUT:
            DebugMessage ("Timed out waiting for COM threads");
            HandleLaggards ();
            break;

         default:
            break;
      }

      if (ResetEvent (m_hCloseEvent))
         DebugMessage ("Reset close event for next use");
      else
      {
         wsprintf (m_szDebug, "attempt to reset close event failed, GLE=%u",
                   GetLastError()) ;
         DebugMessage (m_szDebug);
      }
   }
   else
   {
      DebugMessage ("No active threads, exiting");
   }
}

Additional Info

If you have access to the MSDN, you should also read the article "Multithreading Dos and Don'ts".

Download