Threads of Execution via win32

The Windows NT kernel implements a threading schedule by taking control of the processor when a quantum is finished, pausing the running thread, and resuming the scheduled thread. Every time the thread is changed is known as a context switch. In order to perform a context switch, the Windows NT kernel must save the contents of the processor’s register for the pausing thread, and restore the processor’s register contents for the resuming thread. Of course, this imposes a time penalty, which is a factor when deciding upon thread quantum size.

Adding to the complexity of thread scheduling is the fact that some processes and threads are more important than other ones. Reflecting this, we can assign the Windows process and thread objects a priority. Priorities tell the scheduler to run some threads longer than others, in essence.

Note as well that interprocess context switches are more costly than intraprocess context switches.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

int _tmain(int argc, _TCHAR* argv[])
{
	//initialize the SYSTEM_INFO data structure
	SYSTEM_INFO structSI;

	GetSystemInfo(&structSI);

	//display number of processors
	printf("%d processors.\n", structSI.dwNumberOfProcessors);

	return 0;
}

One of the nice features of a thread is the ability to suspend itself or be suspended. When a thread is suspended, the kernel knows not to include that thread in the schedule until it resumes. Once a thread switches from the suspended to the running state, it simply takes its place in the run schedule.

Threads are kernel objects, and the process that creates them references them through object handles. An active thread object is either suspended, ready to run, or running on a processor.

We use the CreateThread() API to start a new thread of execution. The CreateThread() system call makes a new entry in the system’s list of threads to execute. The CreateThread() call takes five parameters.

The first parameter describes the security to associate the new thread object. This information tells the system what accounts to allow and deny access to the thread for modification and synchronization. The first parameter is of type LPSECURITY_ATTRIBUTES. By specifying NULL for this parameter, we use the default security.

The second parameter is a DWORD value that defines the size of the stack the thread will receive. The process typically accepts the default by setting 0 for this parameter.

The third parameter is the address of the function to execute. Once this function finishes executing, the thread will terminate. Often, this function is called ThreadProc. A ThreadProc function is a user-defined function that returns a DWORD value and accepts a single parameter of type LPVOID.

The fourth parameter is a pointer to the value we wish to pass into the ThreadProc function, if any.

The second-to-last parameter is a process creation flag, of which there two that are allowable to use, the main one being CREATE_SUSPENDED. The final parameter, which is optional, is a pointer to a DWORD value to store the thread identifier for the new thread.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

//prototype for function we will send 
//to CreateThread()
DWORD WINAPI ThreadProc(LPVOID lpParam);

int _tmain(int argc, _TCHAR* argv[])
{
	HANDLE hNewThread;

	hNewThread = CreateThread(
		NULL, //default security
		0, //default stack
		ThreadProc, //thread function name 
		NULL, //no argument to thread function
		0, //default creation flags
		NULL //don't care about thread ID
		);

	if(hNewThread != INVALID_HANDLE_VALUE){
		//success!
		printf("New thread created...\n");
	} else {
                exit(EXIT_FAILURE);
        }

	//do something in this thread
	for(int i = 0; i < 5; i++){
		printf("Hello from the old thread.\t");
		printf("(thread ID %d)\n", GetCurrentThreadId());
		Sleep(100);
	}

	//does the new thread get to finish? 
	//most likely not!
	CloseHandle(hNewThread);

	return 0;
}


DWORD WINAPI ThreadProc(LPVOID lpParam){
	for(int i = 0; i < 15; i++){
		Sleep(100);
		printf("\tHello from the new thread.\t");
		printf("(thread ID %d)\n", GetCurrentThreadId());
	}

	//return 0
	return 0;
}

Note that WINAPI is an alias, er, macro, for __stdcall. The __stdcall macro specifies that the callee cleans the stack, specifically, the parameters that were pushed onto the stack. The __stdcall convention is mainly used by the Windows API.

As we saw when running the program above, we need the parent thread to wait for the caller thread to exit… at least, if we want to be sure the new thread finishes doing whatever it is that it is doing. We do this with the WaitForSingleObject() function. The WaitForSingleObject() function waits until the object specified by the first HANDLE parameter is in the signaled state, or until the time out interval indicated in the second parameter has elapsed.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

//prototype
DWORD WINAPI ThreadProc(LPVOID lpParam);

int _tmain(int argc, _TCHAR* argv[])
{
	DWORD dwRValue;
	HANDLE hThread = CreateThread(
		NULL, //default security
		0, //default stack size
		ThreadProc, //start address of function
		NULL, // no parameters
		0, //no creation flags
		NULL //don't need the creation ID
		);

	if(hThread == INVALID_HANDLE_VALUE){
		printf("Failed to create new thread...\n");
		exit(EXIT_FAILURE);
	}

	do {
		dwRValue = WaitForSingleObject(hThread, 200);
		printf("\n yo, you done yet??? \n");
	} while(dwRValue != WAIT_OBJECT_0);
	
	printf("New thread is finished!\n");

	CloseHandle(hThread);

	return 0;

}

DWORD WINAPI ThreadProc(LPVOID lpParam){
	for(int i = 0; i < 20; i++){
		printf("Doing Thangs... ");
		Sleep(100);
	}
	return 0;
}

Note that the Do…While loop above might not be so hot if we weren’t sure ahead of time that the new thread was going to be running for longer than one timeout interval.

The WaitForSingleObject() function’s return value is of type DWORD. The WAIT_TIMEOUT value indicates that the function has exceeded its timeout parameter. The WAIT_OBJECT_0, that’s a 0 as in zero by the way, value indicates the indicated object is now in the signaled state.

#include <Windows.h>
#include <stdlib.h>
#include <stdio.h>

//let's define a custom parameter 
//to send to our new thread
struct structThreadParam{
	char cLetter;
	int iNumber;
};

DWORD WINAPI ThreadProc(LPVOID lpParam);

int _tmain(int argc, _TCHAR* argv[])
{
	structThreadParam structParam;
	structParam.cLetter = 'Z';
	structParam.iNumber = 90210;

	HANDLE hNewThread = CreateThread(
		NULL,
		0, 
		ThreadProc,
		&structParam,
		0,
		NULL
		);

	if(hNewThread != INVALID_HANDLE_VALUE){
		printf("New thread created.\n");
	} else {
		exit(EXIT_FAILURE);
	}

	//let's wait as long as it takes
	WaitForSingleObject(hNewThread, INFINITE);

	return 0;

}

DWORD WINAPI ThreadProc(LPVOID lpParam){
	//note that conversion is implicit in C
	//but let's be verbose
	structThreadParam *structPtr = (structThreadParam*)lpParam;
	printf("In thread %d: ", GetCurrentThreadId());
	printf("Received %c%d \n", structPtr->cLetter, structPtr->iNumber);

	return 0;
}

Note that we can use the INFINITE value to tell WaitForSingleObject() to wait until the specified thread is finished.

Interested in learning standard C? Check out my book at amazon.com/Big-Als-C-Standard-ebook/dp/B00A4JGE0M/

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s