Шаг 68.
Критические секции

    На этом шаге мы рассмотрим критические секции и приведем пример их использования.

    Критическая секция - это фрагмент программы, защищенный от одновременного выполнения несколькими потоками. Критическую секцию в данный момент может выполнять только один поток. Рассмотрим функции для работы с критической секцией.

    InitializeCriticaiSection - данная функция создает объект под названием критическая секция. Параметры функции:

    EnterCriticalSection - войти в критическую секцию. После выполнения этой функции данный поток становится владельцем данной секции. Следующий поток, вызвав данную функцию, будет находиться в состоянии ожидания. Параметр функции такой же, что и в предыдущей функции.

    LeaveCriticalSection - покинуть критическую секцию. После этого второй поток, который был остановлен функцией EnterCriticalSection, станет владельцем критической секции. Параметр функции LeaveCriticalSection такой же, как и у предыдущих функций.

    DeleteCriticalSection - удалить объект "критическая секция". Параметр аналогичен предыдущим.

    Программно можно определить несколько объектов критической секции, с которыми будут работать несколько потоков. Мы не зря, говоря о критических секциях, упоминаем только потоки. Разные процессы не могут использовать данный объект синхронизации.

    Теперь рассмотрим пример использования критической секции. Изложим идею, положенную в основу примера. Два потока обращаются время от времени к процедуре, выводящей очередной символ из строки в окно. В результате такой конкурентной деятельности должна быть напечатана строка. Часть процедуры, выводящей очередной символ, сделана критической, поэтому доступ к выводу в окно в данный момент имеет только один поток.

    Файл pr68_1.inc:

;Константы.
;Сообщение приходит при закрытии окна.
WM_DESTROY      equ 2
;Сообщение приходит при создании окна.
WM_CREATE       equ 1
;Сообщение при щелчке левой кнопкой мыши в области окна.
WM_LBUTTONDOWN  equ 201h
;Сообщение при щелчке правой кнопкой мыши в области окна.
WM_RBUTTONDOWN  equ 204h
;Свойства окна.
CS_VREDRAW      equ  1h
CS_HREDRAW      equ  2h
CS_GLOBALCLASS  equ  4000h
WS_OVERLAPPEDWINDOW  equ  000CF0000H
stylcl          equ  CS_HREDRAW+CS_VREDRAW+CS_GLOBALCLASS
DX0             equ  300
DY0             equ  200
;Компоненты цветов.
RED             equ  50
GREEN           equ  50
BLUE            equ  255
RGBW   equ (RED or (GREEN shl 8)) or (BLUE shl 16)
RGBT            equ  255 ;Красный.
;Идентификатор стандартной пиктограммы.
IDI_APPLICATION equ 32512
;Иидентификатор курсора.
IDC_CROSS       equ 32515
;Режим показа окна - нормальный.
SW_SHOWNORMAL   equ 1
;Прототипы внешних процедур.
IFDEF MASM
    EXTERN  Sleep@4:NEAR
    EXTERN  CreateThread@24:NEAR
    EXTERN  InitializeCriticalSection@4:NEAR
    EXTERN  EnterCriticalSection@4:NEAR
    EXTERN  LeaveCriticalSection@4:NEAR
    EXTERN  DeleteCriticalSection@4:NEAR
    EXTERN  GetTextExtentPoint32A@16:NEAR
    EXTERN  CreateWindowExA@48:NEAR
    EXTERN  DefWindowProcA@16:NEAR
    EXTERN  DispatchMessageA@4:NEAR
    EXTERN  ExitProcess@4:NEAR
    EXTERN  GetMessageA@16:NEAR
    EXTERN  GetModuleHandleA@4:NEAR
    EXTERN  LoadCursorA@8:NEAR
    EXTERN  LoadIconA@8:NEAR
    EXTERN  PostQuitMessage@4:NEAR
    EXTERN  RegisterClassA@4:NEAR
    EXTERN  ShowWindow@8:NEAR
    EXTERN  TranslateMessage@4:NEAR
    EXTERN  UpdateWindow@4:NEAR
    EXTERN  TextOutA@20:NEAR
    EXTERN  CreateSolidBrush@4:NEAR
    EXTERN  SetBkColor@8:NEAR
    EXTERN  SetTextColor@8:NEAR
    EXTERN  GetDC@4:NEAR
    EXTERN  DeleteDC@4:NEAR
ELSE
    EXTERN  Sleep:NEAR
    EXTERN  CreateThread:NEAR
    EXTERN  InitializeCriticalSection:NEAR
    EXTERN  EnterCriticalSection:NEAR
    EXTERN  LeaveCriticalSection:NEAR
    EXTERN  DeleteCriticalSection:NEAR
    EXTERN  GetTextExtentPoint32A:NEAR
    EXTERN  CreateWindowExA:NEAR
    EXTERN  DefWindowProcA:NEAR
    EXTERN  DispatchMessageA:NEAR
    EXTERN  ExitProcess:NEAR
    EXTERN  GetMessageA:NEAR
    EXTERN  GetModuleHandleA:NEAR
    EXTERN  LoadCursorA:NEAR
    EXTERN  LoadIconA:NEAR
    EXTERN  PostQuitMessage:NEAR
    EXTERN  RegisterClassA:NEAR
    EXTERN  ShowWindow:NEAR
    EXTERN  TranslateMessage:NEAR
    EXTERN  UpdateWindow:NEAR
    EXTERN  TextOutA:NEAR
    EXTERN  CreateSolidBrush:NEAR
    EXTERN  SetBkColor:NEAR
    EXTERN  SetTextColor:NEAR
    EXTERN  GetDC:NEAR
    EXTERN  DeleteDC:NEAR
    Sleep@4                     = Sleep
    CreateThread@24             = CreateThread
    InitializeCriticalSection@4 = InitializeCriticalSection
    EnterCriticalSection@4      = EnterCriticalSection
    LeaveCriticalSection@4      = LeaveCriticalSection
    DeleteCriticalSection@4     = DeleteCriticalSection
    GetTextExtentPoint32A@16    = GetTextExtentPoint32A
    CreateWindowExA@48          = CreateWindowExA
    DefWindowProcA@16           = DefWindowProcA
    DispatchMessageA@4          = DispatchMessageA
    ExitProcess@4               = ExitProcess
    GetMessageA@16              = GetMessageA
    GetModuleHandleA@4          = GetModuleHandleA 
    LoadCursorA@8               = LoadCursorA 
    LoadIconA@8                 = LoadlconA 
    PostQuitMessage@4           = PostQuitMessage 
    RegisterClassA@4            = RegisterClassA 
    ShowWindow@8                = ShowWindow 
    TranslateMessage@4          = TranslateMessage 
    UpdateWindow@4              = UpdateWindow 
    TextOutA@20                 = TextOutA 
    CreateSolidBrush@4          = CreateSolidBrush 
    SetBkColor@8                = SetBkColor 
    SetTextColor@8              = SetTextColor 
    GetDC@4                     = GetDC 
    DeleteDC@4                  = DeleteDC 
ENDIF
;Структуры.
;Структура сообщения.
MSGSTRUCT  STRUC	
           MSHWND     DD ? ;Идентификатор окна, получающего сообщение.
           MSMESSAGE  DD ? ;Идентификатор сообщения.
           MSWPARAM   DD ? ;Доп. информация о сообщении.
           MSLPARAM   DD ? ;Доп. информация о сообщении.
           MSTIME     DD ? ;Время посылки сообщения.
           MSPT       DD ? ;Положение курсора во время посылки сообщения.
MSGSTRUCT  ENDS	
;-----------------------------------
WNDCLASS  STRUC	
          CLSSTYLE             DD ?
          CLSLPFNWNDPROC       DD ?
          CLSCBCLSEXTRA        DD ?
          CLSCBWNDEXTRA        DD ? 
          CLSHINSTANCE         DD ?
          CLSHICON             DD ? 
          CLSHCURSOR           DD ?
          CLSHBRBACKGROUND     DD ?
          MENNAME              DD ?
          CLSNAME              DD ? 
WNDCLASS  ENDS
;Структура для работы с критической секцией. 
CRIT  STRUC
      DD  ?
      DD  ?
      DD  ?
      DD  ?
      DD  ?
      DD  ? 
CRIT ENDS
;Структура для определения длины текста. 
SIZET STRUC
      X1     DWORD   ?
      Y1     DWORD   ? 
SIZET ENDS

    Файл pr68_1.asm:

.386P
;Плоская модель.
.MODEL FLAT, STDCALL
include pr68_1.inc
;Директивы компоновщику для подключения библиотек.
IFDEF MASM
;Для компоновщика  LINK.EXE.
    includelib \masm32\lib\user32.lib
    includelib \masm32\lib\kernel32.lib
    includelib \masm32\lib\gdi32.lib
ELSE
;Для компоновщика  TLINK32.EXE.
    includelib c:\tasm32\lib\import32.lib
ENDIF
;------------------------------------------------
;Сегмент данных. 
_DATA SEGMENT DWORD PUBLIC USE32 'DATA'
     NEWHWND   DD   0
     MSG       MSGSTRUCT <?>
     WC        WNDCLASS  <?>
     SZT       SIZET     <?>
     HINST     DD 0 ;Дескриптор приложения.
     TITLENAME DB 'Вывод в окно двумя потоками',0
     NAM       DB 'CLASS32',0
     XT        DWORD 30
     YT        DWORD 30
     HW        DD ?
     DC        DD ?
     TEXT      DB 'Текст в окне красный',0
     SPA       DB '            '
               DB '            ',0
     IND       DD 0
     SK        CRIT <?>
     THR1      DD ?
     THR2      DD ?
     FLAG1     DD 0
     FLAG2     DD 0
_DATA ENDS 
;Сегмент кода.
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START: 
;Получить дескриптор приложения.
     PUSH  0
     CALL  GetModuleHandleA@4
     MOV   [HINST], EAX
;-------------------------------------
REG_CLASS:
;Заполнить структуру окна. 
;Стиль.
     MOV   [WC.CLSSTYLE],stylcl 
;Процедура обработки сообщений.
     MOV   [WC.CLSLPFNWNDPROC],OFFSET WNDPROC
     MOV   [WC.CLSCBCLSEXTRA],0
     MOV   [WC.CLSCBWNDEXTRA],0
     MOV   EAX,[HINST]
     MOV   [WC.CLSHINSTANCE],EAX
;Пиктограмма окна.
     PUSH  IDI_APPLICATION
     PUSH  0
     CALL  LoadIconA@8
     MOV   [WC.CLSHICON], EAX
;Курсор окна.
     PUSH  IDC_CROSS
     PUSH  0
     CALL  LoadCursorA@8
     MOV   [WC.CLSHCURSOR], EAX
     PUSH  RGBW   ;Цвет кисти.
     CALL  CreateSolidBrush@4   ;Создать кисть.
     MOV   [WC.CLSHBRBACKGROUND],EAX
     MOV   DWORD  PTR   [WC.MENNAME],0
     MOV   DWORD  PTR   [WC.CLSNAME],OFFSET  NAM
     PUSH  OFFSET WC
     CALL  RegisterClassA@4
;Создать окно зарегистрированного класса.
     PUSH  0
     PUSH  [HINST]
     PUSH  0
     PUSH  0
     PUSH  DY0       ;   DY0  -  высота окна.
     PUSH  DX0       ;   DX0  - ширина окна.
     PUSH  100       ;   Координата Y.
     PUSH  100       ;   Координата X.
     PUSH  WS_OVERLAPPEDWINDOW
     PUSH  OFFSET  TITLENAME   ;Имя  окна.
     PUSH  OFFSET NAM          ;Имя класса.
     PUSH  0
     CALL  CreateWindowExA@48
;Проверка на ошибку.
     CMP   EAX,0
     JZ    _ERR
     MOV   [NEWHWND], EAX   ;Дескриптор окна.
;-------------------------------------
     PUSH  SW_SHOWNORMAL
     PUSH  [NEWHWND]
     CALL  ShowWindow@8   ;Показать созданное окно.
     PUSH  [NEWHWND]
     CALL  UpdateWindow@4   ;Перерисовать  видимую 
                            ;часть  окна.
;Петля обработки сообщений. 
MSG_LOOP:
     PUSH  0
     PUSH  0
     PUSH  0
     PUSH  OFFSET MSG
     CALL  GetMessageA@16
     CMP   AX, 0
     JE    END_LOOP
     PUSH  OFFSET MSG
     CALL  TranslateMessage@4
     PUSH  OFFSET MSG
     CALL  DispatchMessageA@4
     JMP   MSG_LOOP 
END_LOOP: 
;Выход из программы (закрыть процесс).
     PUSH  [MSG.MSWPARAM]
     CALL  ExitProcess@4 
_ERR:
     JMP   END_LOOP
;-------------------------------------
;-------------------------------------
;Процедура окна.
;Расположение параметров в стеке: 
;[ЕВР+014Н] LPARAM 
;[ЕВР+10Н] WAPARAM 
;[ЕВР+0СН] MES 
;[ЕВР+8] HWND 
WNDPROC PROC
        PUSH EBP
        MOV  EBP, ESP
        PUSH EBX
        PUSH ESI
        PUSH EDI
        CMP  DWORD PTR [EBP+0CH],WM_DESTROY
        JE   WMDESTROY
        CMP  DWORD PTR [EBP+0CH],WM_CREATE
        JE   WMCREATE
        CMP  DWORD PTR [EBP+0CH],WM_LBUTTONDOWN
        JNE  CONTIN 
;Проверить флаг запуска.
        CMP  FLAG1,0
        JNE  DEFWNDPROC
        MOV  FLAG1,1 
;Инициализировать указатели.
        LEA  EAX,TEXT
        MOV  IND,EAX
        MOV  XT,30 
;Запуск первого потока.
        PUSH OFFSET  THR1
        PUSH 0
        PUSH EAX
        PUSH OFFSET  THREAD1
        PUSH 0
        PUSH 0
        CALL CreateThread@24 
;Запуск второго потока.
        PUSH OFFSET  THR2
        PUSH 0
        PUSH EAX
        PUSH OFFSET  THREAD2
        PUSH 0
        PUSH 0
        CALL CreateThread@24
        JMP  DEFWNDPROC 
CONTIN:
        CMP  DWORD PTR [EBP+0CH],WM_RBUTTONDOWN
        JNE  DEFWNDPROC 
;Проверить флаг запуска.
        CMP  FLAG2,0
        JNE  DEFWNDPROC
        MOV  FLAG2,1 
;Инициализировать указатели.
        LEA  EAX,SPA
        MOV  IND,EAX
        MOV  XT,30 
;Запуск первого потока.
        PUSH OFFSET THR1
        PUSH 0
        PUSH EAX
        PUSH OFFSET THREAD1
        PUSH 0 
        PUSH 0
        CALL CreateThread@24 
;Запуск второго потока.
        PUSH OFFSET THR2
        PUSH 0
        PUSH EAX
        PUSH OFFSET THREAD2
        PUSH 0
        PUSH 0
        CALL CreateThread@24
        JMP  DEFWNDPROC 
WMCREATE:
        MOV  EAX,DWORD PTR [EBP+08H] 
;Запомнить дескриптор окна в глобальной переменной.
        MOV  HW,EAX 
;Инициализировать критическую секцию.
        PUSH OFFSET SK
        CALL InitializeCriticalSection@4
        MOV  EAX, 0
        JMP  FINISH 
DEFWNDPROC:
        PUSH DWORD PTR [EBP+14H]
        PUSH DWORD PTR [EBP+10H]
        PUSH DWORD PTR [EBP+0CH]
        PUSH DWORD PTR [EBP+08H]
        CALL DefWindowProcA@16
        JMP  FINISH 
WMDESTROY: 
;Удалить критическую секцию.
        PUSH OFFSET SK
        CALL DeleteCriticalSection@4
        PUSH 0
        CALL PostQuitMessage@4 ;WM_QUIT
        MOV  EAX, 0 
FINISH:
        POP  EDI
        POP  ESI
        POP  EBX
        POP  EBP
        RET  16
WNDPROC ENDP
;-------------------------------------
;Вывод
OUTSTR  PROC 
;Проверяем, не закончился ли текст.
        MOV  EBX,IND
        CMP  BYTE PTR [EBX],0
        JNE  NO_0
        RET 
NO_0:
;Вход в критическую секцию.
        PUSH OFFSET  SK
        CALL EnterCriticalSection@4
        PUSH HW
        CALL GetDC@4
        MOV  DC,EAX
;Цвет фона = цвет окна.
        PUSH RGBW
        PUSH EAX
        CALL SetBkColor@8
;Цвет текста (красный).
        PUSH RGBT
        PUSH DC
        CALL SetTextColor@8
;Вывести текст.
        PUSH 1
        PUSH IND
        PUSH YT
        PUSH XT
        PUSH DC
        CALL TextOutA@20 
;Вычислить длину текста в пикселях текста.
        PUSH OFFSET SZT
        PUSH 1
        PUSH IND
        PUSH DC
        CALL GetTextExtentPoint32A@16
;Увеличить указатели.
        MOV  EAX,SZT.X1
        ADD  XT,EAX
        INC  IND
;Закрыть контекст.
        PUSH DC
        CALL DeleteDC@4 
;Выход из критической секции.
        PUSH OFFSET SK
        CALL LeaveCriticalSection@4
        RET
OUTSTR  ENDP 
;-------------------------------------
;Первый поток. 
THREAD1 PROC 
L01: 
;Проверить, не конец ли текста.
        MOV  EBX,IND
        CMP  BYTE PTR [EBX],0
        JE   _END1 
;Вывод очередного символа.
        CALL OUTSTR 
;Задержка.
        PUSH 1000
        CALL Sleep@4
        JMP  L01 
_END1:
        RET  4
THREAD1 ENDP 
;-------------------------------------
;Второй поток. 
THREAD2 PROC 
L02: 
;Проверить, не конец ли текста.
        MOV  EBX,IND
        CMP  BYTE PTR [EBX],0
        JE   _END2 
;Вывод очередного символа.
        CALL OUTSTR 
;Задержка.
        PUSH 1000
        CALL Sleep@4
        JMP  L02
_END2:
        RET  4
THREAD2 ENDP 
_TEXT   ENDS	
        END START
Текст этой программы можно взять здесь.

    Результат работы приложения изображен на рисунке 1:


Рис.1. Результат работы приложения

    Трансляция программы:

    При нажатии левой кнопки мыши начинается вывод текстовой строки. При нажатии правой кнопки мыши - выведенная строка стирается. Флаги FLAG1 и FLAG2 введены для того, чтобы вывод строки и вывод пустой строки можно было производить только один раз. Для того чтобы несколько замедлить вывод текста, мы вводим задержку (Sleep) в цикл вызова процедуры OUTSTR в каждом потоке. Обратите внимание, что буквы выводятся в окно в основном парами. Объясняется это тем, что пока один из потоков выводит символ, второй уже ждет разрешения, и, как только первый поток выходит из критической секции, второй поток сразу выводит следующий символ. После в обоих потоках срабатывает задержка (функция Sleep).

    Завершая разговор о критических секциях, отметим, что это наиболее быстрый способ синхронизации. К минусам данного подхода относится невозможность доступа к секции сразу нескольких потоков, а также отсутствие специальных средств, чтобы подсчитывать число обращений к ресурсу.

    На следующем шаге мы рассмотрим взаимоисключения.




Предыдущий шаг Содержание Следующий шаг