На этом шаге мы рассмотрим создание и использование потоков.
Теперь пришла пора вплотную заняться потоками. Вначале решим задачу из 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. Результат работы приложения
Трансляция программы:
ML /с /coff /DMASM pr64_1.asm RC pr64_1.rc LINK /SUBSYSTEM:WINDOWS pr64_1.obj pr64_1.res
TASM32 /ml pr64_1.asm BRCC32 pr64_1.rc TLINK32 -aa pr64_1.obj,,,,,pr64_1.res
Возьмите на вооружение весьма полезную функцию Sleep. Эта функция особенно часто используется именно в потоках, с тем чтобы высвободить процессорное время.
На следующем шаге мы рассмотрим взаимодействие потоков.