C + + multithreaded programming (ODBC programming learning notes)

Learning notes on C language / C + + Programming: multithreading plays a very important role in programming. We can always encounter multithreading problems in actual development or job interview. Our understanding of multithreading reflects the programming level of programmers from one side.

API functions for creating threads

HANDLE CreateThread(
    LPSECURITY_ATTRIBUTES lpThreadAttributes,//SD: thread safety related attribute, often set to NULL
    SIZE_T dwStackSize,//initialstacksize: the size of the initialization stack of the new thread, which can be set to 0
    LPTHREAD_START_ROUTINE lpStartAddress,//threadfunction: callback function executed by thread, also known as thread function
    LPVOID lpParameter,//threadargument: the parameter passed into the thread function. It is NULL when no parameter needs to be passed
    DWORD dwCreationFlags,//creationoption: flag that controls thread creation
    LPDWORD lpThreadId//threadidentifier: outgoing parameter, used to obtain the thread ID. if it is NULL, the thread ID will not be returned
    )

/*
lpThreadAttributes: Point to security_ The pointer of the attributes structure determines whether the returned handle can be inherited by the child process. If it is NULL, it means that the returned handle cannot be inherited by the child process.

dwStackSize: Set the initial stack size in bytes. If it is 0, the same stack space size as the thread calling the function will be used by default.
In any case, Windows dynamically lengthens the stack size as needed.

lpStartAddress: Pointer to a thread function. There is no limit on the function name, but it must be declared in the following form:
DWORD WINAPI The function name (LPVOID lpParam) cannot be called successfully if the format is incorrect.

lpParameter: The parameter passed to the thread function is a pointer to the structure. It is NULL when there is no need to pass the parameter.

dwCreationFlags: The flag created by the control thread can take the following values:
(1)CREATE_SUSPENDED(0x00000004): Create a suspended thread (ready state) and call it until the thread is awakened
(2)0: Indicates activation immediately after creation.
(3)STACK_SIZE_PARAM_IS_A_RESERVATION(0x00010000): dwStackSize Parameter specifies the size of the initial reserved stack,
If stack_ SIZE_ PARAM_ IS_ A_ If the reservation flag is not specified, dwStackSize will be set as the value reserved by the system

lpThreadId:Save the id of the new thread

Return value: if the function succeeds, return the thread handle; otherwise, return NULL. If the thread creation fails, the error information can be obtained through the GetLastError function.


*/

BOOL WINAPI CloseHandle(HANDLE hObject);        //Close an open object handle
/*This function can be used to close the created thread handle. If the function is executed successfully, it will return true (non-0). If it fails, it will return false(0),
If the execution fails, GetLastError can be called Function to get error information.
*/
 

Multithreaded programming instance 1:

 1 #include <iostream>   
 2 #include <windows.h>   
 3 using namespace std;
 4 
 5 DWORD WINAPI Fun(LPVOID lpParamter)
 6 {
 7     for (int i = 0; i < 10; i++)
 8         cout << "A Thread Fun Display!" << endl;
 9     return 0L;
10 }
11 
12 int main()
13 {
14     HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
15     CloseHandle(hThread);
16     for (int i = 0; i < 10; i++)
17         cout << "Main Thread Display!" << endl;
18     return 0;
19 }

Operation results:

You can see that the main thread (main function) and our own thread (Fun function) execute alternately at random. You can see that the Fun function only runs six times. This is because the resources occupied by the main thread are released after running, so that the sub thread has not finished running. It seems that the main thread is running a little fast. Let it sleep.

Use the function Sleep to pause the execution of the thread.

1 VOID WINAPI Sleep(   
2   __in  DWORD dwMilliseconds   
3 );  
dwMilliseconds It means one thousandth of a second, so Sleep(1000); Indicates a pause of 1 second.

Multithreaded programming example 2:

 1 #include <iostream>   
 2 #include <windows.h>   
 3 using namespace std;
 4 
 5 DWORD WINAPI Fun(LPVOID lpParamter)
 6 {
 7     for (int i = 0; i < 10; i++)
 8     {
 9         cout << "A Thread Fun Display!" << endl;
10         Sleep(200);
11     }
12         
13     return 0L;
14 }
15 
16 int main()
17 {
18     HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
19     CloseHandle(hThread);
20     for (int i = 0; i < 10; i++)
21     {
22         cout << "Main Thread Display!" << endl;
23         Sleep(500);
24     }
25         
26     return 0;
27 }

Operation results:

The program will output line breaks whenever the func function and main function output content, but we can see that sometimes the program output line breaks, sometimes there is no output line breaks, and sometimes even two line breaks. What's going on? Now let's change the program.

Multithreaded programming example 3:

 1 #include <iostream>   
 2 #include <windows.h>   
 3 using namespace std;
 4 
 5 DWORD WINAPI Fun(LPVOID lpParamter)
 6 {
 7     for (int i = 0; i < 10; i++)
 8     {
 9         //cout << "A Thread Fun Display!" << endl;
10         cout << "A Thread Fun Display!\n";
11         Sleep(200);
12     }
13         
14     return 0L;
15 }
16 
17 int main()
18 {
19     HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
20     CloseHandle(hThread);
21     for (int i = 0; i < 10; i++)
22     {
23         //cout << "Main Thread Display!" << endl;
24         cout << "Main Thread Display!\n";
25         Sleep(500);
26     }
27         
28     return 0;
29 }

Operation results:

At this time, as we expected, the content we want to output is correctly output and the format is correct. Here, we can regard the screen as a resource, which is shared by two threads. When the Fun function outputs the Fun Display! After that, we will output endl (that is, empty the buffer and wrap the line. Here we don't need to understand what the buffer is), but at this time, the main function gets the chance to run. At this time, the main function gives the CPU to the main function before the Fun function has time to output the line wrap (the time slice runs out). At this time, the main function is directly in the Fun Display! Output Main Display!.

Another case is "output two line breaks". This case is, for example, output Main Display! After outputting endl, the time slice runs out, and it's the sub thread's turn to occupy the CPU. The sub process stopped at Fun Display! When the last time slice ran out!, The next time the time slice comes, it just starts to output endl. At this time, it will "output two newlines".

So why can we change instance 2 to instance 3 to run correctly? The reason is that although multiple threads run concurrently, some operations (such as outputting a whole paragraph of content) must be completed at one go and are not allowed to be interrupted. Therefore, we can see that the running results of instance 2 and instance 3 are different. The difference between them is that there is less endl and more newline \ n.

So, is it the code of instance 2 that we can't make it run correctly? Of course, the answer is No. let me talk about how to make the code of example 2 run correctly. This involves the synchronization of multiple threads. For a resource shared by multiple threads will lead to program confusion, our solution is to allow only one thread to have exclusive access to shared resources. Here, we use mutex to synchronize threads.

When using mutex for thread synchronization, the following functions are used:

HANDLE WINAPI CreateMutex(
    LPSECURITY_ATTRIBUTES lpMutexAttributes,        //Thread safety related properties, often set to NULL
    BOOL                  bInitialOwner,            //Does the current thread that created the Mutex own the Mutex
    LPCTSTR               lpName                    //Name of Mutex
);
/*
MutexAttributes:It is also a structure that represents security. It has the same function as lpThreadAttributes in CreateThread. It determines whether the returned handle can be inherited by the child process. If it is NULL, it indicates that the returned handle cannot be inherited by the child process.
bInitialOwner:Indicates whether the current thread when creating Mutex owns the ownership of Mutex. If TRUE, it specifies that the current creating thread is the owner of Mutex object, and other threads need to release Mutex first
lpName:Mutex Name of
*/
DWORD WINAPI WaitForSingleObject(
    HANDLE hHandle,                             //The handle of the lock to acquire
    DWORD  dwMilliseconds                           //Timeout interval
);

/*

WaitForSingleObject: wait for a specified object (such as Mutex object) until the object is in an unoccupied state (such as Mutex object is released) or exceeds the set time interval. In addition, there is a similar function WaitForMultipleObjects. Its function is to wait for one or all specified objects until all objects are unoccupied or exceed the set time interval.

hHandle: handle of the specified object to wait for.

dwMilliseconds: timeout interval, in milliseconds; If dwMilliseconds is non-0, wait until the dwMilliseconds interval runs out or the object becomes unoccupied. If dwMilliseconds is INFINITE, wait indefinitely until the waiting object is unoccupied.
*/
BOOL WINAPI ReleaseMutex(HANDLE hMutex);

//Description: release the mutex lock object. hMutex is the mutex handle released

Multithreaded instance 4:

 1 #include <iostream>
 2 #include <windows.h>
 3 using namespace std;
 4 
 5 HANDLE hMutex = NULL;//mutex 
 6 //Thread function
 7 DWORD WINAPI Fun(LPVOID lpParamter)
 8 {
 9     for (int i = 0; i < 10; i++)
10     {
11         //Request a mutex lock
12         WaitForSingleObject(hMutex, INFINITE);
13         cout << "A Thread Fun Display!" << endl;
14         Sleep(100);
15         //Release mutex lock
16         ReleaseMutex(hMutex);
17     }
18     return 0L;//Indicates that the returned is a long 0
19 
20 }
21 
22 int main()
23 {
24     //Create a child thread
25     HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);
26     hMutex = CreateMutex(NULL, FALSE,"screen");
27     //Close thread
28     CloseHandle(hThread);
29     //Execution path of main thread
30     for (int i = 0; i < 10; i++)
31     {
32         //Request to obtain a mutex lock
33         WaitForSingleObject(hMutex,INFINITE);
34         cout << "Main Thread Display!" << endl;
35         Sleep(100);
36         //Release mutex lock
37         ReleaseMutex(hMutex);
38     }
39     return 0;
40 }
 
   actually C++The language itself does not provide multithreading, but Windows The system provides us with relevant information API,We can use them for multithreaded programming. This paper explains the knowledge of multithreading programming in the form of examples.

   To create a thread API function

C + + code

HANDLE CreateThread(   
  __in   SEC_ATTRS    
            SecurityAttributes,   
  __in   ULONG    
            StackSize,        // initial stack size   
  __in   SEC_THREAD_START    
            StartFunction,    // thread function   
  __in   PVOID    
            ThreadParameter,  // thread argument
  __in   ULONG    
            CreationFlags,    // creation option   
  __out  PULONG    
            ThreadId          // thread identifier   
);  
   Here we only use the third and fourth parameters. The third parameter passes the address of a function, which is also the new thread we want to specify. The fourth parameter is the parameter pointer passed to the new thread.

   Multithreaded programming instance 1:

C + + code

#include <iostream>   
#include <windows.h>   
using namespace std;   
  
DWORD WINAPI Fun(LPVOID lpParamter)   
{   
      while(1) { cout<<"Fun display!"<<endl; }   
}   
  
int main()   
{   
    HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);   
    CloseHandle(hThread);   
    while(1) { cout<<"main display!"<<endl;  }   
    return 0;   
}  
   We can see the main thread( main Function) and our own thread( Fun Function) is executed alternately at random, but the output of the two threads is too fast for us to see clearly. We can use function Sleep To pause the execution of the thread.

C + + code

VOID WINAPI Sleep(   
  __in  DWORD dwMilliseconds   
);  
       dwMilliseconds It means one thousandth of a second, so Sleep(1000); Indicates a pause of 1 second.

       Multithreaded programming example 2:

C++code
#include <iostream>   
#include <windows.h>   
using namespace std;   
  
DWORD WINAPI Fun(LPVOID lpParamter)   
{       
      while(1) { cout<<"Fun display!"<<endl; Sleep(1000);}   
}   
  
int main()   
{   
      HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);   
      CloseHandle(hThread);   
      while(1) { cout<<"main display!"<<endl;  Sleep(2000);}   
      return 0;   
}  
   By executing the above code, we can clearly see the interleaved output on the screen this time Fun display!and main display!,We find that these two functions are indeed running concurrently. Careful readers may find that our program is Fun Function sum main After the function outputs the content, it will output line feed, but we can see that sometimes the program output line feed, sometimes there is no line feed, and sometimes even two line feeds. What's going on? Now let's change the program.

Classic example of introduction to C + + multithreaded programming

    Multithreaded programming example 3:

C + + code

#include <iostream>   
#include <windows.h>   
using namespace std;   
  
DWORD WINAPI Fun(LPVOID lpParamter)   
{   
      while(1) { cout<<"Fun display!\n"; Sleep(1000);}   
}   
  
int main()   
{   
      HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);   
      CloseHandle(hThread);   
      while(1) { cout<<"main display!\n";  Sleep(2000);}   
      return 0;   
}  
   When we run the program again, we find that at this time, as we expected, the content we want to output is correctly output and the format is correct. Let me talk about why our program didn't run correctly before. Multithreaded programs run concurrently. If multiple threads share some resources, we can't guarantee that these resources can be used correctly, because the resources are not exclusive at this time. Let's take an example.

   Multithreaded programming example 4:

   If there is a resource int a = 3

   There is a thread function selfAdd() The function is to make a += a;

   There is another thread function selfSub() The function is to make a -= a;

   Let's assume that the above two threads are running concurrently. If selfAdd When implementing, our goal is to make a Program 6, but at this time selfSub Got the chance to run, so a Becomes 0, wait until selfAdd When the opportunity for implementation comes, a += a ,But at this time a It's really 0, not 6 as we expected.

   Let's go back to the previous example 2. Here, we can regard the screen as a resource shared by two threads Fun Function output Fun display!After that, it will be output endl(That is to empty the buffer and wrap the line. Here we don't need to understand anything (buffer), but at this time main The function does get a chance to run. At this time Fun Before the function has time to output line feed CPU Give up main Function, and then main The function is directly in Fun display!Post output main display!,As for why sometimes the program will output two line breaks continuously, readers can use the same analysis method to analyze. I won't talk about it here, leaving it to the readers to think for themselves.

   So why can we change instance 2 to instance 3 to run correctly? The reason is that although multiple threads run concurrently, some operations must be completed at one go and are not allowed to be interrupted. Therefore, we can see that the running results of instance 2 and instance 3 are different.

   So, is it the code of instance 2 that we can't make it run correctly? Of course, the answer is No. let me talk about how to make the code of example 2 run correctly. This involves the synchronization of multiple threads. For a resource shared by multiple threads will lead to program confusion, our solution is to allow only one thread to have exclusive access to the shared resources, so as to solve the above problem.

C + + code

HANDLE CreateMutex(      
    LPSECURITY_ATTRIBUTES lpMutexAttributes,       
    BOOL bInitialOwner,                       // initial owner      
    LPCTSTR lpName                            // object name      
);   
   This function is used to create an exclusive resource. The first parameter is not used and can be set to NULL,The second parameter specifies whether the resource initially belongs to the process that created it, and the third parameter specifies the name of the resource.

C + + code

HANDLE hMutex = CreateMutex(NULL,TRUE,"screen");   
   This statement creates a screen And resources belonging to the process that created it.

C + + code

BOOL ReleaseMutex(   
    HANDLE hMutex   // handle to mutex   
);  
   This function is used to release an exclusive resource. Once the process releases the resource, the resource no longer belongs to it. If it needs to be used again, it needs to re apply for the resource. The function of applying for resources is as follows:

C + + code

DWORD WaitForSingleObject(   
    HANDLE hHandle,        // handle to object   
    DWORD dwMilliseconds   // time-out interval   
);  
   The first parameter specifies the handle of the requested resource, and the second parameter is generally specified as INFINITE,It means that if no resource is applied for, it will wait for the resource all the time. If 0 is specified, it means that it will return once the resource is not available. You can also specify how long to wait before returning. The unit is one thousandth of a second. Well, it's time for us to solve the problem of instance 2. We can make some modifications to instance 2, such as the following example.

   Multithreaded programming example 5:

C + + code

#include <iostream>   
#include <windows.h>   
using namespace std;   
  
HANDLE hMutex;   
  
DWORD WINAPI Fun(LPVOID lpParamter)   
{   
       while(1) {    
                 WaitForSingleObject(hMutex, INFINITE);   
                 cout<<"Fun display!"<<endl;    
                 Sleep(1000);   
                 ReleaseMutex(hMutex);   
        }   
}   
  
int main()   
{   
      HANDLE hThread = CreateThread(NULL, 0, Fun, NULL, 0, NULL);   
      hMutex = CreateMutex(NULL, FALSE, "screen");   
      CloseHandle(hThread);   
      while(1) {   
               WaitForSingleObject(hMutex, INFINITE);   
               cout<<"main display!"<<endl;     
               Sleep(2000);   
               ReleaseMutex(hMutex);   
      }   
  
      return 0;   
}  
   Running this code will get the output we expect.

Added by lucy on Thu, 17 Feb 2022 10:08:37 +0200