Multithreaded Programming With the Win 32 API Andrew

  • Slides: 46
Download presentation
Multithreaded Programming With the Win 32 API Andrew Tucker Debugger Development Lead March 13,

Multithreaded Programming With the Win 32 API Andrew Tucker Debugger Development Lead March 13, 1998

What We Will Cover • Intro to Multithreaded Concepts • Starting and Stopping Threads

What We Will Cover • Intro to Multithreaded Concepts • Starting and Stopping Threads • Synchronization • Debugging and Testing Issues • Interprocess Communication • Advanced Topics and Additional Resources

Caveats • Multithreaded feature sets differ between NT, Win 95 and CE and versions

Caveats • Multithreaded feature sets differ between NT, Win 95 and CE and versions of the same OS

Intro to Multithreaded Concepts What is a thread? “path of execution in a process”

Intro to Multithreaded Concepts What is a thread? “path of execution in a process” • owned by a single process • all processes have main thread, some have more • has full access to process address space Operating System Process 1 Main T 1 T 2 T 3 Process 2 Main Process. N Main T 1

Intro to Multithreaded Concepts Scheduling - cooperative vs preemptive • Preemptive - allow a

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, win 95, WCE) • Cooperative - performs context switch only when the user specifies (“manually scheduled”) • Win 16 is neither: multitasking, but not multithread

Starting and Stopping Threads Create. Thread API HANDLE Create. Thread( LPSECURITY_ATTRIBUTES lpsa, DWORD dw.

Starting and Stopping Threads Create. Thread API HANDLE Create. Thread( LPSECURITY_ATTRIBUTES lpsa, DWORD dw. Stack. Size, LPTHREAD_START_ROUTINE lp. Start. Address, LPVOID lp. Parameter, DWORD dw. Creation. Flags, LPDWORD lp. Thread. Id ); // pointer to thread security attributes // initial thread stack size, in bytes // pointer to thread function // argument for new thread // creation flags // 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?

Starting and Stopping Threads • • Difference is the initialization of the CRT library

Starting and Stopping Threads • • Difference is the initialization of the CRT library Linking with multithreaded CRT is not enough • _beginthreadex creates a structure to ensure global and static CRT variables are thread-specific DWORD Thread. Func(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 hthrd 1 = Create. Thread( … Thread. Func … ); HANDLE hthrd 2 = Create. Thread( … Thread. Func … ); }

Starting and Stopping Threads • Thread functions have the following prototype: DWORD WINAPI Thread.

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

Starting and Stopping Threads • • • A return will automatically call the respective

Starting and Stopping Threads • • • A return will automatically call the respective _endthreadex or End. Thread API A return does not close the handle from the creation routine (user must call Close. Handle to avoid resource leak) Threads should be self-terminating (avoid the Terminate. Thread API)

Starting and Stopping Threads Reasons to avoid the Terminate. Thread API: • If the

Starting and Stopping Threads Reasons to avoid the Terminate. Thread API: • If 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 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 DLL

Starting and Stopping Threads • Using a C++ member as a thread function (fixing

Starting and Stopping Threads • Using a C++ member as a thread function (fixing the ‘this’ problem): class Threaded. Class { public: Threaded. Class(); BOOL Start(); void Stop(); private: HANDLE m_h. Thread; BOOL m_b. Running; static UINT WINAPI Static. Thread. Func(LPVOID lpv); DWORD Member. Thread. Func(); }; UINT WINAPI Threaded. Class: : Static. Thread. Func( LPVOID lpv) { Threaded. Class *p. This = (Threaded. Class *)lpv; return p. This->Member. Thread. Func(); } DWORD Threaded. Class: : Member. Thread. Func() { while ( m_b. Running ) { … do processing. . . } } BOOL Threaded. Class: : Start(DWORD dw. Start) { UINT n. TID; m_h. Thread = (HANDLE)_beginthreadex(NULL, 0, Static. Thread. Func, this, 0, &n. TID) return TRUE; } void Threaded. Class: : Stop() { m_b. Running = FALSE; // wait for thread to finish DWORD dw. Exit. Code; Get. Exit. Code. Thread(m_h. Thread, &dw. Exit. Code); while ( dw. Exit. Code == STILL_ACTIVE ) { Get. Exit. Code. Thread(m_h. Thread, &dw. Exit. Code); } m_h. Thread = 0; } int main() { Threaded. Class tc 1, tc 2; tc 1. Start(5); tc 2. Start(5000); Sleep(3000); tc 1. Stop(); tc 2. Stop(); return 0; }

Starting and Stopping Threads • • • Suspend. Thread and Resume. Thread allow you

Starting and Stopping Threads • • • Suspend. Thread and Resume. Thread allow you to pause and restart any thread 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

Starting and Stopping Threads • • • Get. Current. Thread and Get. Current. Thread.

Starting and Stopping Threads • • • Get. Current. Thread and Get. Current. Thread. Id are useful for identifying current thread Get. Exit. Code. Thread is useful for determining if a thread is still alive Get. Thread. Times is useful for performance analysis and measurement

Synchronization • • Used to coordinate the activities of concurrently running threads Always avoid

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

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

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

Synchronization • Interlocked functions: PVOID Interlocked. Compare. Exchange(PVOID *destination, PVOID Exchange, PVOID Comperand) if

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

Synchronization Why must simple operations like incrementing an integer be “atomic”? Multiple CPU instructions

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.

Synchronization • A critical section is a tool for guaranteeing that only one thread

Synchronization • A critical section is a tool for guaranteeing that only one thread is executing a section of code at any time void Initialize. Critical. Section(LPCRITICAL_SECTION lp. Crit. Sec) void Delete. Critical. Section(LPCRITICAL_SECTION lp. Crit. Sec) void Enter. Critical. Section(LPCRITICAL_SECTION lp. Crit. Sec) void Leave. Critical. Section(LPCRITICAL_SECTION lp. Crit. Sec) BOOL Try. Enter. Critical. Section(LPCRITICAL_SECTION lp. Crit. Sec)

Synchronization • Enter. Critical. Section will not block on nested calls as long as

Synchronization • Enter. Critical. Section will not block on nested calls as long as the calls are in the same thread. Calls to Leave. Critical. Section must still be balanced

Synchronization • Critical section example: counting source lines in multiple files DWORD g_dw. Total.

Synchronization • Critical section example: counting source lines in multiple files DWORD g_dw. Total. Line. Count = 0; void Update. Source. Line. Count() { DWORD Count. Lines. Thread(PVOID pv) { File. Name. List fnl; HANDLE *p. Handle. List; CRITICAL_SECTION cs; PSTR psz. File. Name = (PSTR)pv; DWORD dw. Count; Get. File. Name. List(&fnl); Initialize. Critical. Section(&cs); p. Handle. List = malloc(sizeof(HANDLE)*fnl. Size()); dw. Count = Count. Source. Lines(psz. File. Name); Enter. Critical. Section(&cs); for ( int i = 0; i < File. Name. List. Size(); i++) p. Handle. List[i] = _beginthreadex(…Count. Lines. Thread, fnl[i]…); g_dw. Total. Line. Count += dw. Count; Leave. Critical. Section(&cs); //we’ll cover this shortly… Wait. For. Multiple. Objects(fnl. Size(), p. Handle. List, TRUE, INFINITE); return 0; Delete. Critical. Section(&cs); } …process g_dw. Total. Line. Count. . . }

Synchronization • • • Wait functions - allow you to pause until one or

Synchronization • • • 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 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

Synchronization Types of objects that can be “waited on“: • Processes • Threads •

Synchronization Types of objects that can be “waited on“: • Processes • Threads • Console Input • File Change Notifications • Mutexes* • Semaphores* • Events* • Waitable Timers*

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

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

Synchronization DWORD Wait. For. Single. Object(HANDLE h. Handle, DWORD dw. Milliseconds) • Returns WAIT_OBJECT_0

Synchronization DWORD Wait. For. Single. Object(HANDLE h. Handle, DWORD dw. Milliseconds) • Returns WAIT_OBJECT_0 if h. Handle has become signaled or WAIT_TIMEOUT if dw. Milliseconds elapsed and the object is still nonsignaled DWORD Wait. For. Multiple. Objects(DWORD n. Count, HANDLE *p. Handles, BOOL b. Wait. All, DWORD dw. Milliseconds) • If b. Wait. All 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 b. Wait. All 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 dw. Milliseconds elapsed and no object was signaled, WAIT_TIMEOUT is returned. n. Count can be no more than MAXIMUM_WAIT_OBJECTS (currently defined as 64) • INFINITE can be used as a timeout value

Synchronization • Mutexes provide mutually exclusive access to an object (hence the name) HANDLE

Synchronization • Mutexes provide mutually exclusive access to an object (hence the name) HANDLE Create. Mutex(LPSECURITY)ATTRIBUTES lpsa, BOOL b. Initial. Owner, LPCTSTR lp. Name) • • • Ownership is equivalent to the nonsignaled state - if b. Initial. Owner is TRUE the creation state of the mutex is nonsignaled lp. Name is optional Release. Mutex is used to end ownership

Synchronization What’s the difference between a critical section and a mutex? A mutex is

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

Synchronization Two methods to get a handle to a named mutex created by another

Synchronization Two methods to get a handle to a named mutex created by another process: • Open. Mutex - returns handle to an existing mutex • Create. Mutex - creates or returns handle to an existing mutex. Get. Last. Error will return ERROR_ALREADY_EXISTS for the latter case

Synchronization • Comparing mutex and critical section performance

Synchronization • Comparing mutex and critical section performance

Synchronization • • A mutex will not block on nested calls as long as

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

Synchronization • Semaphores allow access to a resource to be limited to a fixed

Synchronization • Semaphores allow access to a resource to be limited to a fixed number HANDLE Create. Semaphore(LPSECURITY_ATTRIBUTE lpsa, LONG c. Sem. Initial, LONG c. Sem. Max, LPCTSTR lp. Name) • • • Semaphores are in the signaled state when their available count is greater than zero Release. Semaphore is used to decrement usage Conceptually, a mutex is a binary semaphore

Synchronization • • Named semaphores can be used across process boundaries with Open. Semaphore

Synchronization • • Named semaphores can be used across process boundaries with Open. Semaphore and Create. Semaphore Can be used to solve the classic “single writer / multiple readers” problem

Synchronization • Example: limiting number of entries in a queue const int QUEUE_SIZE =

Synchronization • Example: limiting number of entries in a queue const int QUEUE_SIZE = 5; HANDLE g_h. Sem = NULL; long g_i. Cur. Size = 0; int main() { const int MAX_THREADS = 64; HANDLE h. Threads[MAX_THREADS]; UINT WINAPI Print. Job(PVOID pv) { Wait. For. Single. Object(g_h. Sem, INFINITE); g_h. Sem = Create. Semaphore(NULL, QUEUE_SIZE, NULL ); Interlocked. Increment(&g_i. Cur. Size); UINT dw. TID; for ( int i = 0; i < MAX_THREADS; i++ ) h. Threads[i] = (HANDLE)_beginthreadex(NULL, 0, Print. Job, NULL, 0, &dw. TID); printf("%08 l. X - entered queue: size = %dn", Get. Current. Thread. Id(), g_i. Cur. Size ); Sleep(500); // print job. . Interlocked. Decrement(&g_i. Cur. Size); Wait. For. Multiple. Objects(MAX_THREADS, h. Threads, TRUE, INFINITE); long l. Prev; Release. Semaphore(g_h. Sem, 1, &l. Prev); } return 0; }

Synchronization • Events provide notification when some condition has been met HANDLE Create. Event(LPSECURITY_ATTRIBUTES

Synchronization • Events provide notification when some condition has been met HANDLE Create. Event(LPSECURITY_ATTRIBUTES lpsa, BOOL b. Manual. Reset, BOOL b. Initial. State, LPCTSTR lp. Name) • • If b. Initial. State is TRUE, object is created in the signaled state b. Manual. Reset specifies the type of event requested

Synchronization Two kinds of event objects: • Auto reset - when signaled it is

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 released • Manual reset - when signaled it remains in the signaled state until it is manually changed to the nonsignaled state

Synchronization • • Named event objects can be used across process boundaries with Open.

Synchronization • • Named event objects can be used across process boundaries with Open. Event and Create. Event Set. Event sets the object state to signaled Reset. Event sets the object state to nonsignaled Pulse. Event conceptually calls Set. Event/Reset. Event sequentially, but. . .

Synchronization Pulse. Event vs Set. Event

Synchronization Pulse. Event vs Set. Event

Synchronization • Example: displaying Output. Debug. String text without a debugger int main() {

Synchronization • Example: displaying Output. Debug. String text without a debugger int main() { HANDLE h. Ack. Event, h. Ready. Event; PSTR psz. Buffer; Set. Event(h. Ack. Event); while ( TRUE ) { h. Ack. Event = Create. Event(NULL, FALSE, int ret = Wait. For. Single. Object(h. Ready. Event, INFINITE); FALSE, “DBWIN_BUFFER_READY”); if (Get. Last. Error() == ERROR_ALREADY_EXISTS) { // handle multiple instance case } h. Ready. Event = Create. Event(NULL, FALSE, “DBWIN_DATA_READY”); psz. Buffer = /* get pointer to data in memory mapped file */; } } if ( ret != WAIT_OBJECT_0) { // handle error } else { printf(psz. Buffer); Set. Event(h. Ack. Event); }

Synchronization • Waitable timers are kernel objects that provide a signal at a specified

Synchronization • Waitable timers are kernel objects that provide a signal at a specified time interval HANDLE Create. Waitable. Timer(LPSECURITY_ATTRIBUTES lpsa, BOOL b. Manual. Reset, LPCTSTR lp. Name) • • Manual/auto reset behavior is identical to events Time interval is specified with Set. Waitable. Timer

Synchronization BOOL Set. Waitable. Timer(HANDLE h. Timer, LARGE_INTEGER *p. Due. Time, LONG l. Period,

Synchronization BOOL Set. Waitable. Timer(HANDLE h. Timer, LARGE_INTEGER *p. Due. Time, LONG l. Period, PTIMERACPROUTINE pfn. Completion, PVOID p. Arg, BOOL f. Resume) • • • p. Due. Time specifies when the timer should go off for the first time (positive is absolute, negative is relative) l. Period specifies how frequently to go off after the initial time f. Resume controls whether the system is awakened when timer is signaled

Synchronization • • Consecutive calls to Set. Waitable. Timer overwrite each other Cancel. Waitabletimer

Synchronization • • Consecutive calls to Set. Waitable. Timer overwrite each other Cancel. Waitabletimer stops the timer so that it will not go off again (unless Set. Waitable. Timer is called)

Synchronization • Example: firing an event every N seconds const int MAX_TIMES = 3;

Synchronization • Example: firing an event every N seconds const int MAX_TIMES = 3; const int N = 10; DWORD WINAPI Thread. Func(PVOID pv) { HANDLE h. Timer = (HANDLE)pv; int i. Count = 0; DWORD dw. Err = 0; int main() { HANDLE h. Timer = Create. Waitable. Timer(NULL, FALSE, NULL); LARGE_INTEGER li; const int n. Nanoseconds. Per. Second = 10000000; __int 64 qw. Time. From. Now = N * n. Nanoseconds. Per. Second; qw. Time. From. Now = -qw. Time. From. Now; while (TRUE) { if ( Wait. For. Single. Object(h. Timer, INFINITE) == WAIT_OBJECT_0 ) { … handle timer event. . . li. Low. Part = (DWORD)(qw. Time. From. Now & 0 x. FFFF); li. High. Part = (DWORD)(qw. Time. From. Now >> 32); Set. Waitable. Timer(h. Timer, &li, N * 1000, NULL, FALSE); if ( ++i. Count >= MAX_TIMES ) break; DWORD dw. TID; HANDLE h. Thread = Create. Thread(NULL, 0, Thread. Func, h. Timer, 0, &dw. TID); } } return 0; Wait. For. Single. Object(h. Thread, INFINITE); return 0; }

Debugging and Testing Issues • • • Very difficult , if not impossible, to

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

Debugging and Testing Issues • • Don’t underestimate the value of peer review and

Debugging and Testing Issues • • 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”

Interprocess Communication • • Tools to provide the ability to pass data between processes

Interprocess Communication • • Tools to provide the ability to pass data between processes or machines Types of IPC: • • Clipboard DDE OLE Memory Mapped Files • Mailslots • • Pipes RPC Sockets WM_COPYDATA

Advanced Topics • • • Thread local storage UI vs worker threads Create. Remote.

Advanced Topics • • • Thread local storage UI vs worker threads Create. Remote. Thread Scheduling and Get/Set. Thread. Priority) Fibers (NT only) Asynchronous procedure calls

Resources • • Advanced Windows by Jeff Richter Win 32 System Programming by Johnson

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