Home arrow C++ arrow Page 6 - Multithreading in C++
C++

Multithreading in C++


Multithreading is growing in importance in modern programming for a variety of reasons, not the least of which being that Windows supports multithreading. While C++ does not feature built-in support for multithreading, it can be used to created multithreaded programs, which is the subject of this article. It is taken from chapter three of The Art of C++, written by Herbert Schildt (McGraw-Hill/Osborne, 2004; ISBN: 0072255129).

Author Info:
By: McGraw-Hill/Osborne
Rating: 5 stars5 stars5 stars5 stars5 stars / 229
July 14, 2005
TABLE OF CONTENTS:
  1. · Multithreading in C++
  2. · An Overview of the Windows Thread Functions
  3. · Priority Classes
  4. · The Windows Synchronization Objects
  5. · The Thread Control Panel
  6. · A Closer Look at the Thread Control Panel
  7. · Demonstrating the Control Panel
  8. · A Multithreaded Garbage Collector
  9. · Synchronizing Access to gclist
  10. · The Entire Multithreaded Garbage Collector
  11. · Using the Multithreaded Garbage Collector

print this article
SEARCH DEVARTICLES

Multithreading in C++ - A Closer Look at the Thread Control Panel
(Page 6 of 11 )

Letís take a closer look at the thread control panel. It begins by defining the following global definitions:

const int NUMPRIORITIES = 5;
const int OFFSET = 2;
// Array of strings for priority list box.
char priorities[NUMPRIORITIES][80] = {
  "Lowest",
  "Below Normal",
  "Normal",
  "Above Normal",
  "Highest"
};

The priorities array holds strings that correspond to a threadís priority setting. It initializes the list box inside the control panel that displays the current thread priority. The number of priorities is specified by NUMPRIORITIES, which is 5 for Windows. Thus, NUMPRIORITIES defines the number of different priorities that a thread may have. (If you adapt the code for use with another operating system, a different value might be required.) Using the control panel, you can set a thread to one of the following priorities:

  THREAD_PRIORITY_HIGHEST

  THREAD_PRIORITY_ABOVE_NORMAL

  THREAD_PRIORITY_NORMAL

  THREAD_PRIORITY_BELOW_NORMAL

  THREAD_PRIORITY_LOWEST

The other two thread priority settings:

  THREAD_PRIORITY_TIME_CRITICAL

  THREAD_PRIORITY_IDLE

are not supported because, relative to the control panel, they are of little practical value. For example, if you want to create a time-critical application, you are better off making its priority class time-critical.

OFFSET defines an offset that will be used to translate between list box indexes and thread priorities. You should recall that normal priority has the value zero. In this example, the highest priority is THREAD_PRIORITY_HIGHEST, which is 2. The lowest priority is THREAD_PRIORITY_LOWEST, which is Ė2. Because list box indexes begin at zero, the offset is used to convert between indexes and priority settings.

Next, the ThrdCtrlPanel class is declared. It begins as shown here:

// A Thread Control Panel Class.
class ThrdCtrlPanel {
  // Information about the thread under control.
  struct ThreadInfo {
    HANDLE hThread; // handle of thread
    int priority;   // current priority
    bool suspended; // true if suspended
    ThreadInfo(HANDLE ht, int p, bool s) {
      hThread = ht;
      priority = p;
      suspended = s;
    }
  };
  // This map holds a ThreadInfo for each
  // active thread control panel.
  static map<HWND, ThreadInfo> dialogmap;

Information about the thread under control is contained within a structure of type ThreadInfo. The handle of the thread is stored in hThread. Its priority is stored in priority. If the thread is suspended, then suspended will be true. Otherwise, suspended will be false.

The static member dialogmap is an STL map that links the thread information with the handle of the dialog box used to control that thread. Because there can be more than one thread control panel active at any given time, there must be some way to determine which thread is associated with which panel. It is dialogmap that provides this linkage.

The ThreadCtrlPanel Constructor

The ThrdCtrlPanel constructor is shown here. The constructor is passed the instance handle of the application and the handle of the thread being controlled. The instance handle is needed to create the control panel dialog box.

// Create a thread control panel. ThrdCtrlPanel::ThrdCtrlPanel(HINSTANCE hInst,
                             HANDLE hThrd)
{
  ThreadInfo ti(hThrd,
                GetThreadPriority(hThrd)+OFFSET,
                false);
  // Owner window is desktop.
  HWND hDialog = CreateDialog(hInst, "ThreadPanelDB",
                       NULL,
                      (DLGPROC) ThreadPanel);
  // Put info about this dialog box in the map. 
  dialogmap.insert(pair<HWND, ThreadInfo>(hDialog, ti));
  // Set the control panel's title.
  char str[80] = "Control Panel for Thread ";
  char str2[4];
  _itoa(dialogmap.size(), str2, 10);
  strcat(str, str2);
  SetWindowText(hDialog, str);
  // Offset each dialog box instance.
  MoveWindow(hDialog, 30*dialogmap.size(),
             30*dialogmap.size(),
             300, 250, 1);
  // Update priority setting in the list box. 
  SendDlgItemMessage(hDialog, IDD_LB, LB_SETCURSEL, 
                          (WPARAM) ti.priority, 0);
  // Increase priority to ensure control. You can
  // change or remove this statement based on your
  // execution environment.
  SetThreadPriority(GetCurrentThread(),
                    THREAD_PRIORITY_ABOVE_NORMAL);
}

The constructor begins by creating a ThreadInfo instance called ti that contains the initial settings for the thread. Notice that the priority is obtained by calling GetThreadPriority( ) for the thread being controlled. Next, the control panel dialog box is created by calling CreateDialog( ). CreateDialog( ) is a Windows API function that creates a modeless dialog box, which makes it independent of the application that creates it. The handle of this dialog box is returned and stored in hDialog. Next, hDialog and the thread information contained in ti are stored in dialogmap. Thus, the thread is linked with the dialog box that controls it.

Next, the title of the dialog box is set to reflect the number of the thread. The number of the thread is obtained based on the number of entries in dialogmap. An alternative that you might want to try implementing is to explicitly pass a name for each thread to the ThrdCtrlPanel constructor. For the purposes of this chapter, simply numbering each thread is sufficient.

Next, the control panelís position on the screen is offset a bit by calling MoveWindow( ), another Windows API function. This enables multiple panels to be displayed without each one fully covering the one before it. The threadís priority setting is then displayed in the priority list box by calling the Windows API function SendDlgItemMessage( ).

Finally, the current thread has its priority increased to above normal. This ensures that the application receives enough CPU time to be responsive to user input no matter what is the priority level of the thread under control. This step may not be needed in all cases. You can experiment to find out.

The ThreadPanel( ) Function

ThreadPanel( ) is the Windows callback function that responds to user interaction with the thread control panel. Like all dialog box callback functions, it receives a message each time the user changes the state of a control. It is passed the handle of the dialog box in which the action occurred, the message, and any additional information required by the message. Its general mode of operation is the same as that for any other callback function used by a dialog box. The following discussion describes what happens for each message.

When the thread control panel dialog box is first created, it receives a WM_INITDIALOG message, which is handled by this case sequence:

caseWM_INITDIALOG:
  // Initialize priority list box.
  for(i=0; i<NUMPRIORITIES i++) {
    SendDlgItemMessage(hwnd, IDD_LB,
        LB_ADDSTRING, 0, (LPARAM) priorities[i]);
    }
    // Set Suspend and Resume buttons for thread.
    hpbSus = GetDlgItem(hwnd, IDD_SUSPEND);
    hpbRes = GetDlgItem(hwnd, IDD_RESUME);
    EnableWindow(hpbSus, true);  // enable Suspend
    EnableWindow(hpbRes, false); // disable Resume
    return 1;

This initializes the priority list box and sets the Suspend and Resume buttons to their initial states, which are Suspend enabled and Resume disabled.

Each user interaction generates a WM_COMMAND message. Each time this message is received, an iterator to this dialog boxís entry in dialogmap is retrieved, as shown here:

case WM_COMMAND:
  map<HWND, ThreadInfo>::iterator p = dialogmap.find(hwnd);

The information pointed to by p will be used to properly process each action. Because p is an iterator for a map, it points to an object of type pair, which is a structure defined by the STL. This structure contains two fields: first and second. These fields correspond to the information that comprises the key and the value, respectively. In this case, the handle is the key and the thread information is the value.

A code indicating precisely what action has occurred is contained in the low-order word of wParam, which is used to control a switch statement that handles the remaining messages. Each is described next.

When the user presses the Terminate button, the thread under control is stopped. This is handled by this case sequence:

case IDD_TERMINATE:
  TerminateThread(p->second.hThread, 0);
  // Disable Terminate button.
  hpbTerm = GetDlgItem(hwnd, IDD_TERMINATE);
  EnableWindow(hpbTerm, false); // disable
  // Disable Suspend and Resume buttons.
  hpbSus = GetDlgItem(hwnd, IDD_SUSPEND);
  hpbRes = GetDlgItem(hwnd, IDD_RESUME);
  EnableWindow(hpbSus, false); // disable Suspend
  EnableWindow(hpbRes, false); // disable Resume
  return 1;

The thread is stopped with a call to TerminateThread( ). Notice how the handle for the thread is obtained. As explained, because p is an iterator for a map, it points to an object of type pair that contains the key in its first field and the value in its second field. This is why the thread handle is obtained by the expression p->second.hThread. After the thread is stopped, the Terminate button is disabled.

Once a thread has been terminated, it cannot be resumed. Notice that the control panel uses TerminateThread( ) to halt execution of a thread. As mentioned earlier, this function must be used with care. If you use the control panel to experiment with threads of your own, you will want to make sure that no harmful side effects are possible.

When the user presses the Suspend button, the thread is suspended. This is accomplished by the following sequence:

case IDD_SUSPEND:
  SuspendThread(p->second.hThread);
  // Set state of the Suspend and Resume buttons.
  hpbSus = GetDlgItem(hwnd, IDD_SUSPEND);
  hpbRes = GetDlgItem(hwnd, IDD_RESUME);
  EnableWindow(hpbSus, false); // disable Suspend
  EnableWindow(hpbRes, true);  // enable Resume
  p->second.suspended = true;
  return 1;

The thread is suspended by a call to SuspendThread( ). Next, the state of the Suspend and Resume buttons are updated such that Resume is enabled and Suspend is disabled. This prevents the user from attempting to suspend a thread twice.

A suspended thread is resumed when the Resume button is pressed. It is handled by this code:

case IDD_RESUME:
  ResumeThread(p->second.hThread);
  // Set state of the Suspend and Resume buttons.
  hpbSus = GetDlgItem(hwnd, IDD_SUSPEND);
  hpbRes = GetDlgItem(hwnd, IDD_RESUME);
  EnableWindow(hpbSus, true);  // enable Suspend
  EnableWindow(hpbRes, false); // disable Resume
  p->second.suspended = false;
  return 1;

The thread is resumed by a call to ResumeThread( ), and the Suspend and Resume buttons are set appropriately.

To change a threadís priority, the user double-clicks an entry in the Priority list box. This event is handled as shown next:

case IDD_LB:
  // If a list box entry was double-clicked,
  // then change the priority.
  if(HIWORD(wParam)==LBN_DBLCLK) {
    p->second.priority = SendDlgItemMessage(hwnd,
                         IDD_LB, LB_GETCURSEL,
                         0, 0);
    SetThreadPriority(p->second.hThread,
                      p->second.priority-OFFSET);
  }
  return 1;

List boxes generate various types of notification messages that describe the precise type of event that occurred. Notification messages are contained in the high-order word of wParam. One of these messages is LBN_DBLCLK, which means that the user double-clicked an entry in the box. When this notification is received, the index of the entry is retrieved by calling the Windows API function SendDlgItemMessage( ), requesting the current selection. This value is then used to set the threadís priority. Notice that OFFSET is subtracted to normalize the value of the index.

Finally, when the user closes the thread control panel dialog box, the IDCANCEL message is sent. It is handled by the following sequence:

case IDCANCEL:
  // If thread is suspended when panel is closed,
  // then resume thread to prevent deadlock.
  if(p->second.suspended) {
    ResumeThread(p->second.hThread);
    p->second.suspended = false;
  }
  // Remove this thread from the list.
  dialogmap.erase(hwnd);
  // Close the panel.
  DestroyWindow(hwnd);
  return 1;

If the thread was suspended, it is restarted. This is necessary to avoid accidentally deadlocking the thread. Next, this dialog boxís entry in dialogmap is removed. Finally, the dialog box is removed by calling the Windows API function DestroyWindow( ).


blog comments powered by Disqus
C++ ARTICLES

- Intel Threading Building Blocks
- Threading Building Blocks with C++
- Video Memory Programming in Text Mode
- More Tricks to Gain Speed in Programming Con...
- Easy and Efficient Programming for Contests
- Preparing For Programming Contests
- Programming Contests: Why Bother?
- Polymorphism in C++
- Overview of Virtual Functions
- Inheritance in C++
- Extending the Basic Streams in C++
- Using Stringstreams in C++
- Custom Stream Manipulation in C++
- General Stream Manipulation in C++
- Serialize Your Class into Streams in C++

Watch our Tech Videos 
Dev Articles Forums 
 RSS  Articles
 RSS  Forums
 RSS  All Feeds
Write For Us 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Contact Us 
Site Map 
Privacy Policy 
Support 

Developer Shed Affiliates

 




© 2003-2017 by Developer Shed. All rights reserved. DS Cluster - Follow our Sitemap
Popular Web Development Topics
All Web Development Tutorials