进程和线程之间的关系
进程就是正在执行中的应用程序,磁盘上的可执行文件只能称之为文件而不能称之为进程。一个进程是一个执行中的文件使用资源的总和。
线程是操作系统分配处理器时间的基本单位,一个线程可以看成是一个“执行单元”,它负责执行包含在进程空间中的代码。
进程中的代码要运行起来,就必须拥有运行这个代码的“执行单元”,也就是线程。就类似于,一个进程,Windows没有分配时间给它,它里面的代码就没办法执行,只有它拥有了线程之后,里面的代码才会有时间来执行。
一个进程可以有多个线程,当一个进程创建的时候操作系统会自动给它分配一个线程,这个线程从程序的指定入口开始执行,所以这个线程也被成为主线程。进程还可以创建多个线程来执行不同的代码部分,这些代码在我们看来都是同时执行的,实际上在系统内部,线程是挨个执行的。因为我们知道Windows其实是一个分时系统,每个线程都占有一个时间片,只有当系统切换到这个线程的时间片的时候,这个线程的代码才会开始执行,当切换到别的时间片的时候,这个线程的代码就停止执行了。
当主线程执行完最后一条代码时,整个进程也会终止了。但是其他的线程就不会。
一个程序使用多线程能更快是因为它能分到更多的时间片,这样它就能处理更多的信息。
编写一个多线程计数器
如果我们要编写一个计数器,一般想到的就是用一个循环加一加一,每加一次就把数字显示出来。但如果说我们要增加一个停止按钮的话,问题就会出来了,当计算机在执行循环的的时候,我们点击停止按钮,计数是不会有反应的,因为计算机必须得从循环中出来之后再去执行我们的停止指令,而循环停止的条件是计算机执行了停止指令,这样就陷入一个窘境。程序会一直执行循环,我们点窗口的叉叉都没用,只有在任务管理器里面杀掉这个进程。
这个时候,多线程就可以解决这个问题了。通过上面的了解,我们可以创建一个线程专门来执行计数的代码。然后在主线程里面我们可以随时执行终止指令,在另一个线程里面每次循环时都要先检测一下终止标志,如果是终止就退出循环。
与线程有关的函数
CreateThread
invoke CreateThread,lpThreadAttributes,dwStackSize,lpStartAddress,\
dwParameter,dwCreationFlags,lpThreadId
.if eax
mov hThread,eax
.endif
该函数创建一个新的线程。
线程创建成功返回一个线程句柄。
线程函数
线程函数只有一个输入参数,函数的格式一般是:
_ProcThread proc uses ebx esi edi lParam
local 局部变量
;要执行的代码
mov eax,返回码
ret
_ProcThread endp
函数的名称可以自定义,只要在用CreateThread函数创建线程的时候指定函数地址就可以了,函数的参数也是用CreateThread函数创建线程时的dwParamter参数传递的。
如果觉得一个参数不够用,我们可以通过全局变量来传递参数,各个子线程和主线程时共享一同个地址空间。
终止线程
线程执行完该线程函数的最后一条代码后线程就会自然终止,然后操作系统会进行下面几个操作:
上面这个是线程自然退出的情况,当然我们也可以手动随时结束线程,下面是终止线程的一些函数:
ExitThread
invoke ExitThread,dwExitCode
参数是线程的退出码。这个函数用于退出本线程,也就是说它不能用于在另一个线程中退出其他线程,只能用在自己线程的代码里面退出自己线程。它的效果和用ret返回是一样的。
TerminateThread
invoke TerminateThread,hThread,dwExitCode
第一个参数是要终止的线程的句柄,第二个参数是要终止的线程的退出码。也就是说这个函数可以在一个线程里面终止另一个线程。函数执行成功返回非0值,失败返回0。注意:这个函数是一个异步函数,也就是说,函数返回非0值后,线程不一定就被终止了,可能要过一会再终止。并且,这个函数是很不建议使用的。因为你不知道它会再什么时候停下,这样就可能导致一些意想不到的结果。
挂起线程
如果我们不想终止线程但是想让它暂时停下来,可以用函数将线程挂起。
SuspendThread
invoke SuspendThread,hThread
该函数将i线程挂起,参数时要挂起的线程的句柄。挂起后系统就不会再给这个线程分配时间片。
ResumeThread
invoke ResumeThread,hThread
该函数将挂起的线程恢复到原来的状态。参数是线程的句柄。
系统为每个线程维护一个暂定计数器,当用SuspendThread函数于某个线程的时候,该线程暂定计数器会加一,当暂定计数器大于0的时候,线程就挂起了。同样,当用ResumeThread函数于某个线程的时候,该线程暂定计数器就会减一,只有计数器减到0的时候下线程才会恢复运行。
SuspendThread函数执行成功返回的是线程的暂停计数器此时的值,执行失败返回-1。ResumeThread函数的返回值定义和这个一样。
多线程计数器源码
先放出计数器的资源文件:
#include
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 0x1000
#define DLG_MAIN 0x1000
#define IDC_COUNTER 0x1001
#define IDC_PAUSE 0x1002
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 227,187,129,48
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "计数器"
FONT 9,"宋体"
BEGIN
LTEXT "计数值: ",-1,10,10,34,8
EDITTEXT IDC_COUNTER,47,8,71,12,ES_READONLY | WS_BORDER | WS_TABSTOP
PUSHBUTTON "计数",IDOK,8,27,50,14
PUSHBUTTON "暂停/继续",IDC_PAUSE,68,27,50,14,WS_DISABLED | WS_TABSTOP
END
然后下面是多线程计数器的源代码:
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;include文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Equ
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000h
DLG_MAIN equ 1000h
IDC_COUNTER equ 1001h
IDC_PAUSE equ 1002h
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
hWinCount dd ?
hWinPause dd ?
hEvent dd ? ;事件对象句柄
dwOption dd ? ;标志位
F_PAUSE equ 0001h ;暂停标志
F_STOP equ 0002h ;停止标志
F_COUNTING equ 0004h ;继续标志
.const
szStop db '停止计数',0
szStart db '计数',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Counter proc uses ebx esi edi lParam
or dwOption,F_COUNTING ;设置状态为正在计数中
and dwOption,not (F_STOP or F_PAUSE)
invoke SetWindowText,hWinCount,offset szStop
invoke EnableWindow,hWinPause,TRUE
xor ebx,ebx
.while !(dwOption & F_STOP) ;当状态为停止时不进入循环,否则执行循环
.if !(dwOption & F_PAUSE) ;当状态不为暂停时进入循环
inc ebx
invoke SetDlgItemInt,hWinMain,\
IDC_COUNTER,ebx,FALSE
.endif
.endw
invoke SetDlgItemInt,hWinMain,\
IDC_COUNTER,0,FALSE
invoke SetWindowText,hWinCount,addr szStart
invoke EnableWindow,hWinPause,FALSE
and dwOption,not (F_COUNTING or F_STOP or F_PAUSE)
ret
_Counter endp
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
local @dwThreadID
mov eax,wMsg
.if eax == WM_COMMAND
mov eax,wParam
.if ax == IDOK
.if dwOption & F_COUNTING
invoke SetEvent,hEvent ;当状态是正在计数,则这是停止计数的消息,因为按下停止计数后线程会关闭,下次按就是计数,所以要提前设置事件为置位
or dwOption,F_STOP
.else
invoke CreateThread,NULL,0,\ ;创建一个新的进程
offset _Counter,NULL,\
NULL,addr @dwThreadID
invoke CloseHandle,eax
.endif
.elseif ax == IDC_PAUSE
xor dwOption,F_PAUSE ;设置状态为暂停
.if dwOption & F_PAUSE
invoke ResetEvent,hEvent
.else
invoke SetEvent,hEvent
.endif
.endif
.elseif eax == WM_CLOSE
invoke EndDialog,hWnd,NULL
.elseif eax == WM_INITDIALOG
push hWnd
pop hWinMain
invoke GetDlgItem,hWnd,IDOK
mov hWinCount,eax ;“计数”控制句柄
invoke GetDlgItem,hWnd,IDC_PAUSE
mov hWinPause,eax ;“暂停”控制句柄
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,eax,DLG_MAIN,\
NULL,offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
end start
程序编译链接后执行:
点击执行:
看起来好像没问题哈,但是如果有心的话,我们可以打开任务管理器看看这个程序。
下面那个Counter.exe就是我们的计数程序,然后我们点暂停看看:
可以看到程序占用的CPU还是差不多,按理说我们按下暂停就没有进行计数了啊。但是看程序的话就知道了,我们按下暂停后,线程函数依然停留在循环当中,虽然没有进行计数,但是循环一直在继续,所以CPU的占用依然还是差不多高。
但是这样的话,如果一个程序的线程很多,那么占用的资源就就可能不能接受了。可能有人会说可以用SuspendThread函数把线程挂起,这样可是可以,但是我们不知道线程在执行到哪一步的时候停止了,有时候我们需要精确控制线程停止的位置。
这个时候事件就派上用场了。这个就下次再写吧。