A Complete Threading Example

 < Free Open Study > 



Before we add the complexity of COM threading into your life, let's look at a COM-free threading example. Assume you have a Win32 console application that defines a structure named FOOFIGHTER which contains two fields. Also assume the program has declared a single global instance of this item:

// An application wide global structure. struct FOOFIGHTER {      int one;      int two; } Foo; int main(int argc, char* argv[]) {      return 0; }

The primary thread of this application is the program's entry point: main(). When a thread wishes to spawn additional threads, the developer can make use of the CreateThread() function. The parameters of this function break down as so:

// A thread can create another thread using this Win32 API call. HANDLE CreateThread(   LPSECURITY_ATTRIBUTES lpThreadAttributes,   // Security attributes   DWORD dwStackSize,                          // Initial thread stack size   LPTHREAD_START_ROUTINE lpStartAddress,      // Pointer to thread function   LPVOID lpParameter,                         // Argument for new thread   DWORD dwCreationFlags,                      // Creation flags   LPDWORD lpThreadId);                        // Pointer to receive thread ID

To illustrate how multiple threads can lead to data corruption, let's set up main() to create some additional threads, all of which will access the shared global FOOFIGHTER structure. Notice in the code below how CreateThread() returns a HANDLE. This handle will be sent into CloseHandle() to clean up, so be sure to hold onto this value:

 // Main thread. int main(int argc, char* argv[]) {      // Make some secondary threads.      for(int i = 0; i < 5; i++)      {           DWORD dw;           HANDLE h = CreateThread(NULL, 0, ThreadProc, (void*)i, 0, &dw);           CloseHandle(h);      }      return 0; }

The third parameter of CreateThread() specifies the name of the function that will perform the work of the new secondary threads; here it is ThreadProc(). The name of this function can be anything at all but must have the correct signature. Our thread procedure will increment each field of the FOOFIGHTER structure by one and print out the results. To simulate a lengthy task, the Sleep() method is called between field access:

// The thread procedure. DWORD WINAPI ThreadProc(void *p) {      // Access field one.      cout<< "[S] Foo.one++ is: " << Foo.one++ << endl;      Sleep(500);      // Access field two.      cout<< "[S] Foo.two++ is: " << Foo.two++ << endl;      Sleep(500);      return 0; }

Now, to illustrate the problem of multiple threads accessing the same point of data, the primary thread (main() ) will also increment the fields of FOOFIGHTER by one:

// Main thread will now access the same data as the secondary threads. int main(int argc, char* argv[]) {      for(int i = 0; i < 5; i++)      {           // Access field one.           cout<< "[P] Foo.one++ is: " << Foo.one++ << endl;           Sleep(500);           // Access field two.           cout<< "[P] Foo.two++ is: " << Foo.two++ << endl;           Sleep(500);           // Make a new thread.           DWORD dw;           HANDLE h = CreateThread(NULL, 0, ThreadProc, (void*)i, 0, &dw);           CloseHandle(h);      }      // Wait for all threads to finish.      Sleep(5000);      return 0; } 

If we run the program, we suddenly have a number of threads all accessing the same single global instance of FOOFIGHTER. The Windows scheduler will swap between active threads, giving ample time for data corruption. Here is some possible output:

click to expand
Figure 7-4: When multiple threads access global data not secured by a Win32 locking primitive, bad things happen.

One would hope to see access to the fields of FOOFIGHTER incremented safely, but the job of doing so is up to the Windows developer. In other words, we need to secure our shared data using a Win32 locking primitive, such as the CRITICAL_SECTION.

Locking Down the FOOFIGHTER

To provide safe data access to the FOOFIGHTER structure among multiple threads, we can wrap a CRITICAL_SECTION around the field manipulation logic. Working with a critical section requires the use of four Win32 functions:

CRITICAL_SECTION Function

Meaning in Life

InitializeCriticalSection()

Initializes a CRITICAL_SECTION structure for use by the program.

DeleteCriticalSection()

Destroys a CRITICAL_SECTION.

EnterCriticalSection()

Locks down a block of code for safe data access.

LeaveCriticalSection()

Unlocks a block of code from safe data access.

Here is an updated version of the previous program, which wraps access to the FOOFIGHTER structure using a Win32 critical section. To make it more interesting, the primary thread asks whether safe or unsafe data access is desired. Based on this flag, we will either safely lock access to the fields of FOOFIGHTER or allow all threads to mangle the data accordingly:

// Safe data access tester. CRITICAL_SECTION cs;          // A global critical section. int ans;                      // Stores safe or mangled access request. DWORD WINAPI ThreadProc(void *p) {      // Enter the safety of a critical section.      if(ans == 1)           EnterCriticalSection(&cs);           cout<< "[S] Foo.one++ is: " << Foo.one++ << endl;           Sleep(500);           cout<< "[S] Foo.two++ is: " << Foo.two++ << endl;           Sleep(500);      // All done with accessing FOOFIGHTER, leave critical section.      if(ans == 1)           LeaveCriticalSection(&cs);      return 0; } int main(int argc, char* argv[]) {      // Ask user what type of access they want.      cout<< "Preserve (1) or Mangle (0) data? ";      cin >> ans;      cout << endl << endl;      // Initialize critical section on startup.      if(ans == 1)           InitializeCriticalSection(&cs);      // Make some secondary threads and access FOOFIGHTER.      for(int i = 0; i < 5; i++)      {           // Enter the safety of a critical section.           if(ans == 1)                EnterCriticalSection(&cs);                cout<< "[P] Foo.one++ is: " << Foo.one++ << endl;                Sleep(500);                cout<< "[P] Foo.two++ is: " << Foo.two++ << endl;                Sleep(500);           // All done with accessing FOOFIGHTER, leave critical section.           if(ans == 1)                LeaveCriticalSection(&cs);           DWORD dw;           HANDLE h = CreateThread(NULL, 0, ThreadProc, (void*)i, 0, &dw);           CloseHandle(h);      }      Sleep(5000);      // Kill critical section on shutdown.      if(ans == 1)           DeleteCriticalSection(&cs);      return 0; } 

If the user now elects to preserve data access using a CRITICAL_SECTION, the output would look like the following:

click to expand
Figure 7-5: The thread-safe FOOFIGHTER.

As you can see, the FOOFIGHTER structure is now safely wrapped by a CRITICAL_ SECTION, which provides safe access by multiple threads. When developers create applications with numerous threads of execution, all shared data must be locked in a similar manner.

 On the CD   The FOOFIGHTER example program is included on your CD-ROM under the Labs\Chapter 07\Threads subdirectory.



 < Free Open Study > 



Developer's Workshop to COM and ATL 3.0
Developers Workshop to COM and ATL 3.0
ISBN: 1556227043
EAN: 2147483647
Year: 2000
Pages: 171

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net