第十五课 多线程编程
运用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,该函数和主线程并行运行。在调用成功后,CreateThread函数立即返回,ThreadProc也开始运行。因为我们不再用线程句柄,我们立即关闭它以避免内存泄漏。我们前面讲过关闭句柄不会终止线程的执行,而只是减少起引用计数。 
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消息是我们自己定义的,它的定义如下: