Windows NT Systems Programming: Spring 1999

[ Home | Syllabus | Course Notes | Assignments | Search]


Multiple Clients


Left Overs


Multiple Clients = multiple threads OR multiple Transactions

We focus on threads. Why Threads?

 


ThreadClient2.jpg (41019 bytes)


Shared Address Space

  1. Global variables
    volatile - tells system not to store data in registers - always write to memory
  2. HEAP

Thread States

 


threadState2.jpg (55576 bytes)


Creating a Thread

Thread runs a special function with one parameter.
This parameter can be a pointer to any arbitrary data structure.

 


Waiting for a thread to finish

WIN32 supports a general WaitFor mechanism which works for many different kinds of events.

 


Thread safe functions

PROBLEM: writing functions which can be simultaneously used by multiple threads. The difficulty is the use of global variables. Simultaneous access to the same global variable can cause failures.

Many systems libraries were designed in a single thread environment. Need to use thread-safe versions. OR use thread synchronization.

_beginthreadex instead of CreateThread

_endthreadex instead of ExitThread - to use thread safe C libraries. see page 209 for details

Related to re-entrant code.

Let's run some experiments to show the problem. The design of revealing experiments is critical in gaining an understanding of large complex systems.


Single Thread/Process Global Variable

(Source Code is here)
// ...
#include <windows.h>	// for defining HANDLE, Sleep 

const int SLEEP_TIME = 1000;

int global1; // file global 
// This variable is accessible by all functions in this file
// this program demonstrates that the main thread can change the value that
// the "testThread" thread sees

void testThread(int* myId ); // thread prototype


void main()
{
	HANDLE testHandle; 
	DWORD threadId;
	int id = 1;

	global1 = 33;
	testHandle = CreateThread  (0, // no child process cannot inherit
			0, // gives same stack space as parent thread
			(LPTHREAD_START_ROUTINE) testThread,
			&id, // Pass in one parameter
			 0,  // Put into ready Q
			&threadID);  // system wide thread ID
	Sleep  (SLEEP_TIME);	// sleep for one second - give thread a chance
	global1 = 55;
	WaitForSingleObject(testHandle, INFINITE); 
}

void testThread(int* myId) 
{
	cout << "Started Thread ID: " << *myId << endl;
	cout << "BEFORE: Global1 is: " << global1 << endl;
	Sleep(2*SLEEP_TIME);	// give main thread chance to change global1
	cout << "AFTER: Global1 is: " << global1 << endl;
	
}

What will the output?


Multiple Threads/Global Variable

(Source Code is here)

void testThread(DWORD myId); 
void main()
{
	HANDLE testHandles[2]; 
	testHandles[0] = CreateThread(0,0,
		(LPTHREAD_START_ROUTINE) testThread,
		(void*)id, 0, &threadId);
	id++;
	testHandles[1]  = CreateThread(0,0,
		(LPTHREAD_START_ROUTINE) testThread,
		(void*)id, 0, &threadId);
	Sleep(SLEEP_TIME);	// sleep for one second
	WaitForMultipleObjects(2, testHandles, TRUE /* wait for all */, INFINITE); 
}
// Separate file
//**** all threads started with this code will share the file global variable "global2"
// demonstrates that two instances of testThread both access the same variable
// and thus can potentially interfere with each other/ (Race condition - one of the
// complexities of writing distributed systems code)

int global2 = 12345;

void testThread(DWORD myId)
{
	int id = myId; 

	cout << "Started Thread ID: " << id << endl;
	cout << "BEFORE: Global2 is: " << global2 << endl;
	global2 = id; 
	Sleep(id*SLEEP_TIME);	// give other thread chance
	cout << "AFTER: Global2 is: " << global2 << endl;
	
}
// What will be the output?
// does it matter if the two threads sleep differently?

Thread Local Storage (TLS)

(Source Code is here)
DWORD tlsIndex; 
void testThread(int* myId); 
void main()
{
// . . .
	tlsIndex = TlsAlloc(); 
//tls gives each thread a private global variable
	testHandles[0] = CreateThread(0,0,
		(LPTHREAD_START_ROUTINE) testThread,
		&id, 0, &threadId);
	
	Sleep(SLEEP_TIME); // give first thread a chance to save its parameter 
//. . . separate file for thread code
extern DWORD tlsIndex; 

void checkGlobal(int); 

void testThread(int* myId)
{
	int id = *myId; 
	int global3 = id;
	TlsSetValue(tlsIndex, &global3); 

	checkGlobal(id);
	
}

void checkGlobal(int id)
{
	int* tls; 

	tls = (int*) TlsGetValue(tlsIndex); 
	cout << "BEFORE: Global3 is: " << *tls << endl;
	*tls = id*1000;
	Sleep(id*SLEEP_TIME);	// give main thread chance to change  global1
	cout << "AFTER: Global3 is: " << *tls << endl;
}
// What output?
// does the timing matter?

Version 3 Multiple Server main program

Now use threads to modify version2 to handle mulitple simultaneous clients.

NO CHANGE ON THE CLIENT SIDE!

Small change on the server side.

(Source Code is here)

// Prototype to thread call
VOID  talkToClient(VOID *  cs);

int main()
{
// . . .
	DWORD threadID;
	HANDLE clientThread; 
	
	serverSocket = getServerSocket(); // contains the "listen" API
	
	while (true) {
		// accept a connection from a client
		clientSocket=accept(serverSocket,
			(LPSOCKADDR) &clientSockAddr,
			&addrLen);
//. . .spawn a thread to handle this client.
		clientThread = CreateThread  (0, // no child process cannot inherit
			0, // gives same stack space as parent thread
			(LPTHREAD_START_ROUTINE) talkToClient,
			(VOID *) clientSocket, // Pass in one parameter
			 0,  // Put into ready Q
			&threadID);  // system wide thread ID
// . . .
	}
	
}


VOID talkToClient(VOID *cs)
{
	SOCKET socket2client=(SOCKET)cs; 
	NET_CONTROL request;

	while(TRUE) // loop until killed
	{
		request = getControl(socket2client);
		if (request == FAILURE) {
			shutdown(socket2client, 2);  // 2 shutdown send and receive
			closesocket(socket2client);
			ExitThread(0); 
		}
		switch (request)
// . . .

Synchronization


Critical Sections

VARIABLE:

FUNCTIONS:


Critical Section Example (listing 6.4)

#include <windows.h>
//. . .
volatile INT count;
CRITICAL_SECTION critSec;

void CountThread(INT iterations)
{
//. . .
  for (i=0; i<iterations; i++)
  {
    EnterCriticalSection(&critSec);
    x=count;
    x++;
    count=x;
    LeaveCriticalSection(&critSec);
  }
}


void main(void)
{
 
// . . .
  InitializeCriticalSection(&critSec);

  for (i=0; i<numThreads; i++)
  {
    // create a thread and pass it the pointer 
    // to its "params" struct
    handles[i]=CreateThread(0, 0,
      (LPTHREAD_START_ROUTINE) CountThread, 
      (VOID *) 25000, 0, &threadID);
  }

  WaitForMultipleObjects(numThreads, handles, 
    TRUE, INFINITE);  

  DeleteCriticalSection(&critSec);
}

MUTEX (BINARY SEMAPHORE)

Can be used across processes by naming it.

VARIABLE:

FUNCTIONS:


MUTEX example

void CountThread(INT iterations)
{
  INT i;
  INT x;
  HANDLE mutex;

  mutex = CreateMutex(0, FALSE, "counterMutex");  // only first thread actually creates it

  for (i=0; i<iterations; i++)
  {
    WaitForSingleObject(mutex, INFINITE); 
    x=count;
    x++;
    count=x;
    ReleaseMutex(mutex); 
  }

  CloseHandle(mutex); 
}


Events

In automatic mode, like a MUTEX, in manual can be used to release a number of waiting processes/threads.

 


WaitFor...

Used to wait for many things (p. 257)

Also allows a time out value.

Multiple Objects can be waited for



Copyright chris wild 1999.
For problems or questions regarding this web contact [Dr. Wild].
Last updated: January 27, 1999.