VC ++ (14): multithreading and thread synchronization (revised)

Source: Internet
Author: User

Considering the consistency of the content, I have almost rewritten this blog. In this section, I mainly introduce the synchronization between threads and threads, and put the chat tool in the next section.

What is a program? A program is a set of computing commands. It is stored on a disk as a file.
What is a process? A process is an instance of a running program and an execution activity of a program in its own address space. Therefore, a program can correspond to multiple processes. For example, we can execute a simple "Hello, world" program compiled by ourselves many times.
Processes are the basic unit for resource application and scheduling. Therefore:

#include <stdio.h>int a = 0;int main(){printf("%d",a);++a;return 0;}

Although a is a global variable, If we execute this program twice, the result of each printing is 0.
In Windows, a process consists of two parts:
(1) kernel objects used by the operating system to manage processes.
These objects are used to store process statistics. They are memory blocks allocated within the operating system and can only be accessed and used by the kernel. The program cannot find the data structure and directly change its content. Windows provides functions to operate kernel objects.
(2) Address Space
It contains code and data of all executable modules or DLL modules, as well as dynamically allocated space. For example, the stack and heap of a thread.
The process knowledge will be carefully explained in the following sections.

In fact, a process never executes anything. To allow a process to complete an operation, it must have a thread running in its environment, this thread is responsible for executing the Code contained in the address space of the process. That is to say, the real completion of code execution is a thread, and the process is only a thread container. A thread is the basic unit for using system resources.

A thread is also composed of two parts:
(1) The Kernel Object of the thread. The operating system uses it to manage threads and store statistical information of threads.
(2) thread stack. It is used to maintain the local variables of all functions and parameters required by the thread for code execution.

The thread has only one kernel object and one stack, and the overhead is relatively small. Therefore, multithreading is often used in programming, and new processes are avoided as much as possible.
Basic functions related to threads include:
Createthread: Create thread
Closehandle: closes the thread handle. Note: This will only invalidate the specified Thread handle (reduce the reference count of the handle) and start the handle check operation. If the last handle associated with an object is disabled, this object will be deleted from the system. Disabling a handle does not terminate the related thread.

How does a thread run? This has something to do with your CPU. If you are a single-core CPU, the system will use the time slice round-robin method to run each thread. If you are a multi-core CPU, therefore, threads may run concurrently. This will cause many problems, such as two threads simultaneously accessing a global variable. They need thread synchronization. The so-called synchronization is not executed by multiple threads at the same time, but by their coordinated pace and in a predetermined order.
There are three basic methods for Thread Synchronization in Windows: mutex object, event object, and key code segment (critical section). The following describes them one by one:

The mutex belongs to the kernel object and contains three members:
1. quantity used: records how many threads are calling this object
2. One thread ID: the ID of the thread maintained by the mutex object
3. A counter: the number of times the previous thread calls this object
Related functions include:
Create a mutex object: createmutex
Determine whether a mutex object can be obtained: waitforsingleobject
For waitforsingleobject, if the mutex object is in a signal state, it is obtained successfully. The function sets the mutex object to a signal-free state, and the program continues to execute. If the mutex object is in a non-signal state, the query fails, and the thread will stay here and wait. The waiting time can be controlled by parameters.
Release mutex: releasemutex
After the code to be protected is executed, it is used to release the mutex object so that the mutex object can be acquired by other threads. Note: only when a thread has a mutex object can the mutex object be released. The function called by other threads cannot be released, which can be determined by the thread ID of the mutex object.

Let's look at an example:

# Include <windows. h> # include <stdio. h> // thread function declaration DWORD winapi thread1proc (lpvoid lpparameter); DWORD winapi thread2proc (lpvoid lpparameter); // global variable int tickets = 100; handle hmutex; int main () {handle hthread1; handle hthread2; // create a mutex object hmutex = createmutex (null, // default security level false, // the thread that creates it does not have a mutex object null ); // No Name // create thread 1hthread1 = createthread (null, // default security level 0, // default stack size thread1proc, // thread function null, // The function does not have parameter 0, // runs null directly after creation); // thread ID, no // create thread 2hthread2 = createthread (null, // default security level 0, // The default stack size is thread2proc, // The thread function is null, // The function does not have a parameter 0, // runs null directly after creation); // The thread ID, no need to // sleep (4000) for 4 seconds when the main thread is sleeping; // sleep (4000) for 4 seconds when the main thread is sleeping; // close the thread handle closehandle (hthread1 ); closehandle (hthread2); // release the mutex object releasemutex (hmutex); Return 0;} // the DWORD winapi thread1proc (lpvoid lpparameter) {While (true) {waitforsingleobject (hmutex, infinite); If (tickets> 0) {sleep (10); printf ("thread1 implements Ticket: % d \ n", tickets --); releasemutex (hmutex);} else {releasemutex (hmutex); break;} return 0;} // DWORD winapi thread2proc (lpvoid lpparameter) {While (true) {waitforsingleobject (hmutex, infinite); If (tickets> 0) {sleep (10); printf ("thread2 restart Ticket: % d \ n", tickets --); releasemutex (hmutex);} else {releasemutex (hmutex); break ;}} return 0 ;}

Be careful when using mutex objects:
Call if a thread already has this mutex object, if it continues to call waitforsingleobject, it will increase the reference count of the mutex object. At this time, you must call releasemutex multiple times to release the mutex object, so that other threads can obtain:

// Create a mutex object hmutex = createmutex (null, // The default security level is true, // the thread that creates it has a mutex object null); // No Name waitforsingleobject (hmutex, infinite); // release the mutex object releasemutex (hmutex );

The event object also belongs to the kernel object, which contains three members:
1. Count
2. indicates whether the event is a Boolean value that indicates whether the event is automatically reset or manually reset.
3. indicates whether the event is in the notified or not notified status.
There is an important difference between automatic resetting and manual resetting of event objects: When the manually reset event object is notified, all threads waiting for the event object become schedulable threads; when an automatically reset event object is notified, only one thread in the thread waiting for the event object changes to a schedulable thread.
Functions related to event objects include:
Create event object: createevent
Handle createevent (lpsecurity_attributes lpeventattributes, bool bmanualreset, bool binitialstate, lptstr lpname );
Set the event object: setevent: set this object to a signal state.
Bool setevent (handle hevent );
Reset event object status: resetevent: Set the specified event object to a stateless state.
Bool resetevent (handle hevent );

The following example shows how to buy a train ticket:

# Include <windows. h> # include <stdio. h> // thread function declaration DWORD winapi thread1proc (lpvoid lpparameter); DWORD winapi thread2proc (lpvoid lpparameter); // global variable int tickets = 100; handle g_hevent; int main () {handle hthread1; handle hthread2; // create the event object g_hevent = createevent (null, // The default security level is true, // manually reset false, // The initial value is null without a signal ); // No Name // create thread 1hthread1 = createthread (null, // default security level 0, // default stack size thread1proc, // thread function null, // The function does not have parameter 0, // runs null directly after creation); // thread ID, no // create thread 2hthread2 = createthread (null, // default security level 0, // The default stack size is thread2proc, // The thread function is null, // The function does not have a parameter 0, // runs null directly after creation); // The thread ID, no need // sleep (4000) for 4 seconds for the main thread to sleep; // close the thread handle // close the handle immediately when it is no longer referenced, reduce its reference count closehandle (hthread1); closehandle (hthread2); // close the event object handle closehandle (g_hevent); Return 0 ;} // thread 1 entry function DWORD winapi thread1proc (lpvoid lpparameter) {While (true) {waitforsingleobject (g_hevent, infinite); If (tickets> 0) {sleep (1 ); printf ("thread1 effecticket: % d \ n", tickets --);} elsebreak;} return 0;} // thread 2 entry function DWORD winapi thread2proc (lpvoid lpparameter) {While (true) {waitforsingleobject (g_hevent, infinite); If (tickets> 0) {sleep (1); printf ("thread2 restart Ticket: % d \ n ", tickets --);} elsebreak;} return 0 ;}

After the program runs, there are no two threads buying tickets. Instead, they quit after four seconds. Why? The problem is that the event object we created is in the non-signal state at the beginning. Therefore, when two threads run to waitforsingleobject, they will wait until the end of their time slice. So nothing is output.
If you want the thread to be able to run, you can set the 3rd parameters to true when creating the thread, or call

SetEvent(g_hEvent);

The program can indeed purchase tickets, but sometimes it will print out the situation where a thread sells 0th tickets, because when the manually reset event object is notified, wait until all threads of the object can be changed to schedulable threads. When two threads run simultaneously, the synchronization of the threads fails.

Some people may think that after the thread obtains the CPU, whether it can use resetevent requires the thread to set the event object to a non-signal state, and then when the protected code is completed, then set the event object to a signal state? We can try:

// Thread 1 entry function DWORD winapi thread1proc (lpvoid lpparameter) {While (true) {waitforsingleobject (g_hevent, infinite); resetevent (g_hevent); If (tickets> 0) {sleep (10); printf ("thread1 restart Ticket: % d \ n", tickets --); setevent (g_hevent);} else {setevent (g_hevent); break ;}} return 0 ;}

Similar to thread 2, this is omitted here. Run the program and find that 0th tickets are still sold. Why? Let's take a closer look: under a single-core CPU, thread 1 may have finished waitforsingleobject execution and switched to thread 2 before it can execute resetevent. At this time, because thread 1 does not execute resetevent, so thread 2 can also get the event object. On the multi-CPU platform, if two threads are executed simultaneously, they may all be executed to the code area that should be protected.
Therefore, in order to achieve synchronization between threads, we should not manually reset the event object, but use the automatically reset event object:

hThread2 = CreateThread(NULL,0,Thread2Proc,NULL0,NULL);

Comment out all the previously written resetevents and setevents. We found that the program only printed a ticket purchase process. Let's analyze the cause:
When an automatically reset event is notified, only one thread waiting for the event becomes a schedulable thread. Here, after thread 1 is changed to a scheduling thread, the operating system sets the event to a non-signal state. When thread 1 is sleep, thread 2 can only wait. After the time slice ends, it is also the turn of thread 1 to run and output thread1 into Ticket: 100. Then the loop goes to waitforsingleobject again, and the event object is in the non-signal state, so the thread cannot continue to execute, but can only wait until its own time slice ends until the main thread wakes up, end the entire program.
The correct method is to call setevent to set it to a signal State immediately after the protected code segment is accessed. Allow other threads waiting for this object to change to the schedulable status:

DWORD WINAPI Thread1Proc(  LPVOID lpParameter){while(TRUE){WaitForSingleObject(g_hEvent,INFINITE);if(tickets > 0){Sleep(10);printf("thread1 sell ticket : %d\n",tickets--);SetEvent(g_hEvent);}else{SetEvent(g_hEvent);break;}}return 0;}

To sum up, the event object should distinguish between manual reset events and automatic reset events. If a manually reset event object is notified, wait until all threads of the event object change to a schedulable thread. When an automatically reset event object is notified, only one thread waiting for the event object changes to a schedulable thread, and the operating system sets the event object to a non-signal state. Therefore, after executing the protected code, you need to call setevent to set the event object to a signal state.

The following describes another thread synchronization method: key code segment.
The key code segment is also known as the critical section and works in the user mode. It is a small piece of code, but it must be exclusive to some resources before the code is executed.
Let's first introduce the first-off API functions:
Use initializecriticalsection to initialize key code segments
Use entercriticalsection to enter key code segments:
Use leavecriticalsection to exit key code segments:
Use deletecriticalsection to delete key code segments and release resources
Let's look at an example:

# Include <windows. h> # include <stdio. h> // thread function declaration DWORD winapi thread1proc (lpvoid lpparameter); DWORD winapi thread2proc (lpvoid lpparameter); // global variable int tickets = 100; critical_section g_cs; int main () {handle hthread1; handle hthread2; // initialize the key code segment initializecriticalsection (& g_cs); // create thread 1hthread1 = createthread (null, // default security level 0, // The default stack size is thread1proc, // The thread function is null, // The function does not have a parameter 0, // runs null directly after creation); // The thread ID, no // create thread 2hthread2 = createthread (null, // default security level 0, // default stack size thread2proc, // thread function null, // function no parameter 0, // run null directly after creation); // thread ID, no // The main thread sleep for 4 seconds (4000); // close the thread handle closehandle (hthread1 ); closehandle (hthread2); // Close event object handle deletecriticalsection (& g_cs); Return 0 ;}// thread 1 entry function DWORD winapi thread1proc (lpvoid lpparameter) {While (true) {// call this function before entering the key code segment to determine whether the right to use the critical section entercriticalsection (& g_cs); sleep (1); If (tickets> 0) {sleep (1 ); printf ("thread1 javasticket: % d \ n", tickets --); // release the right to use the critical area object after access (leavecriticalsection (& g_cs); sleep (1 );} else {leavecriticalsection (& g_cs); break ;}return 0 ;}// thread 2 entry function DWORD winapi thread2proc (lpvoid lpparameter) {While (true) {// call this function before entering the key code segment to determine whether the right to use the critical section entercriticalsection (& g_cs); sleep (1); If (tickets> 0) {sleep (1 ); printf ("thread2 javasticket: % d \ n", tickets --); // release the right to use a critical area object after access ends leavecriticalsection (& g_cs); sleep (1 );} else {leavecriticalsection (& g_cs); break ;}} return 0 ;}

In this example, after a critical zone resource is abandoned, Sleep immediately causes another thread to be called, resulting in two threads selling tickets alternately.
The following is a common mistake in a multi-threaded Program-thread deadlock. The cause of the deadlock. For example, thread 1 has critical zone resource A and is waiting for critical zone resource B. Thread 2 has critical zone resource B and is waiting for critical zone resource. They are different, and no one can execute the results. Let's look at the program:

# Include <windows. h> # include <stdio. h> // thread function declaration DWORD winapi thread1proc (lpvoid lpparameter); DWORD winapi thread2proc (lpvoid lpparameter); // global variable int tickets = 100; rjg_csa; critical_section g_csb; int main () {handle hthread1; handle hthread2; // initialize the key code segment initializecriticalsection (& g_csa); initializecriticalsection (& g_csb); // create thread 1hthread1 = createthread (null, // The default security level is 0, // The default stack size is thread1proc, // The thread function is null, // The function has no parameter 0, // run null directly after creation ); // thread ID, no // create thread 2hthread2 = createthread (null, // default security level 0, // default stack size thread2proc, // thread function null, // The function does not have the parameter 0, // runs null directly after creation); // thread ID, no need to // close the thread handle // when this handle is no longer referenced, close it immediately and reduce its reference count closehandle (hthread1); closehandle (hthread2); // The main thread sleep for 4 seconds (4000 ); // close the event object handle deletecriticalsection (& g_csa); deletecriticalsection (& g_csb); Return 0 ;}// the DWORD winapi thread1proc (lpvoid lpparameter) {While (true) {entercriticalsection (& g_csa); sleep (1); entercriticalsection (& g_csb); If (tickets> 0) {sleep (1); printf ("thread1 expose ticket: % d \ n ", tickets --); leavecriticalsection (& g_csb); leavecriticalsection (& g_csa); sleep (1);} else {leavecriticalsection (& g_csb ); leavecriticalsection (& g_csa); break ;}return 0 ;}// DWORD winapi thread2proc (lpvoid lpparameter) {While (true) {entercriticalsection (& g_csb ); sleep (1); entercriticalsection (& g_csa); If (tickets> 0) {sleep (1); printf ("thread2 implements Ticket: % d \ n", tickets --); round (& g_csa); leavecriticalsection (& g_csb); sleep (1);} else {leavecriticalsection (& g_csa); leavecriticalsection (& g_csb); break;} return 0 ;}

In the program, two critical zone objects g_csa and g_csb are created. Thread 1 first tries to get g_csa, and then sleep after obtaining success, thread 2 tries to get g_csb, then sleep after successful, switches back to thread 1, and thread 1 tries to get g_csb, because g_csb has been obtained by thread 2, it will not succeed in obtaining thread 1. Wait until the end of its time slice, and go to thread 2. After thread 2 obtains g_csb, if you try to obtain g_csa, it will not succeed, but it will be switched back to thread 1 ...... Wait until the main thread wakes up, the execution is complete, and the program ends.

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.