Windows Programming/Multitasking

Processes and Threads

edit

Current versions of Windows are multitasking operating systems. In this chapter, we will discuss some of the tools and API functions that are involved in multitasking, threading, and synchronization for use in Windows.

First, let's explain a little bit of terminology. A process is a single program, with a single entry point, and a single exit point. A thread is a part of a process. A process has at least 1 thread; but can have more than 1 thread. When created, processes and threads run automatically and are alloted time slices of execution time by the scheduler in a round-robin fashion. The operating system may activate and deactivate any thread or process at any time. For this reason, we will need to control access to program resources such as global memory and output devices.

If multiple processes are working together, the resulting group is known as a job. Jobs can also be managed by Windows.

Managing Processes

edit
  • CreateProcess, etc

Jobs

edit

Threads

edit

A few functions are used when dealing with threads, like CreateThread, ResumeThread, SuspendThread, and TerminateThread.


If the volatility of threads is disconcerting, Windows also provides an execution object known as a fiber that only runs when activated by the parent thread.


 

To do:
TerminateThread is unsafe.


CreateThread

edit

The CreateThread function accepts a few parameters:

  • a pointer to the function to be executed within the thread.
  • a pointer to a variable to be passed to the thread's function.

The CreateThread function creates a new thread for a process. The creating thread must specify the starting address of the code that the new thread is to execute. Typically, the starting address is the name of a function defined in the program code. This function takes a single parameter and returns a DWORD value. A process can have multiple threads simultaneously executing the same function.

The following example demonstrates how to create a new thread that executes the locally defined function, ThreadFunc.

DWORD WINAPI ThreadFunc( LPVOID lpParam )  
{ 
   char szMsg[80];
   wsprintf( szMsg, "ThreadFunc: Parameter = %d\n", *lpParam ); 
   MessageBox( NULL, szMsg, "Thread created.", MB_OK );
   return 0; 
} 
VOID main( VOID ) 
{ 
   DWORD dwThreadId, dwThrdParam = 1; 
   HANDLE hThread; 
   hThread = CreateThread( 
       NULL,                        // no security attributes 
       0,                           // use default stack size  
       ThreadFunc,                  // thread function 
       &dwThrdParam,                // argument to thread function 
       0,                           // use default creation flags 
       &dwThreadId);                // returns the thread identifier 

  // Check the return value for success. 
  if (hThread == NULL) 
     ErrorExit( "CreateThread failed." ); 
  CloseHandle( hThread );
}

For simplicity, this example passes a pointer to a DWORD value as an argument to the thread function. This could be a pointer to any type of data or structure, or it could be omitted altogether by passing a NULL pointer and deleting the references to the parameter in ThreadFunc. It is risky to pass the address of a local variable if the creating thread exits before the new thread, because the pointer becomes invalid. Instead, either pass a pointer to dynamically allocated memory or make the creating thread wait for the new thread to terminate. Data can also be passed from the creating thread to the new thread using global variables. With global variables, it is usually necessary to synchronize access by multiple threads.

Note:
User-Mode Scheduling (UMS) is a light-weight mechanism that applications can use to schedule their own threads. Available only on 64-bit versions of Windows 7 and Windows Server 2008 R2.

Passing Parameters

edit

Thread Local Storage (TLS)

edit

A single process can (usually) spawn 2000 threads. This is because the default stack size allocated by the linker is 1MB per thread. 1MB x 2000 is around 2GB which is the maximum a user-process can access. Following is a sample code which spawn many threads till a limit is reached:

 // Threads.cpp : Defines the entry point for the console application.
 //
 #include "stdafx.h"
 #include <process.h>
 #include <conio.h>
 #include <windows.h>
 unsigned int __stdcall myThread (LPVOID params) {
 	printf ("Inside thread");
 	Sleep (INFINITE);
 	return 0;
 }
 int _tmain(int argc, _TCHAR* argv[])
 {
 	int i = 0;
 	unsigned int dwThreadId = 0;
 	while (true) {
 		HANDLE h = (HANDLE) _beginthreadex (NULL, 0, myThread, NULL, 0, &dwThreadId);
 		if (h == NULL) break;
 		++i;
 	}
 	printf ("\n\nI spawned %d threads", i);
 	Sleep (10000);
 	return 0;
 }

Priority

edit

Synchronization

edit
Events
edit
Mutexes
edit
Critical Sections
edit
Spin Locks
edit

Debugging

edit

The naming of a thread, a special feature, that only works with a few windows debuggers, is extremely useful for debugging threads, especially when debugging programs with a lot of threads. It consists in generating a special runtime exception (0x406D1388) which allows the program to pass the name for the thread to the debugger.

//! NameThread - Names a threads for the debugger.
//! 
//! Usage: NameThread( -1, "MyThreadIsNowNamed" );
//! 
void NameThread( unsigned long a_ThreadID, char* a_ThreadName )
{

  typedef struct tagTHREADNAME_INFO
  {
    unsigned long m_Type;// must be 0x1000
    char*         m_Name;// pointer to name (in user addr space)
    unsigned long m_ThreadID;// thread ID (-1=caller thread)
    unsigned long m_Flags;// reserved, must be zero
  } THREADNAME_INFO;

  THREADNAME_INFO info;
  info.m_Type = 0x1000;
  info.m_Name = a_ThreadName;
  info.m_ThreadID = a_ThreadID;
  info.m_Flags = 0;

  __try
  {
    RaiseException( 0x406D1388, 0, sizeof(info)/sizeof(unsigned long),
                    (unsigned long*) &info );
  }
  __except( EXCEPTION_CONTINUE_EXECUTION )
  {
  }

}

Fiber

edit

Next Chapter

edit