Presentation is loading. Please wait.

Presentation is loading. Please wait.

Multithreaded Programming With the Win32 API Andrew Tucker Andrew Tucker Debugger Development Lead March 13, 1998.

Similar presentations


Presentation on theme: "Multithreaded Programming With the Win32 API Andrew Tucker Andrew Tucker Debugger Development Lead March 13, 1998."— Presentation transcript:

1 Multithreaded Programming With the Win32 API Andrew Tucker Andrew Tucker Debugger Development Lead March 13, 1998

2 What We Will Cover Intro to Multithreaded Concepts Starting and Stopping ThreadsStarting and Stopping Threads SynchronizationSynchronization Debugging and Testing IssuesDebugging and Testing Issues Interprocess CommunicationInterprocess Communication Advanced Topics and Additional ResourcesAdvanced Topics and Additional Resources

3 Caveats Multithreaded feature sets differ between NT, Win95 and CE and versions of the same OS Multithreaded feature sets differ between NT, Win95 and CE and versions of the same OS

4 Intro to Multithreaded Concepts What is a thread? “path of execution in a process” owned by a single process owned by a single process all processes have main thread, some have more all processes have main thread, some have more has full access to process address space has full access to process address space Process1 Process2 ProcessNMain T1 T2 T3 Main T1 Operating System

5 Intro to Multithreaded Concepts Scheduling - cooperative vs preemptive Preemptive - allow a thread to execute for a specified amount of time and then automatically performs a “context switch” to change to a new thread (e.G. NT, win95, WCE)Preemptive - allow a thread to execute for a specified amount of time and then automatically performs a “context switch” to change to a new thread (e.G. NT, win95, WCE) Cooperative - performs context switch only when the user specifies (“manually scheduled”)Cooperative - performs context switch only when the user specifies (“manually scheduled”) Win16 is neither: multitasking, but not multithreadWin16 is neither: multitasking, but not multithread

6 Starting and Stopping Threads CreateThread API HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpsa, // pointer to thread security attributes DWORD dwStackSize, // initial thread stack size, in bytes LPTHREAD_START_ROUTINE lpStartAddress, // pointer to thread function LPVOID lpParameter, // argument for new thread DWORD dwCreationFlags, // creation flags LPDWORD lpThreadId // pointer to returned thread identifier ); _beginthreadex CRT function unsigned long _beginthreadex( void *security, unsigned stack_size, unsigned ( __stdcall *start_address )( void * ), void *arglist, unsigned initflag, unsigned *thrdaddr ); So, what’s the difference?

7 Starting and Stopping Threads Difference is the initialization of the CRT library Difference is the initialization of the CRT library Linking with multithreaded CRT is not enough Linking with multithreaded CRT is not enough DWORD ThreadFunc(PVOID pv) { char *psz = strtok((char*)pv, “;”); while ( psz ) { …. process data …. psz = strtok(NULL. “;”); } int main() { // BUG - use _beginthreadex to ensure thread safe CRT HANDLE hthrd1 = CreateThread( … ThreadFunc … ); HANDLE hthrd2 = CreateThread( … ThreadFunc … ); } _beginthreadex creates a structure to ensure global and static CRT variables are thread-specific_beginthreadex creates a structure to ensure global and static CRT variables are thread-specific

8 Starting and Stopping Threads Thread functions have the following prototype: Thread functions have the following prototype: DWORD WINAPI ThreadFunc(PVOID pv); It is very useful to use pv as a pointer to a user-defined structure to pass extra data It is very useful to use pv as a pointer to a user-defined structure to pass extra data

9 Starting and Stopping Threads A return will automatically call the respective _endthreadex or EndThread API A return will automatically call the respective _endthreadex or EndThread API A return does not close the handle from the creation routine (user must call CloseHandle to avoid resource leak) A return does not close the handle from the creation routine (user must call CloseHandle to avoid resource leak) Threads should be self-terminating (avoid the TerminateThread API) Threads should be self-terminating (avoid the TerminateThread API)

10 Starting and Stopping Threads Reasons to avoid the TerminateThread API: If the target thread owns a critical section, it will not be releasedIf the target thread owns a critical section, it will not be released If the target thread is executing certain kernel calls, the kernel state for the thread’s process could be inconsistentIf the target thread is executing certain kernel calls, the kernel state for the thread’s process could be inconsistent If the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLLIf the target thread is manipulating the global state of a shared DLL, the state of the DLL could be destroyed, affecting other users of the DLL

11 Starting and Stopping Threads Using a C++ member as a thread function (fixing the ‘this’ problem): Using a C++ member as a thread function (fixing the ‘this’ problem): class ThreadedClass { public: ThreadedClass(); BOOL Start(); void Stop(); private: HANDLE m_hThread; BOOL m_bRunning; static UINT WINAPI StaticThreadFunc(LPVOID lpv); DWORD MemberThreadFunc(); }; UINT WINAPI ThreadedClass::StaticThreadFunc( LPVOID lpv) { ThreadedClass *pThis = (ThreadedClass *)lpv; return pThis->MemberThreadFunc(); } DWORD ThreadedClass::MemberThreadFunc() { while ( m_bRunning ) { … do processing... } BOOL ThreadedClass::Start(DWORD dwStart) { UINT nTID; m_hThread = (HANDLE)_beginthreadex(NULL, 0, StaticThreadFunc, this, 0, &nTID) return TRUE; } void ThreadedClass::Stop() { m_bRunning = FALSE; // wait for thread to finish DWORD dwExitCode; GetExitCodeThread(m_hThread, &dwExitCode); while ( dwExitCode == STILL_ACTIVE ) { GetExitCodeThread(m_hThread, &dwExitCode); } m_hThread = 0; } int main() { ThreadedClass tc1, tc2; tc1.Start(5); tc2.Start(5000); Sleep(3000); tc1.Stop(); tc2.Stop(); return 0; }

12 Starting and Stopping Threads SuspendThread and ResumeThread allow you to pause and restart any thread SuspendThread and ResumeThread allow you to pause and restart any thread Suspension state is a count not a boolean - calls should be balanced Suspension state is a count not a boolean - calls should be balanced Example: hitting a bp in a debugger causes all current threads to be suspended and resumed on step or go Example: hitting a bp in a debugger causes all current threads to be suspended and resumed on step or go

13 Starting and Stopping Threads GetCurrentThread and GetCurrentThreadId are useful for identifying current thread GetCurrentThread and GetCurrentThreadId are useful for identifying current thread GetExitCodeThread is useful for determining if a thread is still alive GetExitCodeThread is useful for determining if a thread is still alive GetThreadTimes is useful for performance analysis and measurement GetThreadTimes is useful for performance analysis and measurement

14 Synchronization Used to coordinate the activities of concurrently running threads Used to coordinate the activities of concurrently running threads Always avoid coordinating with a poll loop when possible for efficiency reasons Always avoid coordinating with a poll loop when possible for efficiency reasons

15 Synchronization Interlocked functions Interlocked functions Critical Sections Critical Sections Wait functions Wait functions Mutexes Mutexes Semaphores Semaphores Events Events Waitable Timers Waitable Timers

16 Synchronization Interlocked functions: Interlocked functions: PVOID InterlockedCompareExchange(PVOID *destination, PVOID Exchange, PVOID Comperand) if ( *Destination == Comperand ) *Destination = Exchange; LONG InterlockedExchange(LPLONG Target, LONG Value ) *Target = Value; LONG InterlockedExchangeAdd(LPLONG Addend, LONG Increment) *Addend += Increment; LONG InterlockedDecrement(LPLONG Addend) *Addend -= 1; LONG InterlockedIncrement(LPLONG Addend) *Addend += 1; All operations are guaranteed to be “atomic” - the entire routine will execute w/o a context switch All operations are guaranteed to be “atomic” - the entire routine will execute w/o a context switch

17 Synchronization Why must simple operations like incrementing an integer be “atomic”? Multiple CPU instructions are required for the actual implementation. If we retrieved a variable and were then preempted by a thread that changed that variable, we would be using the wrong value.

18 Synchronization A critical section is a tool for guaranteeing that only one thread is executing a section of code at any time A critical section is a tool for guaranteeing that only one thread is executing a section of code at any time void InitializeCriticalSection(LPCRITICAL_SECTION lpCritSec) void DeleteCriticalSection(LPCRITICAL_SECTION lpCritSec) void EnterCriticalSection(LPCRITICAL_SECTION lpCritSec) void LeaveCriticalSection(LPCRITICAL_SECTION lpCritSec) BOOL TryEnterCriticalSection(LPCRITICAL_SECTION lpCritSec)

19 Synchronization EnterCriticalSection will not block on nested calls as long as the calls are in the same thread. Calls to LeaveCriticalSection must still be balanced EnterCriticalSection will not block on nested calls as long as the calls are in the same thread. Calls to LeaveCriticalSection must still be balanced

20 Synchronization Critical section example: counting source lines in multiple files Critical section example: counting source lines in multiple files DWORD g_dwTotalLineCount = 0; DWORD CountLinesThread(PVOID pv) { PSTR pszFileName = (PSTR)pv; DWORD dwCount; dwCount = CountSourceLines(pszFileName); EnterCriticalSection(&cs); g_dwTotalLineCount += dwCount; LeaveCriticalSection(&cs); return 0; } void UpdateSourceLineCount() { FileNameList fnl; HANDLE *pHandleList; CRITICAL_SECTION cs; GetFileNameList(&fnl); InitializeCriticalSection(&cs); pHandleList = malloc(sizeof(HANDLE)*fnl.Size()); for ( int i = 0; i < FileNameList.Size(); i++) pHandleList[i] = _beginthreadex(…CountLinesThread, fnl[i]…); //we’ll cover this shortly… WaitForMultipleObjects(fnl.Size(), pHandleList, TRUE, INFINITE); DeleteCriticalSection(&cs); …process g_dwTotalLineCount... }

21 Synchronization Wait functions - allow you to pause until one or more objects become signaled Wait functions - allow you to pause until one or more objects become signaled At all times, an object is in one of two states: signaled or nonsignaled At all times, an object is in one of two states: signaled or nonsignaled Picture signaled as a flag being raised and nonsignaled as a flag being lowered - the wait functions are watching for a flag to be raised Picture signaled as a flag being raised and nonsignaled as a flag being lowered - the wait functions are watching for a flag to be raised

22 Synchronization Types of objects that can be “waited on“: ProcessesProcesses ThreadsThreads Console InputConsole Input File Change NotificationsFile Change Notifications Mutexes*Mutexes* Semaphores*Semaphores* Events* Events* Waitable Timers*Waitable Timers*

23 Synchronization Processes and threads are non-signaled at creation and become signaled when they terminate Processes and threads are non-signaled at creation and become signaled when they terminate

24 Synchronization DWORD WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds) Returns WAIT_OBJECT_0 if hHandle has become signaled or WAIT_TIMEOUT if dwMilliseconds elapsed and the object is still non- signaledReturns WAIT_OBJECT_0 if hHandle has become signaled or WAIT_TIMEOUT if dwMilliseconds elapsed and the object is still non- signaled DWORD WaitForMultipleObjects(DWORD nCount, HANDLE *pHandles, BOOL bWaitAll, DWORD dwMilliseconds) If bWaitAll is FALSE and one of the object handles was signaled, the return value minus WAIT_OBJECT_0 is the array index of that handle. If bWaitAll is TRUE and all of the objects become signaled the return value minus WAIT_OBJECT_0 is a valid index into the handle array. If dwMilliseconds elapsed and no object was signaled, WAIT_TIMEOUT is returned. nCount can be no more than MAXIMUM_WAIT_OBJECTS (currently defined as 64)If bWaitAll is FALSE and one of the object handles was signaled, the return value minus WAIT_OBJECT_0 is the array index of that handle. If bWaitAll is TRUE and all of the objects become signaled the return value minus WAIT_OBJECT_0 is a valid index into the handle array. If dwMilliseconds elapsed and no object was signaled, WAIT_TIMEOUT is returned. nCount can be no more than MAXIMUM_WAIT_OBJECTS (currently defined as 64) INFINITE can be used as a timeout valueINFINITE can be used as a timeout value

25 Synchronization Mutexes provide mutually exclusive access to an object (hence the name) Mutexes provide mutually exclusive access to an object (hence the name) HANDLE CreateMutex(LPSECURITY)ATTRIBUTES lpsa, BOOL bInitialOwner, LPCTSTR lpName) Ownership is equivalent to the nonsignaled state - if bInitialOwner is TRUE the creation state of the mutex is nonsignaled Ownership is equivalent to the nonsignaled state - if bInitialOwner is TRUE the creation state of the mutex is nonsignaled lpName is optional lpName is optional ReleaseMutex is used to end ownership ReleaseMutex is used to end ownership

26 Synchronization What’s the difference between a critical section and a mutex? A mutex is a OS kernel object, and can thus be used across process boundaries. A critical section is limited to the process in which it was created

27 Synchronization Two methods to get a handle to a named mutex created by another process: OpenMutex - returns handle to an existing mutex OpenMutex - returns handle to an existing mutex CreateMutex - creates or returns handle to an existing mutex. GetLastError will return ERROR_ALREADY_EXISTS for the latter case CreateMutex - creates or returns handle to an existing mutex. GetLastError will return ERROR_ALREADY_EXISTS for the latter case

28 Synchronization Comparing mutex and critical section performance Comparing mutex and critical section performance

29 Synchronization A mutex will not block on nested calls as long as they are in the same thread. ReleaseMutex calls must still be balanced A mutex will not block on nested calls as long as they are in the same thread. ReleaseMutex calls must still be balanced Examples of when to use a mutex: Examples of when to use a mutex: Error logging system that can be used from any processError logging system that can be used from any process Detecting multiple instances of an applicationDetecting multiple instances of an application

30 Synchronization Semaphores allow access to a resource to be limited to a fixed number Semaphores allow access to a resource to be limited to a fixed number HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTE lpsa, LONG cSemInitial, LONG cSemMax, LPCTSTR lpName) Semaphores are in the signaled state when their available count is greater than zero Semaphores are in the signaled state when their available count is greater than zero ReleaseSemaphore is used to decrement usage ReleaseSemaphore is used to decrement usage Conceptually, a mutex is a binary semaphore Conceptually, a mutex is a binary semaphore

31 Synchronization Named semaphores can be used across process boundaries with OpenSemaphore and CreateSemaphore Named semaphores can be used across process boundaries with OpenSemaphore and CreateSemaphore Can be used to solve the classic “single writer / multiple readers” problem Can be used to solve the classic “single writer / multiple readers” problem

32 Synchronization Example: limiting number of entries in a queue Example: limiting number of entries in a queue const int QUEUE_SIZE = 5; HANDLE g_hSem = NULL; long g_iCurSize = 0; UINT WINAPI PrintJob(PVOID pv) { WaitForSingleObject(g_hSem, INFINITE); InterlockedIncrement(&g_iCurSize); printf("%08lX - entered queue: size = %d\n", GetCurrentThreadId(), g_iCurSize ); Sleep(500); // print job.... InterlockedDecrement(&g_iCurSize); long lPrev; ReleaseSemaphore(g_hSem, 1, &lPrev); return 0; } int main() { const int MAX_THREADS = 64; HANDLE hThreads[MAX_THREADS]; g_hSem = CreateSemaphore(NULL, QUEUE_SIZE, QUEUE_SIZE, NULL ); UINT dwTID; for ( int i = 0; i < MAX_THREADS; i++ ) hThreads[i] = (HANDLE)_beginthreadex(NULL, 0, PrintJob, NULL, 0, &dwTID); WaitForMultipleObjects(MAX_THREADS, hThreads, TRUE, INFINITE); return 0; }

33 Synchronization Events provide notification when some condition has been met Events provide notification when some condition has been met HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpsa, BOOL bManualReset, BOOL bInitialState, LPCTSTR lpName) If bInitialState is TRUE, object is created in the signaled state If bInitialState is TRUE, object is created in the signaled state bManualReset specifies the type of event requested bManualReset specifies the type of event requested

34 Synchronization Two kinds of event objects: Auto reset - when signaled it is automatically changed to a nonsignaled state after a single waiting thread has been releasedAuto reset - when signaled it is automatically changed to a nonsignaled state after a single waiting thread has been released Manual reset - when signaled it remains in the signaled state until it is manually changed to the nonsignaled stateManual reset - when signaled it remains in the signaled state until it is manually changed to the nonsignaled state

35 Synchronization Named event objects can be used across process boundaries with OpenEvent and CreateEvent Named event objects can be used across process boundaries with OpenEvent and CreateEvent SetEvent sets the object state to signaled SetEvent sets the object state to signaled ResetEvent sets the object state to nonsignaled ResetEvent sets the object state to nonsignaled PulseEvent conceptually calls SetEvent/ResetEvent sequentially, but... PulseEvent conceptually calls SetEvent/ResetEvent sequentially, but...

36 Synchronization PulseEvent vs SetEvent

37 Synchronization Example: displaying OutputDebugString text without a debugger Example: displaying OutputDebugString text without a debugger int main() { HANDLE hAckEvent, hReadyEvent; PSTR pszBuffer; hAckEvent = CreateEvent(NULL, FALSE, FALSE, “DBWIN_BUFFER_READY”); if (GetLastError() == ERROR_ALREADY_EXISTS) { // handle multiple instance case } hReadyEvent = CreateEvent(NULL, FALSE, FALSE, “DBWIN_DATA_READY”); pszBuffer = /* get pointer to data in memory mapped file */; SetEvent(hAckEvent); while ( TRUE ) { int ret = WaitForSingleObject(hReadyEvent, INFINITE); if ( ret != WAIT_OBJECT_0) { // handle error } else { printf(pszBuffer); SetEvent(hAckEvent); }

38 Synchronization Waitable timers are kernel objects that provide a signal at a specified time interval Waitable timers are kernel objects that provide a signal at a specified time interval HANDLE CreateWaitableTimer(LPSECURITY_ATTRIBUTES lpsa, BOOL bManualReset, LPCTSTR lpName) Manual/auto reset behavior is identical to events Manual/auto reset behavior is identical to events Time interval is specified with SetWaitableTimer Time interval is specified with SetWaitableTimer

39 Synchronization BOOL SetWaitableTimer(HANDLE hTimer, LARGE_INTEGER *pDueTime, LONG lPeriod, PTIMERACPROUTINE pfnCompletion, PVOID pArg, BOOL fResume) pDueTime specifies when the timer should go off for the first time (positive is absolute, negative is relative) pDueTime specifies when the timer should go off for the first time (positive is absolute, negative is relative) lPeriod specifies how frequently to go off after the initial time lPeriod specifies how frequently to go off after the initial time fResume controls whether the system is awakened when timer is signaled fResume controls whether the system is awakened when timer is signaled

40 Synchronization Consecutive calls to SetWaitableTimer overwrite each other Consecutive calls to SetWaitableTimer overwrite each other CancelWaitabletimer stops the timer so that it will not go off again (unless SetWaitableTimer is called) CancelWaitabletimer stops the timer so that it will not go off again (unless SetWaitableTimer is called)

41 Synchronization Example: firing an event every N seconds Example: firing an event every N seconds const int MAX_TIMES = 3; const int N = 10; DWORD WINAPI ThreadFunc(PVOID pv) { HANDLE hTimer = (HANDLE)pv; int iCount = 0; DWORD dwErr = 0; while (TRUE) { if ( WaitForSingleObject(hTimer, INFINITE) == WAIT_OBJECT_0 ) { … handle timer event... if ( ++iCount >= MAX_TIMES ) break; } return 0; } int main() { HANDLE hTimer = CreateWaitableTimer(NULL, FALSE, NULL); LARGE_INTEGER li; const int nNanosecondsPerSecond = 10000000; __int64 qwTimeFromNow = N * nNanosecondsPerSecond; qwTimeFromNow = -qwTimeFromNow; li.LowPart = (DWORD)(qwTimeFromNow & 0xFFFFFFFF); li.HighPart = (DWORD)(qwTimeFromNow >> 32); SetWaitableTimer(hTimer, &li, N * 1000, NULL, NULL, FALSE); DWORD dwTID; HANDLE hThread = CreateThread(NULL, 0, ThreadFunc, hTimer, 0, &dwTID); WaitForSingleObject(hThread, INFINITE); return 0; }

42 Debugging and Testing Issues Very difficult, if not impossible, to reproduce and test every possible deadlock and race condition Very difficult, if not impossible, to reproduce and test every possible deadlock and race condition Stepping through code will not necessarily help Stepping through code will not necessarily help OutputDebugString can be very helpful OutputDebugString can be very helpful

43 Don’t underestimate the value of peer review and code inspection Don’t underestimate the value of peer review and code inspection Hint: after every wait or release ask yourself “what if a context switch occurred here” Hint: after every wait or release ask yourself “what if a context switch occurred here” Debugging and Testing Issues

44 Interprocess Communication Tools to provide the ability to pass data between processes or machines Tools to provide the ability to pass data between processes or machines Types of IPC: Types of IPC: ClipboardClipboard DDEDDE OLEOLE Memory Mapped FilesMemory Mapped Files MailslotsMailslots PipesPipes RPCRPC SocketsSockets WM_COPYDATAWM_COPYDATA

45 Advanced Topics Thread local storage Thread local storage UI vs worker threads UI vs worker threads CreateRemoteThread CreateRemoteThread Scheduling and Get/SetThreadPriority) Scheduling and Get/SetThreadPriority) Fibers (NT only) Fibers (NT only) Asynchronous procedure calls Asynchronous procedure calls

46 Resources Advanced Windows by Jeff Richter Advanced Windows by Jeff Richter Win32 System Programming by Johnson Hart Win32 System Programming by Johnson Hart Win32 Multithreaded Programming by Aaron Cohen and Mike Woodring Win32 Multithreaded Programming by Aaron Cohen and Mike Woodring Windows NT Programming in Practice by editors of WDJ (including Paula T) Windows NT Programming in Practice by editors of WDJ (including Paula T)


Download ppt "Multithreaded Programming With the Win32 API Andrew Tucker Andrew Tucker Debugger Development Lead March 13, 1998."

Similar presentations


Ads by Google