⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 012.txt

📁 会变语言实现的一些程序
💻 TXT
📖 第 1 页 / 共 5 页
字号:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; 代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

        .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

 


          .if   ax == IDOK

            .if   dwThreads

          or    dwOption,F_STOP

              invoke  KillTimer,hWnd,1

          .else

          mov   dwCounter1,0

          mov   dwCounter2,0

      xor   ebx,ebx

      .while  ebx < 10

          invoke  CreateThread,NULL,0,\

        offset _Counter,NULL,\

          NULL,addr @dwThreadID

      invoke  CloseHandle,eax

      inc ebx

      .endw

      invoke  SetTimer,hWnd,1,500,NULL

      .endif

      .endif

;********************************************************************

    .elseif eax ==  WM_CLOSE

    .if   ! dwThreads

      invoke  EndDialog,hWnd,NULL

        .endif

;********************************************************************

      .elseif eax ==  WM_INITDIALOG

      push     hWnd

      pop   hWinMain

      invoke  GetDlgItem,hWnd,IDOK

          mov   hWinCount,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

目录中的ThreadSynErr.rc文件定义了如图12.3所示的界面。



图12.3  多线程同步的演示程序

ThreadSynErr.rc文件的代码为:

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

#include       

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

#define ICO_MAIN       1000

#define DLG_MAIN       1000

#define IDC_COUNTER1     1001

#define IDC_COUNTER2     1002

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ICO_MAIN       ICON     "Main.ico"

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

DLG_MAIN DIALOG 227, 187, 129, 56

STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU

CAPTION "多线程同步的演示程序"

FONT 9, "宋体"

{

 LTEXT "计数器一:", -1, 7, 7, 40, 8

 LTEXT "计数器二:", -1, 7, 22, 41, 8

 EDITTEXT IDC_COUNTER1, 51, 5, 71, 12, ES_READONLY | WS_BORDER | WS_TABSTOP

 EDITTEXT IDC_COUNTER2, 51, 20, 71, 12, ES_READONLY | WS_BORDER | WS_TABSTOP

 PUSHBUTTON "计数", IDOK, 72, 36, 50, 14

}

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

“问题程序”还是用循环计数的功能来演示,程序中设置了两个全局变量dwCounter1和dwCounter2用做计数器,当按下“计数”按钮的时候,程序将两个计数器清零,并且用循环语句建立10个线程来同时执行_Counter线程函数:

    mov   dwCounter1,0

    mov   dwCounter2,0

    xor   ebx,ebx

    .while  ebx < 10

      invoke  CreateThread,NULL,0,offset _Counter,NULL,\

      NULL,addr @dwThreadID

      invoke  CloseHandle,eax

      inc   ebx

    .endw

    invoke  SetTimer,hWnd,1,500,NULL

最后,程序设立一个定时器来定时将计数器值显示到编辑框中。

在线程函数中,使用下面的计算代码:

  .while  ! (dwOption & F_STOP)

    inc   dwCounter1

    mov   eax,dwCounter2

    inc   eax

    mov   dwCounter2,eax

  .endw

在这段代码中,递增第一个计数器使用inc指令,由于计算用单条指令完成,所以计数器一不会因为同步问题出错,递增第二个计数器的代码使用了3条指令,首先将原来的计数值取到eax中,递增eax后再写回到变量中,如果不存在多个线程同步的问题,这两种算法的结果是一样的,显示到编辑框中的计数值应该是相等的。

在存在同步问题的情况下,如果线程在mov eax,dwCounter2或者inc eax指令执行后被打断,并且其他线程在这期间修改了dwCounter2的值的话,根据前面的分析,就会有一次计数值被丢失。如果显示到编辑框中的计数值不相等,则证明存在同步的问题,通过比较两个计数值的差值,还可以得知同步问题发生的机会是多少。

好了,大家可能都迫不及待地想看运行结果了吧,结果如图12.3所示,这是程序在笔者的450 MHz的计算机上运行了10秒以后的结果,可以看到,10个线程加起来总共进行了 783 189 430次计算,计数器二却丢失了783 189 430-274 090 739=509 098 691次计数,因为同步问题丢失的计数竟然占了65%,可见这绝对不是偶尔发生一次两次的事情,大家可以想像一下,如果有人往一个银行账户中汇款,三笔汇款中丢了两笔,人们会有何感想呢?

 

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递交“进入更衣

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -