Inter-thread Communication
Generally, a secondary thread in an application always executes a specific task in the main thread. In this way, there must be a channel for information transmission between the main thread and the secondary thread, that is, communication is required between the main thread and the secondary thread. This kind of Inter-thread communication is not only inevitable, but also complicated and frequent in multi-thread programming. The following describes how to use it.
- Use global variables for communication
Since each thread of the same process shares the resources allocated by the operating system, the simplest way to solve inter-thread communication is to use global variables. For standard global variables, we recommend using the volatile modifier, which tells the compiler not to optimize the variable, that is, it does not need to be put into a register, and the value can be changed externally. If the information to be transmitted between threads is complex, we can define a structure to transmit information by passing a pointer to the structure.
- Use custom messages
We can send custom messages to another thread in the execution function of one thread for communication purpose. A thread sends messages to another thread through the operating system. With the message-driven mechanism of the Windows operating system, when a thread sends a message, the operating system first receives the message and then forwards the message to the target thread, the thread that receives the message must have a message loop.
Routine 7 multithread7
This routine demonstrates how to use custom messages for inter-thread communication. First, the main thread sends the message wm_calculate to the ccalculatethread thread. After receiving the message, the ccalculatethread thread calculates the message and sends the wm_display message to the main thread. After receiving the message, the main thread displays the calculation result.
- Create a project multithread7 Based on the dialog box. In the idd_multithread7_dialog dialog box, add three radio buttons: idc_radio1, idc_radio2, and idc_radio3. The titles are 1 + 2 + 3 + 4 + ...... + 10, 1 + 2 + 3 + 4 + ...... + 50, 1 + 2 + 3 + 4 + ...... + 100. Add the "idc_sum" button, titled "sum ". Add the label box idc_status and select "border" for the attribute ";
- Define the following variables in multithread7dlg. h:
protected: int nAddend;
Indicates the size of the addend.
Double-click the three single-choice buttons to add the message response function:
void CMultiThread7Dlg::OnRadio1() { nAddend=10;}void CMultiThread7Dlg::OnRadio2() { nAddend=50; }void CMultiThread7Dlg::OnRadio3() { nAddend=100; }Complete the initialization in the oninitdialog function:
BOOL CMultiThread7Dlg::OnInitDialog(){…… ((CButton*)GetDlgItem(IDC_RADIO1))->SetCheck(TRUE); nAddend=10;……Add the following in multithread7dlg. h:
#include "CalculateThread.h"#define WM_DISPLAY WM_USER+2class CMultiThread7Dlg : public CDialog{// Constructionpublic: CMultiThread7Dlg(CWnd* pParent = NULL); // standard constructor CCalculateThread* m_pCalculateThread;……protected: int nAddend; LRESULT OnDisplay(WPARAM wParam,LPARAM lParam);……Add the following in multithread7dlg. cpp:
BEGIN_MESSAGE_MAP(CMultiThread7Dlg, CDialog)…… ON_MESSAGE(WM_DISPLAY,OnDisplay)END_MESSAGE_MAP()LRESULT CMultiThread7Dlg::OnDisplay(WPARAM wParam,LPARAM lParam){ int nTemp=(int)wParam; SetDlgItemInt(IDC_STATUS,nTemp,FALSE); return 0;}The above Code enables the winner Thread class cmultithread7dlg to process the wm_display message, that is, the calculation result is displayed in the idc_status label box.
- Double-click idc_sum to add the message response function:
void CMultiThread7Dlg::OnSum() { m_pCalculateThread= (CCalculateThread*)AfxBeginThread(RUNTIME_CLASS(CCalculateThread)); Sleep(500); m_pCalculateThread->PostThreadMessage(WM_CALCULATE,nAddend,NULL);}The onsum () function is used to establish a calculatethread thread and send wm_calculate messages to the thread at a delay.
- Right-click the project and select "New Class ..." Add the base class for the project as the cwinthread-derived Thread class ccalculatethread.
In the file calculatethread. H, add
#define WM_CALCULATE WM_USER+1 class CCalculateThread : public CWinThread{……protected: afx_msg LONG OnCalculate(UINT wParam,LONG lParam);……In the file calculatethread. cpp, add
Long ccalculatethread: oncalculate (uint wparam, long lparam) {int ntmpt = 0; For (INT I = 0; I <= (INT) wparam; I ++) {ntmpt = ntmpt + I ;}sleep (500);: postmessage (hwnd) (getmainwnd ()-> getsafehwnd (), wm_display, ntmpt, null ); return 0;} begin_message_map (ccalculatethread, cwinthread) // {afx_msg_map (ccalculatethread) // note-The classwizard will add and remove mapping macros here. //} afx_msg_map on_thread_message (wm_calculate, oncalculate) // compare it with the main thread. Note the difference between them: end_message_map ()Add one entry at the beginning of the calculatethread. cpp file:
#include "MultiThread7Dlg.h"
The above Code adds the wm_calculate message to the ccalculatethread class, and the response function of the message is oncalculate. The function is to accumulate the message based on the value of the wparam parameter, and the accumulation result is in the Temporary Variable ntmpt, the latency is 0.5 seconds. The wm_display message is sent to the main thread for display, and ntmpt is passed as a parameter.
Compile and run this routine to learn how to transmit messages between threads.
Thread Synchronization
Although multithreading can bring us benefits, there are also many problems to solve. For example, for a dedicated system resource such as a disk drive, because the thread can execute any code segment of the process and the thread runs automatically by the System Scheduling, there is a certain degree of uncertainty, therefore, two threads may simultaneously operate on the disk drive, resulting in an operation error. For example, a computer in the banking system may use one thread to update its user database, another thread is used to read the database to respond to the needs of the depositor. It is very likely that the thread reading the database reads the database that is not completely updated, this may be because only a portion of the data has been updated during reading.
It is called the synchronization of threads to make the threads of the same process work in a coordinated and consistent manner. MFC provides a variety of synchronization objects. The following describes only the four most commonly used objects:
- Ccriticalsection)
- Event (cevent)
- Mutex (cmutex)
- Semaphores (csemaphore)
Through these classes, we can easily achieve thread synchronization.
A. Use the ccriticalsection class
When multiple threads access a dedicated shared resource, you can use the "critical section" object. At any time, only one thread can have an object in the critical section. threads in the critical section can access protected resources or code segments. Other threads wishing to enter the critical section will be suspended and waiting, this ensures that multiple threads do not access Shared resources at the same time.
The procedure for using the ccriticalsection class is as follows:
- Defines a Global Object of the ccriticalsection class (to make each thread accessible), such as the ccriticalsection critical_section;
- Call the member lock () of the ccriticalsection class to obtain the critical zone object before accessing the resources or code to be protected:
critical_section.Lock();
This function is called in the thread to obtain the critical section requested by the thread. If no other thread occupies the critical zone object at this time, the thread that calls lock () gets the critical zone; otherwise, the thread will be suspended and put into a system queue for waiting, until the thread with a critical section has been released.
- After the access to the critical section is complete, use the member function unlock () of the ccriticalsection to release the critical section:
critical_section.Unlock();
In other words, thread a executes the critical_section.lock () statement, if other threads (B) are executing the critical_section.lock (); Statement and critical_section. unlock (); the statement before the statement, thread a will wait until thread B finishes executing critical_section. unlock (); statement, thread a will continue to execute.
The following is an example.
Routine 8 multithread8
- Create a project multithread8 Based on the dialog box. In the idd_multithread8_dialog dialog box, add two buttons and two edit box controls. The IDS of the two buttons are idc_writew and idc_writed, respectively, the title is "Write 'W'" and "Write 'D'" respectively. The IDS of the two edit boxes are idc_w and idc_d, And the read-only attribute is selected;
- Declare two thread functions in the multithread8dlg. h file:
UINT WriteW(LPVOID pParam);UINT WriteD(LPVOID pParam);
- Use classwizard to add the cedit class variables m_ctrlw and m_ctrld to idc_w and idc_d respectively;
- Add the following content to the multithread8dlg. cpp file:
To correctly use the synchronization class in a file, add the following content at the beginning of the file:
#include "afxmt.h"
Defines the critical section and a character array. To be used between different threads, it is defined as a global variable:
CCriticalSection critical_section;char g_Array[10];
Add a thread function:
Uint writew (lpvoid pparam) {cedit * pedit = (cedit *) pparam; pedit-> setwindowtext (""); critical_section.lock (); // lock the critical section, when other threads encounter critical_section.lock (); when the statement is executed, wait // until the critical_section.unlock (); statement for (INT I = 0; I <10; I ++) {g_array [I] = ''w''; pedit-> setwindowtext (g_array); sleep (1000);} critical_section.unlock (); Return 0;} uint writed (lpvoid pparam) {cedit * pedit = (cedit *) pparam; pedit-> setwindowtext (""); critical_section.lock (); // lock the critical section. Other threads encounter critical_section.lock (); wait // until the critical_section.unlock (); statement for (INT I = 0; I <10; I ++) {g_array [I] = ''d ''; pedit-> setwindowtext (g_array); sleep (1000);} critical_section.unlock (); Return 0 ;}
- Double-click idc_writew and idc_writed to add the response function:
void CMultiThread8Dlg::OnWritew() { CWinThread *pWriteW=AfxBeginThread(WriteW, &m_ctrlW, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteW->ResumeThread();}void CMultiThread8Dlg::OnWrited() { CWinThread *pWriteD=AfxBeginThread(WriteD, &m_ctrlD, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteD->ResumeThread(); }Because the code is relatively simple, it is not detailed in detail. Compile and run the routine. You can click two buttons to observe the function of the critical class.
B. Use the cevent class
The cevent class provides event support. An event is a synchronization object that allows a thread to wake up in a certain situation. For example, in some network applications, one thread (as a) is responsible for listening to the communication port, and the other thread (as B) is responsible for updating user data. By using the cevent class, thread a can notify thread B when to update user data. Each cevent object can have two states: Signal State and no signal state. The thread monitors the status of the cevent Class Object in it, and takes corresponding actions accordingly.
In MFC, there are two types of cevent objects: manual events and automatic events. An automatic cevent object is automatically returned to the non-signal state after at least one thread is released. However, after the human event object obtains the signal, it releases the available thread until it calls the member function resetevent () set it to the stateless state. When you create a cevent Class Object, automatic events are created by default. The prototype and parameters of the member functions of the cevent class are described as follows:
1、CEvent(BOOL bInitiallyOwn=FALSE, BOOL bManualReset=FALSE, LPCTSTR lpszName=NULL, LPSECURITY_ATTRIBUTES lpsaAttribute=NULL);
- Binitiallyown: Specifies the initialization status of the event object. True indicates a signal, and false indicates no signal;
- Bmanualreset: Specifies whether the event to be created is a manual event or an automatic event. True indicates a manual event, and false indicates an automatic event;
- The last two parameters are generally set to null, which is not described here.
2、BOOL CEvent::SetEvent();
Set the status of the cevent class object to a signal state. If the event is a manual event, the cevent class object remains in a signal State until the member function resetevent () is called to reset it to a non-signal state. If the cevent class object is an automatic event, after setevent () sets the event to a signal state, the cevent class object is automatically reset from the system to a non-signal state.
If the function is successfully executed, a non-zero value is returned. Otherwise, zero is returned.
3、BOOL CEvent::ResetEvent();
This function sets the event status to stateless, and maintains the status until setevent () is called. Because automatic events are automatically reset by the system, automatic events do not need to call this function. If the function is successfully executed, a non-zero value is returned. Otherwise, zero is returned. We generally call the waitforsingleobject function to monitor the event status. We have already introduced this function. Because of the language description, the understanding of the cevent class is indeed difficult, but you only need to carefully taste the following routine and read it several times.
Routine 9 multithread9
- Create a project multithread9 Based on the dialog box. In the idd_multithread9_dialog dialog box, add a button and two edit box controls. The ID of the button is idc_writew and the title is "Write 'W '"; the IDS of the two edit boxes are idc_w and idc_d, And the read-only attribute is selected;
- Declare two thread functions in the multithread9dlg. h file:
UINT WriteW(LPVOID pParam);UINT WriteD(LPVOID pParam);
- Use classwizard to add the cedit class variables m_ctrlw and m_ctrld to idc_w and idc_d respectively;
- Add the following content to the multithread9dlg. cpp file:
To correctly use synchronization classes in files, add
#include "afxmt.h"
Defines the event object and a character array. To be used between different threads, it is defined as a global variable.
CEvent eventWriteD;char g_Array[10];
Add a thread function:
UINT WriteW(LPVOID pParam){ CEdit *pEdit=(CEdit*)pParam; pEdit->SetWindowText(""); for(int i=0;i<10;i++) { g_Array[i]=''W''; pEdit->SetWindowText(g_Array); Sleep(1000); } eventWriteD.SetEvent(); return 0;}UINT WriteD(LPVOID pParam){ CEdit *pEdit=(CEdit*)pParam; pEdit->SetWindowText(""); WaitForSingleObject(eventWriteD.m_hObject,INFINITE); for(int i=0;i<10;i++) { g_Array[i]=''D''; pEdit->SetWindowText(g_Array); Sleep(1000); } return 0;}By carefully analyzing these two thread functions, you will understand the cevent class correctly. The thread writed executes to waitforsingleobject (eventwrited. m_hobject, infinite); wait until the event eventwrited is a signal and the thread is executed. Because the eventwrited object is an automatic event, when waitforsingleobject () returns, the system automatically resets the eventwrited object to the stateless state.
- Double-click the idc_writew button to add the response function:
void CMultiThread9Dlg::OnWritew() { CWinThread *pWriteW=AfxBeginThread(WriteW, &m_ctrlW, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteW->ResumeThread(); CWinThread *pWriteD=AfxBeginThread(WriteD, &m_ctrlD, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteD->ResumeThread(); }Compile and run the program, and click "Write 'W'" to understand the role of the event object.
C. Use cmutex class
The mutex object is similar to the object in the critical section. The difference between the mutex object and the object in the critical section is that the mutex object can be used between processes, while the object in the critical section can only be used between threads of the same process. Of course, mutex objects can also be used between various threads of the same process, but in this case, using the critical section will save more system resources and improve efficiency.
D. Use the csemaphore class
When a counter is required to limit the number of threads that can be used, the "semaphore" object can be used. The csemaphore class object stores the Count value of the thread that currently accesses a specified resource. The Count value is the number of threads that can still use this resource. If the Count reaches zero, all access attempts to the resources controlled by the csemaphore class object will be put into a queue for waiting until the timeout or the Count value is not zero. When a thread is released and has accessed the protected resource, the Count value is reduced by 1. When a thread completes access to the controlled shared resource, the Count value increases by 1. The maximum number of threads that can be accessed by resources controlled by csemaphore objects at the same time is specified in the object's build function.
The constructor prototype and parameters of the csemaphore class are described as follows:
CSemaphore (LONG lInitialCount=1, LONG lMaxCount=1, LPCTSTR pstrName=NULL, LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);
- Linitialcount: the initial count value of the semaphore object to access the initial value of the number of threads;
- Lmaxcount: Maximum number of threads that can access resources protected by semaphores at the same time;
- The latter two parameters are generally null in the same process and are not discussed too much;
When using the csemaphore constructor to create a semaphore object, you must specify the maximum allowed resource count and the current available resource count. Generally, the current available resource count is set to the maximum Resource Count. Each time a thread is added to access a shared resource, the current available resource count is reduced by 1, as long as the current available resource count is greater than 0, a semaphore signal can be sent. However, when the current available count is reduced to 0, it indicates that the number of threads currently occupying resources has reached the maximum allowed number, and other threads cannot enter, at this time, the semaphore signal cannot be sent. After processing shared resources, the thread should use the releasesemaphore () function to add 1 to the number of currently available resources while leaving.
The following is a simple example to illustrate the csemaphore class usage.
Routine 10 multithread10
- Create a project multithread10 Based on the dialog box. In the idd_multithread10_dialog dialog box, add a button and three edit box controls. The button ID is idc_start, the title is "Write 'A', 'B', and 'C' at the same time". The IDS of the three edit boxes are idc_a, idc_ B, and idc_c, respectively, and the attributes are read-only;
- Declare two thread functions in the multithread10dlg. h file:
UINT WriteA(LPVOID pParam);UINT WriteB(LPVOID pParam);UINT WriteC(LPVOID pParam);
- Use classwizard to add cedit variables m_ctrla, m_ctrlb, and m_ctrlc to idc_a, idc_ B, and idc_c respectively;
- Add the following content to the multithread10dlg. cpp file:
To correctly use the synchronization class in a file, add the following content at the beginning of the file:
#include "afxmt.h"
Defines a semaphore object and a character array. To be used between different threads, it is defined as a global variable:
Csemaphore semaphorewrite (); // The maximum number of threads for resource access is 2. Currently, the number of accessible threads is 2 char g_array [10];
Add three thread functions:
UINT WriteA(LPVOID pParam){ CEdit *pEdit=(CEdit*)pParam; pEdit->SetWindowText(""); WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE); CString str; for(int i=0;i<10;i++) { pEdit->GetWindowText(str); g_Array[i]=''A''; str=str+g_Array[i]; pEdit->SetWindowText(str); Sleep(1000); } ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL); return 0;}UINT WriteB(LPVOID pParam){ CEdit *pEdit=(CEdit*)pParam; pEdit->SetWindowText(""); WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE); CString str; for(int i=0;i<10;i++) { pEdit->GetWindowText(str); g_Array[i]=''B''; str=str+g_Array[i]; pEdit->SetWindowText(str); Sleep(1000); } ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL); return 0;}UINT WriteC(LPVOID pParam){ CEdit *pEdit=(CEdit*)pParam; pEdit->SetWindowText(""); WaitForSingleObject(semaphoreWrite.m_hObject,INFINITE); for(int i=0;i<10;i++) { g_Array[i]=''C''; pEdit->SetWindowText(g_Array); Sleep(1000); } ReleaseSemaphore(semaphoreWrite.m_hObject,1,NULL); return 0;}These three thread functions are no longer mentioned. When the semaphores object has a signal, the thread executes the waitforsingleobject statement to continue the execution. At the same time, the number of available threads is reduced by 1. If the semaphores object has no signal when the thread executes the waitforsingleobject statement, the thread waits here until the semaphore object has a signal thread.
- Double-click the idc_start button to add the response function:
void CMultiThread10Dlg::OnStart() { CWinThread *pWriteA=AfxBeginThread(WriteA, &m_ctrlA, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteA->ResumeThread(); CWinThread *pWriteB=AfxBeginThread(WriteB, &m_ctrlB, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteB->ResumeThread(); CWinThread *pWriteC=AfxBeginThread(WriteC, &m_ctrlC, THREAD_PRIORITY_NORMAL, 0, CREATE_SUSPENDED); pWriteC->ResumeThread(); }
Well, I will introduce multi-threaded programming here. I hope this article will help you.