Шаг 64.
Потоки

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

    Теперь пришла пора вплотную заняться потоками. Вначале решим задачу из 61 шага при помощи потока.

    Поток может быть создан при помощи функции CreateThread. Рассмотрим параметры этой функции.

    Как уже было сказано, выполнение потока начинается с потоковой функции. Окончание работы этой функции приводит к естественному окончанию работы потока. Поток также может закончить свою работу, выполнив функцию ExitThread с указанием кода выхода. Наконец, порождающий поток может закончить работу порожденного потока при помощи функции TerminateThread. В нашем примере запускаемый процесс не может сам закончить свою работу и прекращает свою работу вместе с приложением по команде TerminateThread. Надо сказать, что такое завершение, вообще говоря, является аварийным и не рекомендуется к обычному употреблению. Связано это с тем, что при таком завершении не выполняется никаких действий по освобождению занятых ресурсов (блоки памяти, открытые файлы и т. п.). Поэтому стройте свои приложения так, чтобы поток завершался по выходу из потоковой процедуры. Таким образом, в некотором смысле приведенный ниже пример является демонстрацией того, чего не рекомендуется делать.

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

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

    Файл pr64_1.rc:

//Определение констант.
#define WS_SYSMENU         0x00080000L
//Элементы на окне должны быть изначально видимы.
#define WS_VISIBLE         0x10000000L
//Бордюр вокруг элемента.
#define WS_BORDER          0x00800000L
//Возможность фокусировать  элемент
//при помощи клавиши TAB.
#define WS_TABSTOP         0x00010000L
//Текст в окне редактирования прижат к левому краю.
#define ES_LEFT            0x0000L
//Стиль всех элементов на окне.
#define WS_CHILD           0x40000000L
//Запрещается ввод с клавиатуры.
#define ES_READONLY        0x0800L
//Стиль - кнопка.
#define BS_PUSHBUTTON      0x00000000L
//Центрировать  текст на  кнопке.
#define BS_CENTER          0x00000300L
//Стиль - диалоговое  окно Windows 95.
#define DS_3DLOOK          0x0004L
//Определение диалогового окна.
DIAL1 DIALOG 0, 0, 240, 100
STYLE  WS_SYSMENU | DS_3DLOOK
CAPTION "Пример использования потока"
FONT 8, "Arial"
{
//Окно редактирования, идентификатор 1.
CONTROL "", 1, "edit", ES_LEFT | WS_CHILD | WS_VISIBLE | 
      WS_BORDER | WS_TABSTOP | ES_READONLY, 
      100, 5, 130, 12 
//Кнопка, идентификатор 2. 
CONTROL "Выход", 2, "button", BS_PUSHBUTTON | BS_CENTER | 
     WS_CHILD | WS_VISIBLE | WS_TABSTOP, 180, 76, 50, 14
}

    Файл pr64_1.inc:

;Константы.
;Сообщение приходит при закрытии окна.
WM_CLOSE      equ 10h
;Сообщение приходит при создании окна.
WM_INITDIALOG equ 110h
;Сообщение приходит при событии 
;с элементом на окне.
WM_COMMAND    equ 111h
;Сообщение посылки текста элементу.
WM_SETTEXT    equ  0Ch
;Прототипы внешних процедур.
IFDEF MASM
    EXTERN  SendMessageA@16:NEAR
    EXTERN  GetDlgItem@8:NEAR
    EXTERN  Sleep@4:NEAR
    EXTERN  TerminateThread@8:NEAR  
    EXTERN  CreateThread@24:NEAR
    EXTERN  wsprintfA:NEAR
    EXTERN  GetLocalTime@4:NEAR
    EXTERN  ExitProcess@4:NEAR
    EXTERN  GetModuleHandleA@4:NEAR
    EXTERN  DialogBoxParamA@20:NEAR
    EXTERN  EndDialog@8:NEAR
ELSE
    EXTERN  SendMessageA:NEAR
    EXTERN  GetDlgltem:NEAR
    EXTERN  Sleep:NEAR
    EXTERN  TerminateThread:NEAR
    EXTERN  CreateThread:NEAR
    EXTERN  wsprintfA:NEAR
    EXTERN  GetLocalTime:NEAR
    EXTERN  ExitProcess:NEAR
    EXTERN  GetModuleHandleA:NEAR
    EXTERN  DialogBoxParamA:NEAR
    EXTERN  EndDialog:NEAR
    SendMessageA@16    = SendMessageA
    GetDlgItem@8       = GetDlgltem
    Sleep04            = Sleep
    TerminateThread@8  = TerminateThread
    CreateThread@24    = CreateThread
    wsprintfA          = wsprintfA
    GetLocalTime@4     = GetLocalTime 
    ExitProcess@4      = ExitProcess 
    GetModuleHandleA@4 = GetModuleHandleA 
    DialogBoxParamA@20 = DialogBoxParamA 
    EndDialog@8        = EndDialog
ENDIF
;Структуры.
;Структура сообщения.
MSGSTRUCT  STRUC	
           MSHWND     DD ? ;Идентификатор окна, получающего сообщение.
           MSMESSAGE  DD ? ;Идентификатор сообщения.
           MSWPARAM   DD ? ;Доп. информация о сообщении.
           MSLPARAM   DD ? ;Доп. информация о сообщении.
           MSTIME     DD ? ;Время посылки сообщения.
           MSPT       DD ? ;Положение курсора во время посылки сообщения.
MSGSTRUCT ENDS	
;Структура данных дата-время
DAT   STRUC		
       year    DW ?	
       month   DW ?
       dayweek DW 9	
       day     DW 9	
       hour    DW 9	
       min     DW 9	
       sec     DW 9	
       msec    DW 9	
DAT   ENDS		

    Файл pr64_1.asm:

.386P
;Плоская модель.
.MODEL FLAT, STDCALL
include pr64_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'
     MSG        MSGSTRUCT <?>
     HINST      DD 0 ;Дескриптор приложения.
     PA         DB "DIAL1",0
     TIM        DB  "Дата  %u/%u/%u  Время  %u:%u:%u",0
     STRCOPY    DB  50   DUP(?)
     DATA       DAT <0>
     HTHR       DD   ?
_DATA ENDS 
;Сегмент кода.
_TEXT SEGMENT DWORD PUBLIC USE32 'CODE'
START: 
;Получить дескриптор приложения.
     PUSH  0
     CALL  GetModuleHandleA@4
     MOV   [HINST], EAX
;-------------------------------------
;Создать диалоговое окно.
     PUSH  0
     PUSH  OFFSET WNDPROC
     PUSH  0
     PUSH  OFFSET PA
     PUSH  [HINST] 
     CALL  DialogBoxParamA@20 
     CMP   EAX,-1 
     JNE   KOL 
;Здесь можно разместить сообщение об ошибке.
KOL:
;-------------------------------------
     PUSH  0
     CALL  ExitProcess@4
;-------------------------------------
;-------------------------------------
;Процедура окна.
;Расположение параметров в стеке: 
;[ЕВР+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_CLOSE
        JNE  L1
L3: 
;Здесь реакция на закрытие окна. 
;Удалить поток.
        PUSH 0
        PUSH HTHR
        CALL TerminateThread@8 
;Закрыть диалог.
        PUSH 0
        PUSH DWORD PTR [EBP+08H]
        CALL EndDialog@8
        JMP  FINISH 
L1:
        CMP  DWORD PTR [EBP+0CH],WM_INITDIALOG
        JNE  L2
;Здесь начальная инициализация. 
;Получить дескриптор окна редактирования.
        PUSH 1
        PUSH DWORD PTR [EBP+08H]
        CALL GetDlgItem@8 
;Создать поток.
        PUSH OFFSET HTHR  ;Сюда дескриптор потока.
        PUSH 0
        PUSH EAX     ;Параметр.
        PUSH OFFSET GETTIME ;Адрес процедуры.
        PUSH 0
        PUSH 0
        CALL CreateThread@24 
        JMP  FINISH 
L2:
        CMP  DWORD PTR [EBP+0CH],WM_COMMAND
        JNE  FINISH 
;Кнопка выхода?
        CMP  WORD PTR [EBP+10H],2
        JE   L3  
FINISH:
        POP  EDI
        POP  ESI
        POP  EBX
        POP  EBP
        MOV  EAX,0
        RET  16
WNDPROC ENDP
;-------------------------------------
;Потоковая функция.
; [EBP+8]  параметр=дескриптор окна редактирования 
GETTIME PROC
        PUSH EBP
        MOV  EBP,ESP 
L0: 
;Задержка в 1 секунду.
        PUSH 1000
        CALL Sleep@4 
;Получить локальное время.
        PUSH OFFSET DATA
        CALL GetLocalTime@4 
;Получить строку для вывода даты и времени.
        MOVZX EAX,DATA.sec
        PUSH EAX
        MOVZX EAX,DATA.min 
        PUSH EAX
        MOVZX EAX,DATA.hour
        PUSH EAX
        MOVZX EAX,DATA.year
        PUSH EAX
        MOVZX EAX,DATA.month
        PUSH EAX
        MOVZX EAX,DATA.day
        PUSH EAX
        PUSH OFFSET TIM
        PUSH OFFSET STRCOPY
        CALL wsprintfA 
;Отправить строку в окно редактирования.
        PUSH OFFSET STRCOPY
        PUSH 0
        PUSH WM_SETTEXT
        PUSH DWORD PTR [EBP+08H]
        CALL SendMessageA@16
        JMP  L0 ;Бесконечный цикл.
        POP  EBP
        RET  4
GETTIME ENDP 
_TEXT   ENDS	
        END START
Текст этой программы можно взять здесь.

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


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

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

    Возьмите на вооружение весьма полезную функцию Sleep. Эта функция особенно часто используется именно в потоках, с тем чтобы высвободить процессорное время.

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




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