[ Home | Syllabus | Course Notes | Assignments | Search]
We focus on threads. Why Threads?


Thread runs a special function with one parameter.
This parameter can be a pointer to any arbitrary data structure.
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.
(Source Code is here)
// ... #include <windows.h> // for defining HANDLE, Sleepconst 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?
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?
(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?
Now use threads to modify version2 to handle mulitple simultaneous clients.
NO CHANGE ON THE CLIENT SIDE!
Small change on the server side.
// Prototype to thread call VOIDtalkToClient(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)
// . . .
VARIABLE:
FUNCTIONS:
#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);
}
Can be used across processes by naming it.
VARIABLE:
FUNCTIONS:
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);
}
In automatic mode, like a MUTEX, in manual can be used to release a number of waiting processes/threads.
Used to wait for many things (p. 257)
Also allows a time out value.
Multiple Objects can be waited for