ITPub博客

首页 > IT职业 > IT生活 > iczelion tut15 (转)

iczelion tut15 (转)

原创 IT生活 作者:gugu99 时间:2007-10-31 16:17:44 0 删除 编辑
iczelion tut15 (转)[@more@]

Tutorial 15: Multithreading Programming


We will learn how to create a multithreading program in this tutorial. We also study the communication methods between the threads.

DOWNLOAD the example here.

Theory:

In previous tutorial, you learn the a process consists of at least one thread: the primary thread. A thread is a chain of execution. You can also create additional threads in your program. You can view multithreading as multitasking within one program. In teRM of implementation, a thread is a function that runs concurrently with the main program. You can run several instances of the same function or you can run several functions simultaneously depending on your requirement. Multithreading is specific to win32, no Win16 counterpart exists.
Threads run in the same process so they can access any resources in the process such as global variables, handles etc. However, each thread has its own stack so local variables in each thread are private. Each thread also owns its private register set so when windows switches to other threads, the thread can "remember" its last status and can "resume" the task when it gains control again. This is handled internally by Windows.
We can divide threads into two caterories:
  1. User interface thread: This type of thread creates its own window so it receives Windows Messages. It can respond to the user via its own window hence the name. This type of thread is subject to Win16 Mutex rule which allows only one user interface thread in 16-bit user and gdi kernel. While a user interface thread is executing code in 16-bit user and gdi kernel, other UI threads cannot use the service of the 16-bit user and gdi kernel. Note that this Win16 Mutex is specific to Windows 95 since underneath, Windows 95 api functions thunk down to 16-bit code. windows NT has no Win16 Mutex so the user interface threads under NT work more smoothly than under Windows 95.
  2. Worker thread: This type of thread does not create a window so it cannot receive any windows message. It exists primarily to do the assigned job in the background hence the name worker thread.
I advise the following strategy when using multithreading capability of Win32: Let the primary thread do user interface stuff and the other threads do the hard work in the background. In this way, the primary thread is like a Governor, other threads are like the Governor's staff. The Governor delegates jobs to his staff while he maintains contact with the public. The Governor staff obediently performs the work and reports back to the Governor. If the Governor were to perform every task himself, he would not be able to give much attention to the public or the press. That's akin to a window which is busy doing a lengthy job in its primary thread: it doesn't respond to the user until the job is completed. Such a program can benefit from creating an additonal thread which is responsible for the lengthy job, allowing the primary thread to respond to the user's commands.
We can create a thread by calling CreateThread function which has the following syntax:

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.

Example:

You should download the example zip file and run thread1.exe. Click the "Savage Calculation" menu item. This will instruct the program to perform "add eax,eax " for 600,000,000 times. Note that during that time, you cannot do anything with the main window: you cannot move it, you cannot activate its menu, etc. When the calculation is completed, a message box appears. After that the window accepts your command normally.
To avoid this type of inconveniece to the user, we can move the "calculation" routine into a separate worker thread and let the primary thread continue with its user interface task. You can see that even though the main window responds more slowly than usual,  it still responds

.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
includelib masm32libuser32.lib
includelib masm32libkernel32.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
 

Analysis:

The main program presents the user with a normal window with a menu. If the user selects "Create Thread" menu item, the program creates a thread as below:

  .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.

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/10748419/viewspace-980170/,如需转载,请注明出处,否则将追究法律责任。

下一篇: iczelion tut14 (转)
请登录后发表评论 登录
全部评论
  • 博文量
    3122
  • 访问量
    2226441