WIN32汇编语言教程:第12章 多线程 · 12.4 线程间的同步(3)

12.4.2 临界区

了解了同步问题产生的根源,再提出解决方案是很简单的,这在其他的应用程序中早有体现,如各种多用户版的数据库在操作记录之前都要对记录进行锁定,保证一条记录在同一时刻只能被一个对象操作;Windows中的写文件函数在遇到其他程序正在写入中的时候会返回共享错误,而不是不管青红皂白直接写入了事。类似的例子还可以找到很多,归纳起来不外乎一点:就是保证整个存取过程的独占性,在一个线程对某个对象进程操作的过程中,需要有某种机制阻止其他线程的操作。

将这个思路用于多线程之间的同步,可以设计出一些方案来:

(1)设置一个“允许操作”标志变量,当线程需要进行独占操作的时候将标志位复位,操作完成后将标志位置位,任何线程如果需要对对象操作,操作之前必须判断标志位是否置位,如果没有则等待。

(2)如果觉得上面的方法存在CPU占用率的问题,可以使用事件对象来代替自己定义的标志变量。

(3)使用临界区对象(Critical Section Objects)。

考察这些方案,其实方案1和2不一定就能正常工作,因为设置标志和测试标志这个过程是由多条指令完成的,这些指令本身就存在同步问题,比如某个线程测试到标志变量变为“允许”状态,然后它将标志变量的状态复位并开始操作数据,但如果线程在测试标志变量和将标志变量复位之间被打断的话,其他线程可能在这期间也在做同样的事情。将ThreadSynErr例子按照方案1和2修改后运行,就可以发现计数值还是不同步的。

其实Windows提供了专门的解决方案——使用临界区对象。

临界区也是Windows中的一种对象,从理解的角度看,同样可以把它看做是一种标志,只不过多个线程同时操作这个“标志”的时候,由Windows负责标志测试中的同步问题罢了。

临界区对象是定义在数据段中的一个CRITICAL_SECTION结构,结构的具体字段不必关心,也不应该关心,因为它的维护和测试工作都是由Windows来完成的,只需把它想像成一个标志就可以了,结构应当定义成全局变量,因为在各线程中都要测试它。

定义了CRITICAL_SECTION结构后,必须首先对它进行初始化:

   invoke InitializeCriticalSection, lpCriticalSection

lpCriticalSection参数指向数据段中定义的CRITICAL_SECTION结构。

假如将需要独占的工作看成是使用一个单人更衣室,那么标志就相当于更衣室门上的牌子,当一个人进入更衣室的时候,将牌子翻到“里面有人”这一面,出来的时候将牌子翻回到“里面无人”这一面,上面的方法1和2就相当于谁先看到这个牌子,谁就可以进入,当几个人同时看到牌子的时候就产生矛盾了。如果使用临界区,就相当于门口站了一个工作人员(这里就是Windows),只有向他申请后获得允许的人才可以进入,其他的人即使同时提交了申请,也将暂时被拦在外面。

所以,定义并初始化临界区以后,当需要对只能独占的数据进行操作的时候,可以先向Windows递交“进入更衣室”的申请,只有里面没有人,Windows才会答复,这个工作由EnterCriticalSection函数来完成:

   invoke EnterCriticalSection, lpCriticalSection

如果当前由其他线程拥有临界区,函数不会返回,如果函数返回就表示现在可以独占数据了。调用EnterCriticalSection函数可以看成是让Windows检测标志,如果是“不允许”则等待;是“允许”则将标志修改为“不允许”状态并返回。

当完成操作的时候,还要将临界区交还Windows,以便其他线程可以申请使用,这个工作由LeaveCriticalSection函数完成:

   invoke LeaveCriticalSection, lpCriticalSection

LeaveCriticalSection函数的功能可以看成是将标志从“不允许”改回“允许”状态。

当程序不再使用临界区的时候,必须使用DeleteCriticalSection将它删除:

   invoke DeleteCriticalSection, lpCriticalSection

现在用临界区来改进前面的ThreadSynErr程序,修改后的代码在所附光盘的Chapter12\ThreadSyn目录中,其中ThreadSyn.rc文件和ThreadSynErr.rc文件没有改动。修改后的ThreadSyn.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_COUNTER1       equ    1001
IDC_COUNTER2       equ    1002
;####################################################################
; 数据段
;####################################################################
                .data?
hInstance     dd    ?
hWinMain          dd     ?
hWinCount     dd    ?
 
dwCounter1    dd    ?
dwCounter2    dd    ?
 
dwThreads     dd    ?
stCS            CRITICAL_SECTION   <?>
dwOption          dd    ?
F_STOP       equ    0001h
                .const
szStop         db     '停止计数',0
szStart        db     '计数',0
;####################################################################
; 代码段
;####################################################################
                .code
;####################################################################
_Counter          proc      uses ebx esi edi,_lParam
 
                 inc    dwThreads
                 invoke SetWindowText,hWinCount,addr szStop
                 and    dwOption,not F_STOP
 
             .while ! (dwOption & F_STOP)
                     invoke EnterCriticalSection,addr stCS
                     inc    dwCounter1
                     mov    eax,dwCounter2
                     inc    eax
                     mov    dwCounter2,eax
                     invoke LeaveCriticalSection,addr stCS
             .endw
             dec    dwThreads
             invoke SetWindowText,hWinCount,addr szStart
              ret
 
_Counter          endp
;####################################################################
_ProcDlgMain      proc      uses ebx edi esi hWnd,wMsg,wParam,lParam
                local  @dwThreadID
 
                mov    eax,wMsg
;********************************************************************
                .if    eax == WM_TIMER
                         invoke EnterCriticalSection,addr stCS
                        invoke SetDlgItemInt,hWinMain,IDC_COUNTER1,\
                               dwCounter1,FALSE
                         invoke SetDlgItemInt,hWinMain,IDC_COUNTER2,\
                                 dwCounter2,FALSE
                        invoke LeaveCriticalSection,addr stCS
;********************************************************************
                 .elseif eax == WM_COMMAND
                         mov    eax,wParam
                         .if    ax ==  IDOK
                                 .if    dwThreads
                                        or    dwOption,F_STOP
                                       invoke KillTimer,hWnd,1
                                 .else
                                         mov    dwCounter1,0

上页:第12章 多线程 · 12.4 线程间的同步(2) 下页:第12章 多线程 · 12.4 线程间的同步(4)

第12章 多线程

版权所有 © 云南伯恩科技 证书:粤ICP备09170368号