用32位汇编语言写一个多线程计数器

进程和线程之间的关系

进程就是正在执行中的应用程序,磁盘上的可执行文件只能称之为文件而不能称之为进程。一个进程是一个执行中的文件使用资源的总和。

线程是操作系统分配处理器时间的基本单位,一个线程可以看成是一个“执行单元”,它负责执行包含在进程空间中的代码。

进程中的代码要运行起来,就必须拥有运行这个代码的“执行单元”,也就是线程。就类似于,一个进程,Windows没有分配时间给它,它里面的代码就没办法执行,只有它拥有了线程之后,里面的代码才会有时间来执行。

一个进程可以有多个线程,当一个进程创建的时候操作系统会自动给它分配一个线程,这个线程从程序的指定入口开始执行,所以这个线程也被成为主线程。进程还可以创建多个线程来执行不同的代码部分,这些代码在我们看来都是同时执行的,实际上在系统内部,线程是挨个执行的。因为我们知道Windows其实是一个分时系统,每个线程都占有一个时间片,只有当系统切换到这个线程的时间片的时候,这个线程的代码才会开始执行,当切换到别的时间片的时候,这个线程的代码就停止执行了。

当主线程执行完最后一条代码时,整个进程也会终止了。但是其他的线程就不会。

一个程序使用多线程能更快是因为它能分到更多的时间片,这样它就能处理更多的信息。

编写一个多线程计数器

如果我们要编写一个计数器,一般想到的就是用一个循环加一加一,每加一次就把数字显示出来。但如果说我们要增加一个停止按钮的话,问题就会出来了,当计算机在执行循环的的时候,我们点击停止按钮,计数是不会有反应的,因为计算机必须得从循环中出来之后再去执行我们的停止指令,而循环停止的条件是计算机执行了停止指令,这样就陷入一个窘境。程序会一直执行循环,我们点窗口的叉叉都没用,只有在任务管理器里面杀掉这个进程。

这个时候,多线程就可以解决这个问题了。通过上面的了解,我们可以创建一个线程专门来执行计数的代码。然后在主线程里面我们可以随时执行终止指令,在另一个线程里面每次循环时都要先检测一下终止标志,如果是终止就退出循环。

与线程有关的函数

CreateThread

invoke    CreateThread,lpThreadAttributes,dwStackSize,lpStartAddress,\
          dwParameter,dwCreationFlags,lpThreadId
.if       eax
          mov    hThread,eax
.endif

该函数创建一个新的线程。

  • lpThreadAttributes:指向一个SECURITY_ATTRIBUTES结构,用来指定线程的安全属性,主要就是用来指定线程是否可以被继承。一般情况下设置为NULL就可以了,这个时候是指定默认的安全属性。
  • dwStackSize:线程的堆栈大小,每个线程都有一个自己的私有的堆栈环境。如果指定为0,那么这个线程的堆栈大小和主线程的堆栈大小是一样的。系统自动在进程的地址空间为每个新线程分配私有的堆栈空间,这些空间在线程结束的时候自动被系统释放,并且如果需要,堆栈空间会自动增长。
  • lpStartAddress:线程开始执行的地址,这个地址其实是一个规定格式的函数的入口地址。所以这个函数也被称为线程函数。
  • dwParameter:传递给线程函数的自定义参数。
  • dwCreationFlags:创建标志。如果为0,则表示线程创建后立刻开始运行,如果指定CREATE_SUSPENDED标志,表示线程创建后处于挂起状态,直到用ResumeThread函数显示地启动线程为止。
  • lpTgreadId:指向一个双字变量,用来接收函数返回的线程ID。线程ID在系统范围内是唯一的,一些函数需要用到线程ID

线程创建成功返回一个线程句柄。

线程函数

线程函数只有一个输入参数,函数的格式一般是:

_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		<resource.h>
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#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函数把线程挂起,这样可是可以,但是我们不知道线程在执行到哪一步的时候停止了,有时候我们需要精确控制线程停止的位置。

这个时候事件就派上用场了。这个就下次再写吧。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值