Windows Multithreading issues

Source: Internet
Author: User
Tags mutex posix semaphore

Processes and threads are two concepts that are often encountered in the operating system, and there is a concept that is an application. The application consists of instructions and data that are only distributed on disk before they begin to run. The application being executed is called a process, and the process is not just instructions and data, it has a state . A state is a number of values stored in the processor register that record some information, such as the address of the current execution instruction, the value stored in memory, and so on. A process is the basic building block of an application, and multiple applications run concurrently. Each process can run multiple threads. Threads also have some state, but the state of a thread is basically just the value stored in its register and the data on its stack. Threads share many states with other threads in the same application. the advantage of a process is that each process is independent, that a process's death has no effect on other running processes , and that the disadvantage of multiple processes is that each process requires its own TLB (translation look-aside buffer, convert side-view buffer) entry, This increases the number of misses for TLB entries and caches, and the disadvantage of multiple processes is that the sharing of data between processes requires explicit control, which can be costly. Multithreading is a bit of a thing. The cost of sharing data between multiple threads is low because a thread can store data items in memory, and that data is immediately visible to all other threads in the process, and the other advantage is that all threads share the same TLB and cache entries, so the cache misses are low for multithreaded applications. The disadvantage is that a thread failure is likely to cause the entire application to terminate. For example, the browser is multi-process, you can use the browser to open multiple tabs, each tab is a separate process, a failure of a tab page will not cause the entire browser crash. If the browser is multithreaded, if a thread executes some bad code, the entire browser is likely to collapse.

For multi-threaded applications to work effectively, some common states must be shared between threads. When multithreading updates the same data in an unsafe way, it generates data contention, and one way to avoid data contention is to use thread synchronization correctly. Synchronization primitives include mutual exclusion and critical areas, spin locks, semaphores, read/write locks, and barriers . There are many mechanisms for threading and interprocess communication, such as memory, shared memory and memory-mapped files, condition variables, signals and events, message queues, named pipes, network stacks , and so on.

Creating Threads

The Windows operating system support for multithreading is broadly similar to the support provided by POSIX threads. A native thread that creates Windows can call the CreateThread function, which returns a handle to the thread that was created, and if the return value is 0, the call is unsuccessful.

#include <Windows.h> #include <stdio.h> #include <tchar.h>dword WINAPI mythread (__in lpvoid Lpparameter) {printf ("Thread%i\n", GetCurrentThread ()); return 0;} int _tmain (int argc,_tchar* argv[]) {HANDLE handle;handle=createthread (0,0,mythread,0,0,0); GetChar (); return 0;}

In the preceding section of code, calling CreateThread causes the operating system to generate a new thread and then returns a handle to the thread, but the runtime does not establish the thread-local data structure it requires. The runtime also provides two thread creation functions _beginthread () and _beginthreadex (). The difference between the two is that _beginthread () creates a thread that shuts down the thread handle when it exits, and the thread handle returned by the _beginthreadex () call requires an explicit call to CloseHandle to be freed.

#include <Windows.h> #include <process.h> #include <stdio.h> #include <tchar.h>dword WINAPI Mythread1 (__in lpvoid lpparameter) {printf ("CreateThread created thread, ID:%i\n", GetCurrentThreadID ()); return 0;} unsigned int __stdcall mythread2 (void *data) {printf ("__beginthreadex created thread, ID:%i\n", GetCurrentThreadID ()); return 0;} void mythread3 (void *data) {printf ("__beginthread created thread, ID:%i\n", GetCurrentThreadID ());} int _tmain (int argc,_tchar* argv[]) {HANDLE h1,h2,h3;h1=createthread (0,0,mythread1,0,0,0); h2= (HANDLE) _beginthreadex (0,0,&mythread2,0,0,0); WaitForSingleObject (H2,infinite); CloseHandle (H2); h3= (HANDLE) _beginthread (&mythread3,0,0); GetChar ();}

Under multi-threaded, sometimes you want to wait for a thread to finish and then continue to do other things, to achieve this, you can use the Windows API function WaitForSingleObject, or waitformultipleobjects. Both functions will wait for the object to be marked as signaled (signaled) before returning.

In the Windows operating system, it is often mentioned that a concept, a handle, and many of the Windows API return values are handles, in fact, the handle is plainly an unsigned integer. The Windows API call that returns a handle is actually a resource created in kernel space, and the handle is just the index of the resource. Once the application has finished using the resource, it is possible to call the CloseHandle function to let the kernel free the associated kernel space resources.

The terminating thread can call ExitThread or terminatethread, or it can call the library function Endthread or endthreadex to terminate the thread.

If the thread is in a suspended state, the startup thread can call the ResumeThread function, which takes the thread's handle as a parameter. The SuspendThread function can force a running thread to hang, which is best not to be called easily, because if the thread holds a thread, it is easy to get a problem with such resources as the mutex, hehe.

#include <Windows.h> #include <process.h> #include <stdio.h> #include <tchar.h>unsigned int __ StdCall mythread (void *data) {printf ("thread ID created:%i\n", GetCurrentThreadID ()); return 0;} int _tmain (int argc,_tchar* argv[]) {HANDLE h;h= (HANDLE) _beginthreadex (0,0,&mythread,0,create_suspended,0); GetChar (); ResumeThread (h); GetChar (); WaitForSingleObject (H,infinite); CloseHandle (h); return 0;}

Thread synchronization and resource sharing

Windows provides synchronization objects that are similar to POSIX rules. Threads are synchronized in that way, such as mutexes, critical sections, read and write locks, semaphores, condition variables, events, and so on.

Give an example.

#include <Windows.h> #include <process.h> #include <stdio.h> #include <tchar.h> #include < Math.h>int isprime (int num) {int i;for (i=2;i< (int) (sqrt (float) num) +1.0); i++) {if (num%i==0) return 0;} return 1;} volatile int counter=2;unsigned int __stdcall Test (void *) {while (counter<20) {int num=counter++;p rintf ("Thread ID:%i ; Value =%i, is prime =%i\n ", GetCurrentThreadID (), Num,isprime (num));} return 0;} int _tmain (int argc,_tchar* argv[]) {HANDLE h1,h2;h1= (HANDLE) _beginthreadex (0,0,&test, (void *) 0,0,0); h2= (HANDLE) _beginthreadex (0,0,&test, (void *) 1,0,0); WaitForSingleObject (H1,infinite); WaitForSingleObject (H2,infinite); CloseHandle (H1); CloseHandle (H2); GetChar (); return 0;}



Calculates all primes in a given range with two threads. Create two threads, and the two threads will always test the numbers until all the numbers within 2-20 are calculated. From the execution results can be seen, the system produces a total of two threads, the IDs are 13788 and 13792, these two threads will simultaneously access the shared variable counter, will certainly lead to data contention, if you want each thread to test different numbers, It is necessary to take some measures to protect the operation of the shared variable counter. The difference in the display order is that the time it takes for a thread to judge a number differs from the time displayed by the function call printf output.

There are many solutions, nothing more than a classic approach to thread synchronization.

The first method is to add access protection to the critical section Code to ensure that only a single thread executes. The Declaration of the critical section can call InitializeCriticalSection (), call DeleteCriticalSection () to delete the critical section, and if the thread wants to enter the critical section, it can call the EnterCriticalSection () function, If the critical section is at noon other threads, the calling thread can enter the critical section and execute the relevant code; If the critical section is wired, the calling thread will hibernate until the thread that is executing the critical section calls LeaveCriticalSection () to leave the critical section. The thread that calls EnterCriticalSection () will not leave until access to the critical section is obtained, and there is no time-out concept, hehe.

#include <Windows.h> #include <process.h> #include <stdio.h> #include <tchar.h> #include < Math.h>int isprime (int num) {int i;for (i=2;i< (int) (sqrt (float) num) +1.0); i++) {if (num%i==0) return 0;} return 1;} volatile int counter=2; critical_section critical;unsigned int __stdcall Test (void *) {while (counter<20) {entercriticalsection (& critical); int num=counter++; LeaveCriticalSection (&critical);p rintf ("Thread ID:%i; Value =%i, is prime =%i\n ", GetCurrentThreadID (), Num,isprime (num));} return 0;} int _tmain (int argc,_tchar* argv[]) {HANDLE h1,h2;initializecriticalsection (&critical); h1= (HANDLE) _ Beginthreadex (0,0,&test, (void *) 0,0,0) h2= (HANDLE) _beginthreadex (0,0,&test, (void *) 1,0,0); WaitForSingleObject (H1,infinite); WaitForSingleObject (H2,infinite); CloseHandle (H1); CloseHandle (H2); GetChar ();D eletecriticalsection (&critical); return 0;}

In fact, it is time-consuming to make the thread hibernate and then wake up the thread because it involves entering the kernel. Because it is possible for a thread to go into hibernation, the thread that has already been in the critical section has left, so that the waiting thread sleeps and then wakes up a little bit of nonsense ... You can call TryEnterCriticalSection () to return immediately, and a return value of TRUE indicates that the thread obtains access to the critical section. You can modify the test function at this point,

unsigned int __stdcall test (void *) {while (counter<20) {while (! TryEnterCriticalSection (&critical)) {}int num=counter++; LeaveCriticalSection (&critical);p rintf ("Thread ID:%i; Value =%i, is prime =%i\n ", GetCurrentThreadID (), Num,isprime (num));} return 0;}

There is also a problem, and the process will persist the try until access to the critical section is obtained, which deprives the other threads of processor time. The solution is to allow the thread that wants to enter the critical section to wait briefly, like adding a wait time, and leaving the timeout. Two methods, one that sets the number of times the thread that calls EnterCriticalSection is rotated before hibernation, and one that initializes the critical section by initializing the call InitializeCriticalSectionAndSpinCount (). Parameters are the number of pointers and rotations that point to the critical section, or you can set the number of rotations of the created critical section by calling Setcriticalsectionspincount ().

The second scenario is to protect the code snippet with a mutex . Mutexes are kernel objects, so they can be shared between processes. Create mutexes by calling CreateMutex or Createmutexex. To obtain a mutex, call WaitForSingleObject, either already obtaining the mutex or returning after the specified timeout. After the thread finishes, call ReleaseMutex to release the code snippet that is protected by the mutex.

#include <Windows.h> #include <process.h> #include <stdio.h> #include <tchar.h> #include < Math.h>int isprime (int num) {int i;for (i=2;i< (int) (sqrt (float) num) +1.0); i++) {if (num%i==0) return 0;} return 1;} volatile int counter=2; HANDLE mutex;unsigned int __stdcall Test (void *) {while (counter<20) {WaitForSingleObject (mutex,infinite); int num= counter++; ReleaseMutex (Mutex);p rintf ("Thread ID:%i; Value =%i, is prime =%i\n ", GetCurrentThreadID (), Num,isprime (num));} return 0;} int _tmain (int argc,_tchar* argv[]) {HANDLE H1,h2;mutex=createmutex (0,0,0); h1= (HANDLE) _beginthreadex (0,0,&test, (void *) 0,0,0); h2= (HANDLE) _beginthreadex (0,0,&test, (void *) 1,0,0); WaitForSingleObject (H1,infinite); WaitForSingleObject (H2,infinite); CloseHandle (H1); CloseHandle (H2); GetChar (); CloseHandle (mutex); return 0;}

Note:volatile and Const like, volatile is a type modifier ( type specifier ). It is designed to modify variables that are accessed and modified by different threads. If you do not add volatile, it will basically result in the inability to write multithreaded programs or the compiler losing a lot of optimization opportunities.

The

Third scenario is to use a lightweight read-write lock . Locking is a recurring concept in the database, and the nature of the lock is that the allows multiple threads to have read access to the data, or a single thread has write access to the data . You can call Initializesrwlock () to initialize the lock, which is essentially a user variable and does not use kernel resources. As the reader acquires the lock call Acquiresrwlockshared (), as the reader releases the lock call Releasesrwlockshared (), the writer acquires the lock call Acquiresrwlockexclusive (), The writer releases the lock Call Releasesrwlockexclusive ().

#include <Windows.h> #include <process.h> #include <stdio.h> #include <tchar.h>int array[100][ 100]; SRWLOCK lock;unsigned int __stdcall write (void *param) {for (int. y=0;y<100;y++) {for (int x=0;x<100;x++) { Acquiresrwlockexclusive (&lock); array[x][y]++;array[y][x]--; Releasesrwlockexclusive (&lock);}} return 0;} unsigned int __stdcall read (void *param) {int value=0;for (int y=0;y<100;y++) {for (int x=0;x<100;x++) { Acquiresrwlockshared (&lock); value=array[x][y]+array[y][x]; Releasesrwlockshared (&lock);} printf ("value =%i\n", value); return value;}} int  _tmain (int argc,_tchar* argv[]) {HANDLE h1,h2;initializesrwlock (&lock); h1= (HANDLE) _beginthreadex (0,0, &write, (void *) 0,0,0); h2= (HANDLE) _beginthreadex (0,0,&read, (void *) 0,0,0); WaitForSingleObject (H1,infinite); WaitForSingleObject (H2,infinite); CloseHandle (H1); CloseHandle (H2); GetChar (); return 0;}

The fourth scenario is the use of semaphores . Calling CreateSemaphore and Createsemaphoreex can create semaphores, and calling OpenSemaphore can get a handle to a semaphore. The semaphore is the kernel object, and the creation function returns its handle, calling CloseHandle to release. The semaphore is called by the wait function WaitForSingleObject, whose arguments are the semaphore handle and timeout, return the decrement semaphore, or return after the timeout is reached. Call ReleaseSemaphore () to increment the semaphore, whose parameters are the semaphore handle, the signal increment, and an optional pointer to a long variable, and the value before the semaphore is written to the long variable.

For example, the semaphore is created with a maximum value of 1, the initial value is 1, two threads are created, the same code is executed, the value of the variable value is incremented by 200, and the value of the variable value becomes 400 when the end application terminates.

#include <Windows.h> #include <process.h> #include <stdio.h> #include <tchar.h>handle Semaphore;int value;void Add (int num) {WaitForSingleObject (semaphore,infinite); value+=num; ReleaseSemaphore (semaphore,1,0);} unsigned int __stdcall test (void *) {for (int counter=0;counter<100;counter++) {Add (2);} return 0;} int  _tmain (int argc,_tchar* argv[]) {HANDLE h1,h2;value=0;semaphore=createsemaphore (0,1,1,0); h1= (HANDLE) _ Beginthreadex (0,0,&test, (void *) 0,0,0) h2= (HANDLE) _beginthreadex (0,0,&test, (void *) 0,0,0); WaitForSingleObject (H1,infinite); WaitForSingleObject (H2,infinite); CloseHandle (H1); CloseHandle (H2); CloseHandle (semaphore);p rintf ("value =%i\n", value); GetChar (); return 0;}

The fifth solution is a conditional variable that needs to be used in conjunction with a critical section or a lightweight read-write lock so that the thread can go into hibernation until it is true. For example, for producer-consumer issues, the producer thread is responsible for adding data to the queue, the consumer thread entering the critical section, and deleting a data from the queue. This problem can be solved with the condition variable ...

The sixth option is to send an event completion signal to other threads or processes . Events are used to signal one or more threads indicating that an event has occurred. The thread waiting for an event to occur waits for the event object. The thread that completes the task sets the event to signaled sent state, and then waits for the thread to be freed. There are two types of events: manual reset and automatic reset. The event is a kernel object, and calling CreateEvent will return a handle, openevent open an existing event, and SetEvent () sets the event to the state that the signal has been issued. Auto Reset: After SetEvent, the event is automatically reset to the non-triggered state, manual reset: After SetEvent, you need to call the ResetEvent event before it is set to the non-triggered state. When a manual reset event is triggered, all threads that are waiting for the event become scheduled, and when an auto-reset event is triggered, only one thread that is waiting for the event becomes a scheduled state.

For example, call CreateEvent to create an event object that needs to be reset manually and created as a non-signaled state. Then create two threads, the first one waits for the event, the second executes the output message, and then sends the event object. The signal causes the first thread to continue executing and outputs the second message.

#include <Windows.h> #include <process.h> #include <stdio.h> #include <tchar.h>handle event; unsigned int __stdcall thread1 (void *param) {WaitForSingleObject (event,infinite);p rintf ("Thread 1 done \ n"); return 0;} unsigned int __stdcall thread2 (void *param) {printf ("Thread 2 done \ n"); SetEvent (event); return 0;} int  _tmain (int argc,_tchar* argv[]) {HANDLE h1,h2;event=createevent (0,0,0,0); h1= (HANDLE) _beginthreadex (0,0, &thread1,0,0,0); h2= (HANDLE) _beginthreadex (0,0,&thread2,0,0,0); WaitForSingleObject (H1,infinite); WaitForSingleObject (H2,infinite); CloseHandle (H2); CloseHandle (H1); CloseHandle (event); GetChar (); return 0;}




Copyright NOTICE: This article for Bo Master original article, without Bo Master permission not reproduced.

Windows Multithreading issues

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.