📄 012.txt
字号:
12.1 进程和线程
进程和线程在字面上看起来颇为相近,两者又是息息相关的,所以往往给初学者造成混淆,其实从英文原文来看,进程(Process)和线程(Thread)是完全不同的。在开始介绍本章中的多线程和下一章中有关进程的内容之前,首先在这里介绍进程和线程的概念以及它们之间的联系。
进程是正在执行中的应用程序,磁盘上存储的可执行文件只能称之为文件而不能称为进程,内存中正在执行的文件才叫做进程。一个进程是一个执行中的文件使用资源的总和,包括虚拟地址空间、代码、数据、对象句柄、环境变量和执行单元等。当一个应用程序同时被多次执行时,产生的是多个进程,因为虽然它们由同一个文件执行而来,但是它们的地址空间等资源是互相隔离的,这与不同文件在执行的情况是一样的。
进程是不“活泼”的,要使进程中的代码被真正运行起来,它必须拥有在这个环境中运行代码的“执行单元”,这就是线程,线程是操作系统分配处理器时间的基本单位,一个线程可以看成是一个执行单元,它负责执行包含在进程地址空间中的代码。当一个进程被建立的时候,操作系统会自动为它建立一个线程,这个线程从程序指定的入口地址开始执行(对于Win32汇编,就是源代码最后start指定的入口地址),通常把这个线程称为主线程,当主线程执行完最后一句代码的时候,进程也就没有继续存在的理由了,这时操作系统会撤销进程拥有的地址空间和其他资源,对我们来说,这就意味着程序的终止。
在主线程中,程序可以继续建立多个线程来“同时”执行进程地址空间中的代码,这些线程被称为子线程。操作系统为每个线程保存单独的寄存器环境和单独的堆栈,但是它们共享进程的地址空间、对象句柄、代码和数据等其他资源,它们可以执行相同的代码,可以对相同的数据进行操作,也可以使用相同的句柄。读者可以把一个进程中的多个线程看成是进程范围内的“多任务”。
进程和线程的关系可以看做是“容器”和“内容物”的关系,进程是线程的容器,线程总是在某个进程的环境中被创建,它不可以脱离进程单独存在,而且线程的整个生命周期都存在于进程中,如果进程被结束,其中的线程也就自然结束了。
系统中可以同时存在多个进程,每个进程中同时又可以有多个线程在执行,为了使所有进程中的线程都能够“同时”运行,操作系统为每个线程轮流分配时间片,当轮到一个线程执行的时候,系统将线程的寄存器值恢复回去并开始执行,当时间片结束的时候,系统打断线程的执行,将线程当前的寄存器环境保存下来并切换到另一个线程中去执行,如此循环。当切换到的线程和上一个时间片的线程并不属于同一个进程的时候,操作系统同时切换物理内存到线性地址空间的映射关系,这样线程存取的就是自己所属的进程中的代码和数据。
对于单处理器的计算机来说,不同线程实际上是在轮流使用同一个处理器,一个程序的运行速度并不会因为建立多个线程而加快,因为线程多了以后每个线程等待的时间也就越长,但是对安装了多个处理器的计算机来说,操作系统可以将不同的线程安排到不同的处理器中去执行,这样一个进程中的多个线程就会真正获得多个时间片而加快整个进程的运行速度。当然这个过程还需要操作系统的支持。Windows 9x系统不支持多处理器,即使系统中安装有多个处理器,所有线程还是被安排在同一个处理器上运行,其他的处理器则处于空闲状态。Windows NT系统支持多处理器。
虽然大部分的个人计算机是单处理器的计算机,在应用程序中使用多线程并不能提高程序的运行速度,但多线程编程的出发点并不仅仅是为了使用多处理器,更多的是用来解决一些实际问题。在本章接下来的篇幅中,通过一个典型的“问题程序”来引出多线程编程的内容。
12.2.1 一个单线程的“问题程序”
来看一个“问题程序”,假设编写一个计数程序,程序的要求如下:
● 界面如图12.1所示,用对话框做主界面,对话框中放置“计数”按钮和“暂停/恢复”按钮,并有一个编辑框用来显示计数结果。
● 开始计数之前,“暂停/恢复”按钮处于灰化状态,当开始计数后,该按钮被激活,用户按动一次这个按钮,则计数暂停,再一次按动,则继续计数。
● 开始计数后,“计数”按钮上的文字将被改为“停止计数”,如果按下“停止计数”按钮,程序将恢复初始状态——第一个按钮变回“计数”按钮,同时灰化“暂停/恢复”按钮。
图12.1 一个有问题的计数程序
程序的代码在所附光盘的Chapter12\Counter目录中,里面的Counter.rc文件定义了图12.1所示的对话框,其代码如下:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 1000
#define DLG_MAIN 1000
#define IDC_COUNTER 1001
#define IDC_PAUSE 1002
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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, "宋体"
{
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
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
汇编源文件写起来似乎很简单,只要在WM_COMMAND消息中对“计数”和“暂停/恢复”按钮的动作进行处理就可以了。在“计数”按钮中,可以调用一个计数子程序不停地进行加法运算并将结果显示出来,为了能够随时停止或暂停,可以设置一个标志位,按动“停止计数”或者“暂停/恢复”按钮时设置不同的标志,计数子程序在循环中通过测试这个标志位来决定是否暂停或退出。按照这个思路,程序可以写成目录中Counter.asm所示的样子:
.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 1000
DLG_MAIN equ 1000
IDC_COUNTER equ 1001
IDC_PAUSE equ 1002
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
hWinCount dd ?
hWinPause 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
or dwOption,F_COUNTING
and dwOption,not (F_STOP or F_PAUSE)
invoke SetWindowText,hWinCount,addr 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 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
or dwOption,F_STOP
.else
call _Counter
.endif
.elseif ax == IDC_PAUSE
xor dwOption,F_PAUSE
.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
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -