运用W32的多线程模式来编程,我们可以遵循某种策略:即让主线程仅来做用户界面的工作,而其它繁重的工作则交由工作者线程在后台完成。这就好比我们日常生活中的许多例子。譬如:政府管理者好比是用户界面线程,它负责听取民意,给职能部门分配工作,然后把工作成果汇报给公众。而具体的职能部门就是工作者线程,它负责完成下达的具体工作。如果让政府管理这来具体地做每一件事,它必须作一件事后再做另一项,那它就不能及时来听取和反馈民意。这样就无法管理好一个国家了。当然即使采用多线程制,政府管理部门也不一定就能管理好国家,但是程序却可以采用多线程机制来管理好她自己的工作。我们可以调用CreateThread函数来生成新线程。该函数的语法如下:
CREATETHREAD PROTO LPTHREADATTRIBUTES:DWORD,\ dwStackSize:DWORD,\ lpStartAddress:DWORD,\ lpParameter:DWORD,\ dwCreationFlags:DWORD,\ LPTHREADID: DWORD
生成一个线程的函数和生成一个进程基本相同。
lpThreadAttributes
-->如果您想要线程有缺省的安全属性,可以置该值为NULL。
dwStackSize -->
指定线程的堆栈大小。如果为0,那线程的大小和进程相同。
lpStartAddress-->
线程函数的起始地址。注意该函数仅接收一个32位的参数和返回一个32位的值。(该参数可以是一个指针,而且进程的线程可以直接存取进程定义全局变量,所以您大可不必担心不能如何把大量的参数传递给线程)。
lpParameter --> 传递给线程的上下文。
dwCreationFlags
-->如果是0的话则表示创线程建后立即启动,相反的是标志位CREATE_SUSPENDED,这样您需要稍后显示地让该线程运行。
lpThreadId
--> 内核给新生成的线程分配的线程ID。
如果生成线程成功的话,CreateThread函数就返回新线程的句柄。否则返回NULL。
如果没有给参数dwCreationFlags指定CREATE_SUSPENDED的话,该线程就会立即运行。如果不这样,我们上面说了,需要显示地启动该线程,要这样做您需要调用ResumeThread函数。
在线程返回后(线程的执行类似与执行一个函数,如果它调用了最后一条指令后,在汇编中是ret,那么该线程就结束了,除非您让它进入一个循环,譬如我们讲的用户界面线程就是如此,只不过它不退出的原因是进入的循环是在{while
(
GetMessage(...))...}中,如果您没有给它传递一个值为0的消息,那它可不会退出),系统会自动调用ExitThread函数透明地处理线程一些退出时的清理工作。当然您可以自己调用该函数,但似乎没有什么意义。要得到退出时的退出码,您可以调用GetExitCodeThread函数。
如果您想结束一个程序,可以调用TerminateThread函数,不过使用该函数要小心行事,因为该函数一旦被调用线程就会退出,这样它就没有机会来做清理自己的工作了。
现在我们来看看线程间的通讯机制。
总的说来一共有三种方法:
WM_MYCUSTOMMSG equ WM_USER+100h
小于WM_USER
的值是Windows系统的保留值,大于该值留给用户来使用。
如果其中有一个线程是工作者线程的话,那就不能用该种方法来进行通讯了,这是因为工作者线程没有消息队列。您应当用下面这种策略来进行工作者线程和用户界面线程之间的通讯:
User interface Thread ------> global variable(s)----> Worker thread Worker Thread ------> custom window message(s) ----> User interface Thread
稍后我们的例子中将讲解这种通讯办法。
最后的办法是事件对象。您可以把事件对象看作是一种标志。如果事件对象的状态是无信号的话,说明该线程正在睡眠或挂起,在该种状态下系统是不会给该线程分配CPU时间片的。当一个线程的状态转成有信号时,WINDOWS就会唤醒该线程并且让它正常运行。
.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
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
我们看到上面的线程的代码仅仅是做简单的计数工作,因为我们设了一个很大的基数,所以该线程会持续一段您能感觉得到的时间,当结束后它会向主线程发送WM_FINISH消息。WM_FINISH消息是我们自己定义的,它的定义如下:
WM_FINISH equ WM_USER+100h
WM_USER消息是我们能够使用的最小消息值。
显然我们一看到WM_FINISH,就能从字面上理解该消息的意义。主线程接收到该消息后,会弹出一个对话框告诉用户,计算线程已经结束了。
通过线程之间的通讯,用户可以多次选择"Create
Thread",那样就可以运行多个计算线程了。
本例子中,线程之间的通讯是单向的。如果您想让主线程也能向工作者线程发送消息的话,譬如加入一个菜单项来控制工作者线程的结束,您可以这样做: