Tutorial 15: Multithreading Programming
Download the example here.
CreateThread proto lpThreadAttributes:DWORD,/ dwStackSize:DWORD,/ lpStartAddress:DWORD,/ lpParameter:DWORD,/ dwCreationFlags:DWORD,/ lpThreadId:DWORD
CreateThread function looks a lot like CreateProcess. lpThreadAttributes --> You can use NULL if you want the thread to have default security descriptor. dwStackSize --> specify the stack size of the thread. If you want the thread to have the same stack size as the primary thread, use NULL as this parameter. lpStartAddress--> Address of the thread function.It's the function that will perform the work of the thread. This function MUST receive one and only one 32-bit parameter and return a 32-bit value. lpParameter --> The parameter you want to pass to the thread function. dwCreationFlags --> 0 means the thread runs immediately after it's created. The opposite is CREATE_SUSPENDED flag. lpThreadId --> CreateThread function will fill the thread ID of the newly created thread at this address.
If CreateThread call is sucessful, it returns the handle of the newly created thread. Otherwise, it returns NULL. The thread function runs as soon as CreateThread call is success ful unless you specify CREATE_SUSPENDED flag in dwCreationFlags. In that case, the thread is suspended until ResumeThread function is called. When the thread function returns with ret instruction, Windows calls ExitThread function for the thread function implicitly. You can call ExitThread function with in your thread function yourself but there' s little point in doing so. You can retrieve the exit code of a thread by calling GetExitCodeThread function. If you want to terminate a thread from other thread, you can call TerminateThread function. But you should use this function under extreme circumstance since this function terminates the thread immediately without giving the thread any chance to clean up after itself.
Now let's move to the communication methods between threads. There are three of them:
Using global variables Windows messages Event Threads share the process's resources including global variables so the threads can use global varibles to communicate with each other. However this method must be used with care. Thread synchronization must enter into consideration. For example, if two threads use the same structure of 10 members , what happens when Windows suddenly yanks the control from one of the thread when it was in the middle of updating the structure? The other thread will be left with an inconsistent data in the structure! Don't make any mistake, multithreading programs are harder to debug and maintain. This sort of bug seems to happen at random which is very hard to track down. You can also use Windows messages to communicate between threads. If the threads are all user interface ones, there's no problem: this method can be used as a two-way communication. All you have to do is defining one or more custom windows messages that are meaningful to the threads. You define a custom message by using WM_USER message as the base value say , you can define it like this:WM_MYCUSTOMMSG equ WM_USER+100h
Windows will not use any value from WM_USER upward for its own messages so you can use the value WM_USER and above as your own custom message value. If one of the thread is a user interface thread and the other is a worker one, you cannot use this method as two-way communication since a worker thread doesn't have its own window so it doesn't have a message queue. You can use the following scheme:
User interface Thread ------> global variable(s)----> Worker thread Worker Thread ------> custom window message(s) ----> User interface Thread
In fact, we will use this method in our example. The last communication method is an event object. You can view an event object as a kind of flag. If the event object is in "unsignalled" state, the thread is dormant or sleeping, in this state, the thread doesn't receive CPU time slice. When the event object is in "signalled" state,Windows "wakes up" the thread and it starts performing the assigned task.
.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 includelib /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 endp
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
end start
.if ax==IDM_CREATE_THREAD mov eax,OFFSET ThreadProc invoke CreateThread,NULL,NULL,eax,/ NULL,0,/ ADDR ThreadID invoke CloseHandle,eax The above function creates a thread that will run a procedure named ThreadProc concurrently with the primary thread. After the successful call, CreateThread returns immediately and ThreadProc begins to run. Since we do not use thread handle, we should close it else there'll be some leakage of memory. Note that closing the thread handle doesn't terminate the thread. Its only effect is that we cannot use the thread handle anymore.
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
As you can see, ThreadProc performs a savage calculation which takes quite a while to finish and when it finishs it posts a WM_FINISH message to the main window. WM_FINISH is our custom message defined like this:
WM_FINISH equ WM_USER+100h You don't have to add WM_USER with 100h but it's safer to do so. The WM_FINISH message is meaningful only within our program. When the main window receives the WM_FINISH message, it respons by displaying a message box saying that the calculation is completed. You can create several threads in succession by selecting "Create Thread" several times. In this example, the communication is one-way in that only the thread can notify the main window. If you want the main thread to send commands to the worker thread, you can so as follows: 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. When the user selects "Kill Thread" menu item, the main program will set the value TRUE in the command flag. When ThreadProc sees that the value of the command flag is TRUE, it exits the loop and returns thus ends the thread.