Multithreaded Programming With the Win 32 API Andrew
- Slides: 46
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 • 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 of the same OS
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 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. 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 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. 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 _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 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 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 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. 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 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: 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 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 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 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. 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 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 • Console Input • File Change Notifications • Mutexes* • Semaphores* • Events* • Waitable Timers*
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 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 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 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 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 • • 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 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 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 = 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 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 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. 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 • 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 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, 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 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; 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 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 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 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. 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 Hart Win 32 Multithreaded Programming by Aaron Cohen and Mike Woodring Windows NT Programming in Practice by editors of WDJ (including Paula T)
- Win-lose attitude
- Win win win lose lose lose
- Win-win win-lose lose-lose
- Multithreading program in java
- Multithreaded programming language
- Multithreaded algorithms
- Apt multithreaded
- Multithreaded games
- Boulwarism negotiation
- Example of a win win situation
- Win win nacin placanja
- The habit of winning summary
- Think win win
- Win win or no deal
- Enlarging the pie
- Example of a win win situation
- Habit 4 examples
- Greedy programming vs dynamic programming
- What is system programing
- Linear vs integer programming
- Perbedaan linear programming dan integer programming
- Definisi linear
- Vẽ hình chiếu vuông góc của vật thể sau
- V cc
- Làm thế nào để 102-1=99
- Tỉ lệ cơ thể trẻ em
- Lời thề hippocrates
- đại từ thay thế
- Quá trình desamine hóa có thể tạo ra
- Các môn thể thao bắt đầu bằng tiếng bóng
- Công thức tính thế năng
- Thế nào là mạng điện lắp đặt kiểu nổi
- Hình ảnh bộ gõ cơ thể búng tay
- Dot
- Nguyên nhân của sự mỏi cơ sinh 8
- Vẽ hình chiếu đứng bằng cạnh của vật thể
- độ dài liên kết
- Gấu đi như thế nào
- Thiếu nhi thế giới liên hoan
- Khi nào hổ con có thể sống độc lập
- điện thế nghỉ
- Một số thể thơ truyền thống
- Trời xanh đây là của chúng ta thể thơ
- Lp html
- Các số nguyên tố là gì
- đặc điểm cơ thể của người tối cổ
- Fecboak