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

12.4.1 产生同步问题的原因

对于多线程的程序来说,线程之间的同步永远是个重要的问题。如果多个线程都要存取同样的对象(如存取同样的内存变量或读写同一个文件等),而一个线程操作的结果反过来又会影响另一个线程的运行的时候,同步问题就变得异常重要。

产生同步问题的根源在于线程之间的切换是无法预测的,一个线程无法知道什么时候自己的时间片会结束,也无法知道下一个时间片会被分配给哪个线程。事实上,线程可以在任何地方被Windows打断。读者应该记住的是:惟一可以确定的事实就是线程只能在两条指令之间被打断,因为指令是CPU执行的最小单位,线程不可能在一条指令执行到一半的时候被打断。

对于单线程的程序来说,主线程在单个时间片结束的时候被Windows挂起,然后在轮到下一个时间片的时候继续执行,这中间整个进程的环境不会有任何改变,因为进程中不存在其他线程。但对于多线程的程序来说,在主线程挂起的过程中可能会有子线程被分配了时间片,如果子线程在执行中改变了主线程正在存取的对象,就可能会引发错误的结果。举一个例子来说明,假定往银行账户里存钱的操作步骤有3步:

(1)获取账户的余额。

(2)将用户存入的数额和余额相加,得到新的余额。

(3)将账户中的余额数据更新为新的数值。

现在从两个不同的储蓄所里同时向一个账户存钱,假设原来的余额是10 000元,储蓄所A要存入1 000元,储蓄所B要存入2 000元,如果没有同步机制,就可能发生下面的情况:

① 蓄所A首先执行第(1)步,获得余额数据10 000,然后进行第(2)步运算得到结果11 000元。

② 这时储蓄所B的业务也刚好发生,在储蓄所A计算第(2)步的过程中,储蓄所B执行了第(1)步,由于储蓄所A还没有执行到第(3)步,所以账户余额还没有被更新,储蓄所B得到的余额数据还是10 000元。

③ 储蓄所B计算新余额,得到结果12 000元。

④ 在储蓄所B计算新余额的过程中,储蓄所A执行了第(3)步,将余额更新为11 000元。

⑤ 最后,储蓄所B执行了第(3)步,将自己的计算结果12 000元更新到余额数据中。

结果就是储蓄所A的业务实际上是丢失了;另一种情况,假如储蓄所B的动作很快,在上面的第④步骤发生之前,在第③步骤中就将计算结果12 000更新到余额数据中了,那么在第④步骤中储蓄所A的计算结果11 000就会将12 000覆盖,这时的结果就是储蓄所B的业务丢失了,所以同步问题产生的错误结果是很难预测的。

将这个比喻引伸到两个线程的同步问题上就表现在:假如线程A将某个内存变量的值取到eax寄存器中,准备在经过运算后将结果写回去,这时被Windows切换到线程B中,线程B在这个时间片中对同一个内存变量进行了修改,当切换回线程A的时候,线程A在上一个时间片中刚取到eax中的数值和内存变量中的值就不同步了,计算结果当然就是错误的。

有人可能会认为出现这种情况的概率是很低的,线程中有这么多条指令要执行,难道偏偏就在程序取完数据还没开始处理的时候被系统打断吗?通过下面的例子就可以发现发生这种事情的可能性有多大。例子程序位于所附光盘的Chapter12\ThreadSynErr目录中,还是用递增计数器的方法来演示同步问题,ThreadSynErr.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    ?
dwThreads     dd    ?
 
dwOption          dd    ?
F_STOP        equ    0001h
 
dwCounter1    dd    ?
dwCounter2    dd    ?
                 .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)
                         inc    dwCounter1
                       mov    eax,dwCounter2
                        inc    eax
                         mov    dwCounter2,eax
                .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 SetDlgItemInt,hWinMain,IDC_COUNTER1,\
                                 dwCounter1,FALSE
                       invoke SetDlgItemInt,hWinMain,IDC_COUNTER2,\
                               dwCounter2,FALSE
;********************************************************************
                 .elseif eax == WM_COMMAND
                         mov    eax,wParam

上页:第12章 多线程 · 12.3 使用事件对象控制线程(2) 下页:第12章 多线程 · 12.4 线程间的同步(2)

第12章 多线程

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