Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1760113
  • 博文数量: 290
  • 博客积分: 10653
  • 博客等级: 上将
  • 技术积分: 3178
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-24 23:08
文章存档

2013年(6)

2012年(15)

2011年(25)

2010年(86)

2009年(52)

2008年(66)

2007年(40)

分类:

2008-01-08 14:20:32

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.

 

在前一课中,你知道一个进程至少包含一个线程:主线程。一个线程是一执行系列.你也可以在你的程序中创建其它的线程.你能把多线程看作一个程序内的多重任务处理.在执行项中,一个线程是一个和主程序同时运行的函数.你能运行一个函数的几个实例或者依赖于你的需求同时运行几个函数.多线程是WIN32特有的,而在WIN16中并不存在.

 

 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:

 

多个线程运行在同一个进程中,所以他们能访问进程中的任何资源.例如: 全局变量,句柄等等.然而,每一个线程拥有它自己的堆栈所以局部变量在每一个线程中是私有的.每一个线程也拥有它自己的私有寄存器组,所以WINDOWS切换到其它线程时,这些线程能记得它们最后的状态并且当它再一次获得控制权时它能恢复这个任务(一组数据,指令)(在操作系统中引入线程,是为了提高系统的吞吐量和资源利用率,线程切换往往发生在多任务系统中的线程调度,在多任务操作系统中,线程作为调度的最小单位,而进程作为资源分配的最小单位)这些都是在WINDOWS内部处理,对用户而言是透明的.

我们能将线程划分为两类:

  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.

用户界面线程:这种类型的线程创建它们自己的窗口,所以它接收WINDOWS消息并响应它们。能通过它自己的窗口来响应用户,这是它得名(user interface thread )的原因。这种类型的用户线程服从于WIN16中的互斥规则,这条规则是,仅允许一个用户界面线程在16位的usergdi内核中。(对内核独占访问)即当一个用户界面线程正在16位的usergdi内核中执行代码时,其它的用户界面线程就不能够使用16位的user gdi内核提供的服务。注意:这个win16的互斥体是windows95特有的,因为windows 95 中的API函数是16位的形式代码。而windows NT并没有WIN16互斥体,所以在NT中工作的用户界面线程比在windows95中工作的用户界面线程更既有稳定性。

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

工人线程:这种类型的线程并不创建窗口,所以它不能接收任何windows消息。它主要在幕后做那些已分配的作业,这是它得名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.

当用户在win32中应用多线程编程时,我建议你们应用如下策略:让主线程作为用户界面线程,而其它的线程在后台中做那些艰难的工作。使用这种方式,主线程就像是统治者,而其它线程就像是统治者的官员。当统治者想维持和公众的联系时,他就把这些工作委托给他的官员。官员在完成工作后将回去给统治者作汇报。如果统治者将自己完成所有的任务,那么他将不能够给群众更多的关心。这与一个在主线程中忙于处理一个冗长作业的窗口类似:除非工作完成,否则它绝不响应用户。这样在创建程序时为这个程序增加多个线程,让新添加的线程为那些冗长的作业负责,允许主线程响应用户的命令。这对程序来说是有益的。
We can create a thread by calling CreateThread function which has the following syntax:

我们能通过调用CreateThread函数来创建一个线程,这个函数句法如下:

CreateThread proto lpThreadAttributes:DWORD,\
                                dwStackSize:DWORD,\
                                lpStartAddress:DWORD,\
                                lpParameter:DWORD,\
                                dwCreationFlags:DWORD,\
                                lpThreadId:DWORD

CreateThread function looks a lot like CreateProcess.

CreateThread函数看起来和CreateProcess函数有些相像。
lpThreadAttributes  --> You can use NULL if you want the thread to have default security descriptor.                     lpThreadAttributes  -->
如果你想让线程有默认的安全性描述你能用NULL指定这个参数。


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.                                                         dwStackSize -->
指定线程的堆栈大小。如果你想让创建的线程和主线程的堆栈大小一样,用NULL作为这个参数的值。

                                                              
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.

lpStartAddress-->线程函数的开始地址。执行函数是线程的工作。这个函数必须接收一个并且仅接收一个的32位参数然后返回一个32位的值。


lpParameter  --> The parameter you want to pass to the thread function. lpParameter  -->
指定你想传递给线程函数的参数。


dwCreationFlags --> 0 means the thread runs immediately after it's created. The opposite is CREATE_SUSPENDED flag.                   dwCreationFlags -->
创建标记。0意味着线程在被创建后立即执行。于之相对的是CREATE_SUSPENDED标记。


lpThreadId --> CreateThread function will fill the thread ID of the newly created thread at this address.                               lpThreadId -->
创建线程的函数将在这个地址上填充新创建线程的ID

If CreateThread call is sucessful, it returns the handle of the newly created thread. Otherwise, it returns NULL.                           如果CreateThread调用是成功的。它返回新创建线程的局部,否则,返回值为空。


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.

一旦CreateThread函数调用成功,线程函数就立即运行。(注意线程函数和创建线程函数的区别)除非在dwCreationFlags参数中指定CREATE_SUSPENDED标记,如果是这样的话,在ResumeThread函数被调用之前,线程都将被暂停执行。


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.

当线程函数伴随着ret指令的执行而返回时,windows调用ExitThread函数来处理线程函数返回的清理工作。(堆栈平衡,等等)你也可以自己调用这个函数,但这样做几乎没有意义。
You can retrieve the exit code of a thread by calling GetExitCodeThread function.

你能通过调用GetExitCodeThread函数来得到一个线程的退出码。

 


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.

如果你想从其它的线程中终止一个线程,你能调用TerminateThread函数。但是你要在极端的环境下用这个函数,因为这个函数立即就终止了线程而没有给线程任何机会来做清理工作。

Now let's move to the communication methods between threads.
There are three of them:

让我们来看看线程间的通讯方法。这里主要有三种:

  • Using global variables   使用全局变量
  • Windows messages         windows 消息
  • 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.

一个线程共享进程的所有资源,包括全局变量。所有线程能用全局变量来和其它的线程通讯。然而这种方法必须小心使用。线程同步是必须要考虑的一部分。例如:如果两个线程同时使用一个有10个成员的数据结构,当一个线程正在修改这个结构的数据时,windows突然将控制权转交给另外一个线程,什么会产生呢?另外的那个线程将留下不相容数据在这个结构中!(导致了程序的不可再现性)不要犯任何错误,因为调试和维护一个多线程程序是非常困难的。这种程序错误似乎是随机产生的,并且也很难被跟踪到。

 

 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:

你也能用windows消息来完成线程之间的通讯。如果所有的线程都是用户界面线程,这种方法是没有任何问题:这种方法能用于双向通讯。你要做的就是为每一个线程有目的的定义一个或多个windows消息。你可以通过用WM_USER消息作为基值来定义其它的自定义消息。

像这样定义:

        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:

Windows将不使用WM_USER以上的任何值来作为它的消息,所以你能用WM_USER上面的任何值作为你自定义的消息值。如果一个线程是用户界面线程而另外一个线程是工作者线程,你不能用这种方法来进行双向通讯,因为一个工作者线程没有它自己的窗口所以它没有消息队列。但你可以用下面这种方案:

                            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.

事实上,我们将在我们的例子中用这种方法:

最后一种通讯方法是一个事件对象。你能将一个事件对象看作标记的一种。如果事件对象处于“无信号“状态,这个线程是休眠的或者是睡眠的。在这种状态的线程并不接收CPU时间片。当事件对象是”有信号“状态,windows”唤醒“这个线程并让它开始执行已分配任务。

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

你能下载这个例子文件并且运行thread.exe.点“Savage Calculation “菜单项,程序将执行”add eax eax “指令600000000次。注意在这段时间,你不能对主窗口做任何事:你不能移动它,你不能启动它的菜单,等等。当计算完成,一个消息框将出现。在这之后窗口才能接收你正常的命令。为了避免对用户的这种不便,我们能移动计算“程序给一个单独的工作者线程并且让主线程继续做它的用户界面工作。你能看出,虽然主窗口响应慢了很多,但是它任然在响应你。

.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
 

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:

主程序呈现给用户一个正常的窗口和菜单。如果用户选择“CreateThread”菜单项,这个程序将创建一个线程,代码如下:

            .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的过程,它还和主线程并发执行。在成功调用之后,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

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:

如你所见,ThreadProc执行一个原始的计算,这个计算将花费一会儿的时间才能完成,当它完成时它传递一个WM_FINISH消息给主窗口。WM_FINISH是我们自定义的消息,句法如下:

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.

你不必要为WM_USER加上100h 但是这样做比较安全。这个WM_FINISH消息仅仅在我们的程序中才有意义。当主程序接收到wm_finish消息时,它通过弹出一个消息框来响应这个消息,告诉用户计算已经完成。

你能连续的点击“CreateThread” 多次来创建多个线程。


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
  • 在菜单中增加一个菜单项并在菜单中说一些像“Kill Thread”(Caption属性)

 

  • a global variable which is used as a command flag. TRUE=Stop the thread, FALSE=continue the thread
  • 声明一个用来作为命令标志的全局变量,TRUE==停止这个线程,FALSE=继续运行这个线程。

 

  • Modify ThreadProc to check the value of the command flag in the loop.ThreadProc函数的循环语句中添加一段代码用来检查命令标志的值。

 

 

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.

当用户选择”Kill Thread“菜单项时,主程序将命令标志设置为TRUE。当ThreadProc看到命令标志的值为TRUE时,它退出这个循环从而 ends指令了结束这个线程。


This article come from Iczelion's asm page

风向改变翻译于2008-1-8

放假回家在车上耽误了几天,呵呵  

 

文件: tut15.zip
大小: 4KB
下载: 下载

阅读(1019) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~