Fifteenth lesson multi-thread programming
In this lesson, we will learn how to make multi-threaded programming. In addition, we will also learn how to communicate between different threads.
Theory: In the previous lesson, we learned the process, which told each process to have at least one main thread. This thread is actually a clue execution of the process. In addition to this main thread, you can add other threads to the process, i.e., add other execution clues, and to some extent, it can be seen to an application increase Multi-tasking feature. Once the program runs, you can hang or run these threads based on a variety of conditions, especially in the multi-CPU environment, these threads are running concurrent. These are the concepts of W32, and there is no equality concept under Win16.
The advantage of running different threads in the same process is that these threads can share the resources of the process, such as global variables, resources, etc. Of course, each thread can also have its own private stack for saving private data. Alternatively, each thread needs to save its context to remember or restore its context when switching, of course, is done by the operating system, which is transparent to the user.
We can divide thread into two categories:
Handling threads for user interface: This type of thread generates its own window and is responsible for processing related window messages. The user interface thread complies with the mutual exclusive principle under Win16, that is, the kernel function in the User and the GDI library is not allowed to be in the GDI or User, that is, when a user interface program is in GDI or User, the kernel does not allow heavy Into. Thus we can inquire about the code of the Win95's part of the kernel, comply with 16-bit mode. Winows NT is a pure 32-bit operating system, so there is no such problem. Worker thread: This type of thread does not have to process window interfaces, of course, no processing message. It is generally running in the background to do some coarse, which is probably what it is called the worker thread.
Using the W32 multi-thread mode to program, we can follow some strategy: let the main thread just do the user interface, and other heavy work will be completed in the background in the background. This is like many examples in our daily lives. For example, government administrators are like user interface threads. It is responsible for listening to public opinion, assigns work to the functional department, and reports the results of the work to the public. The specific functional department is the worker thread, which is responsible for completing the specific work of the following. If the government is managing this to make everything, it must be another one after another, then it can't be listening and feedback public opinion. This will not manage a country. Of course, even if you adopt multi-wire-proof, government administration does not necessarily manage the country, but the program can use multi-threaded mechanism to manage her own work. We can call the CreateThread function to generate a new thread. The syntax of this function is as follows:
Createthread Proto LPTHREADATTRIBUTES: DWORD, / DWSTACKSIZE: DWORD, / LPSTARTDRESS: DWORD, / LPPARATER: DWORD, / DWCREATIONFLAS: DWORD, / LPTHREADID: DWORD
Generate a function of a thread and generate a process basically the same. LPTHREADATTRIBUTES -> If you want the thread to have default security properties, you can set this value null. DWSTACKSIZE -> Specify the stack size of the thread. If 0, the size and process of the thread is the same. LPSTARTADDRESS -> The start address of the thread function. Note that this function receives only one 32-bit parameter and returns a 32-bit value. (This parameter can be a pointer, and the process thread can directly access the process definition global variable, so you must have a lot to pass a lot of parameters to threads). LPParameter -> The context passed to the thread. DWCREATIONFLAGS -> If it is 0, start immediately after the creation is built, the opposite is the flag bit CREATE_SUSPENDED, so you need to display the thread later. LPTHREADID -> The thread ID assigned to the newly generated thread. If the generated thread is successful, the CreateTHRead function returns the handle of the new thread. Otherwise returns NULL. If you do not specify create_suspended to the parameter dwcreationFlags, the thread will run immediately. If so, we said above, you need to start the thread, you have to do this, you need to call the ResumeThRead function. After the thread returns (the thread is similar to the execution of a function, if it is called the last instruction, it is RET in the assembly, then the thread is over, unless you let it enter a loop, such as the user interface we speak The thread is like this, but it does not exit because the loop is in {while (getMessage (...) ...}, if you do not send it a message that is 0, then it will not Exit), the system automatically calls the exitthread function to transparently handle some cleaning work when some of the thread exits. Of course, you can call this function yourself, but there seems to be no significance. To get an exit code when you exit, you can call the getExitCodetThread function. If you want to end a program, you can call the TerminateThread function, but use this function to take care, because the function will exit once the call is called, so that it doesn't have a chance to make your own work.
Now let's take a look at the communication mechanism between threads. That is, there are three ways:
Using the global variable Using the Windows Message Delivery Mechanism Using the Event Top We say that the thread share the resources of the process, where global variables are included, so threads can be communicated by using global variables. However, the obvious disadvantage of this approach is that the problem of synchronization must be considered when there are multiple threads to access the same global variable. For example, there is a structure with ten member variables. One of the threads assumes that only five member variables are updated, and the scheduling thread is deprived of the row to another thread, so that If you want to use the global structural variable, it is obviously wrong. Additional multi-threaded procedures are also difficult to debug, especially these mistakes are very concealed and difficult to reset. If both threads are user interface threads, communication with Windows messages is relatively convenient.
All you have to do is customize some Windows messages (note don't conflict with Windows predefined messages) and then pass between threads. You can define the message like this, regard the WM_USER (which is equal to 0x0400) as a base, and then sequentially order numbers, such as:
WM_MYCUSTOMMSG EQU WM_USER 100H
The value smaller than the WM_USER is the retention value of the Windows system, which is greater than the value left to the user. If there is a thread that is a worker thread, then it cannot communicate with this method because the worker thread has no message queue. You should use the following strategy communication between workers thread and user interface thread: user interface thread ------> Global variable (s) ----> worker thread worker thread ----- -> Custom WinDow Message (s) ----> User Interface Thread
This way in our example will be explained later. The final method is an event object. You can see the event object as a sign. If the status of the event object is no signal, it means that the thread is sleeping or hang, in which the system does not assign CPU time slice to the thread. When a thread is turned into a signal, Windows wakes up the thread and allows it to operate.
Example: You can download the example and run Thread1.exe, then activate the menu item "Savage Calculation", then start executing the instruction "Add Eax, Eax", execute 600,000,000 times, you will find it in this process, the user interface will stop Response, you can neither use the menu, or you cannot use the mobile window. When the calculation is complete, a dialog box will pop up, and the window can be turned off and the window can be run as normal.
In order to avoid this inconvenience, we put the calculated work into a separate worker thread, while the main window only responds to the user's activities. You can see although the user interface is slower than usual, it is still possible.
.386 .model flat, stdcall option casemap: none WinMain proto: DWORD,: DWORD,: DWORD,: DWORD include /masm32/include/windows.inc include /masm32/include/user32.inc include / masm32 / include / kernel32. Inc includelib /masm32/lib/user32.lib incruDelib /masm32/lib/kernel32.lib
.const idm_create_thread EQU 1 IDM_EXIT EQU 2 WM_FINISH EQU WM_USER 100H
.DATA CLASSNAME DB "WIN32ASMTHREADCLASS", 0 AppName DB "Win32 ASM Multithreading Example", 0 Menuname DB "Firstmenu", 0 SuccessString DB "The Calculation IS Completed!", 0
.data? hinstance hinstance? CommandLine LPSTR? HWND HANDLE? Threadid DWORD?
. Code Start: Invoke GetModuleHandle, Null Mov Hinstance, Eax Invoke Getcommandline Mov Commandline, Eax Invoke Winmain, Hinstance, Null, CommandLine, SW_SHOWDEFAULT INVOKE EXITPROCESS, EAX
WinMain proc hInst: HINSTANCE, hPrevInst: HINSTANCE, CmdLine: LPSTR, CmdShow: DWORD LOCAL wc: WNDCLASSEX LOCAL msg: MSG mov wc.cbSize, SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc. cbClsExtra, NULL mov wc.cbWndExtra, NULL push hInst pop wc.hInstance mov wc.hbrBackground, COLOR_WINDOW 1 mov wc.lpszMenuName, OFFSET MenuName mov wc.lpszClassName, OFFSET ClassName invoke LoadIcon, NULL, IDI_APPLICATION mov wc.hIcon, eax mov wc.hIconSm, eax invoke LoadCursor, NULL, IDC_ARROW mov wc.hCursor, eax invoke RegisterClassEx, addr wc invoke CreateWindowEx, WS_EX_CLIENTEDGE, aDDR ClassName, aDDR AppName, / WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, / CW_USEDEFAULT, 300,200, NULL, NULL, / hInst, Null Mov Hwnd, Eax Invoke ShowWindow, Hwnd, SW_SHOWNORMAL INVOKE UPDATEWINDOW, HWND .WHILE TRUE INVOKE GETMESSAGE, ADDR MSG, NULL, 0, 0 .Break .if (! EAX) Invoke TranslateMessage, Addr Msg Invoke DispatchMessage, Addr Msg .Endw Mov Eax, Msg.wParam Ret Winmain Endp
WndProc proc hWnd: HWND, uMsg: UINT, wParam: WPARAM, lParam: LPARAM .IF uMsg == WM_DESTROY invoke PostQuitMessage, NULL .ELSEIF uMsg == WM_COMMAND mov eax, wParam .if lParam == 0 .if ax == IDM_CREATE_THREAD mov eax, OFFSET ThreadProc invoke CreateThread, NULL, NULL, eax, / 0, / ADDR ThreadID invoke CloseHandle, eax .else invoke DestroyWindow, hWnd .endif .endif .ELSEIF uMsg == WM_FINISH invoke MessageBox, NULL, ADDR SuccessString, ADDR AppName, MB_OK .ELSE invoke DefWindowProc, hWnd, uMsg, wParam, lParam ret .ENDIF xor eax, eax ret WndProc endpThreadProc PROC USES ecx Param: DWORD mov ecx, 600000000 Loop1: add eax, eax dec ecx jz get_out jmp Loop1 get_out: invoke PostMessage, HWND, WM_FINISH, NULL, NULL RET ThreadProc ENDP
End Start
Analysis: The main thread of the main program is a user interface thread, which has a normal window. Users choose the menu item "create thread", the program generates a thread:
.if ax == IDM_CREATE_THREAD mov eax, OFFSET ThreadProc invoke CreateThread, NULL, NULL, eax, / NULL, 0, / ADDR ThreadID invoke CloseHandle, eax above code segment to produce a thread, the thread is a function of the ThreadProc subject code, the function And the main thread is run in parallel. After the call is successful, the CreateThread function returns immediately, and ThreadProc has started running. Because we no longer use the thread handle, we immediately turn off it to avoid memory leakage. We have told that the handle does not terminate the execution of the thread, but only reduces the reference count.
ThreadProc PROC USES ecx Param: DWORD mov ecx, 600000000 Loop1: add eax, eax dec ecx jz Get_out jmp Loop1 Get_out: invoke PostMessage, hwnd, WM_FINISH, NULL, NULL ret ThreadProc ENDP we see above-threaded code is just to do simple Counting work, because we have a big base, so the thread will continue to feel the time you can feel, when it is over, it sends a WM_FINISH message to the main thread. The WM_FINISH message is our own definition, it is defined as follows:
WM_FINISH EQU WM_USER 100H
The WM_USER message is the minimum message value we can use.
Obviously we see WM_FINISH, you can understand the meaning of the message from the literal. After receiving the message, the main thread will pop up a dialog telling the user that the calculation thread is over.
With communication between threads, users can select "CREATE THREAD" multiple times, so they can run multiple compute threads.
In this example, communication between threads is unidirectional. If you want the main thread to send a message to worker threads, such as adding a menu item to control the end of the worker thread, you can do this:
add a menu item saying something like "Kill Thread" in the menu a global variable which is used as a command flag. TRUE = Stop the thread, FALSE = continue the thread Modify ThreadProc to check the value of the command flag in the loop. Set up a global variable, when the thread starts, we set its value to false, when the user activates the menu item we add, this value becomes true. Decision each time the thread is reduced, it is judged that the value is determined, if the thread is thread, the thread is ended and exits the thread.