платформа Win32 - Туториалы Iczelion'a на русском, адаптированные для FreeBasic. Урок 15 - Треды (ветви)

Предыдущая тема Следующая тема Перейти вниз

платформа Win32 - Туториалы Iczelion'a на русском, адаптированные для FreeBasic. Урок 15 - Треды (ветви)

Сообщение  electrik в Пн Июн 29, 2009 6:47 pm

Win32 API. Урок 15. Треды (ветви)

Мы узнаем, как создать мультитредную программу. Мы также изучим методы, с помощью которых треды могут общаться друг с другом.

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

Теория:

В предыдущем туториале, вы изучили процесс, состоящий по крайней мере из одного треда: основного. Тред - это цепь инструкций. Вы также можете создавать дополнительные треды в вашей программе. Вы можете считать мультитрединг как многозадачность внутри одной программы. Если говорить в терминах непосредственной реализации, тред - это функция, которая выполняется параллельно с основной программой. Вы можете запустить несколько экземпляров одной и той же функции или вы можете запустить несколько функций одновременно, в зависимости от ваших требований. Мультитрединг свойственен Win32, под Win16 аналогов не существует.

Треды выполняются в том же процессе, поэтому они имеют доступ ко всем ресурсам процесса: глобальным переменным, хэндлам и т.д. Тем не менее, каждый тред имеет свой собственный стек, так что локальные переменные в каждом треде приватны. Каждый тред также имеет свой собственный набор регистров, поэтому когда Windows переключается на другой тред, предыдущий "запоминает" свое состояние и может "восстановить" его, когда он снова получает контроль. Это обеспечивается
внутренними средствами Windows. Мы можем поделить треды на две категории:

1. Тред интерфейса пользователя: тред такого типа создает свое собственное окно, поэтому он получает оконные сообщения. Он может отвечать пользователю с помощью своего окна. Этот тип тредов действует согласно Win16 Mutex правилу, которое позволяет только один тред пользовательского интерфейса в 16-битном пользовательском и gdi-ядре. Пока один подобный тред выполняет код 16-битного пользовательского и gdi-ядра, другие UI треды не могут использовать сервисы этого ядра. Заметьте, что этот Win16 Mutex свойственен Windows 9x, так как его функции обращаются к 16-битному коду. В Windows NT нет Win16 Mutex'а, поэтому треды пользовательского интерфейса под NT работают более плавно, чем под Windows 95.
2. рабочий тред: Этот тип тредов не создает окно, поэтому он не может принимать какие-либо windows-сообщения. Он существует только для того, чтобы делать предназначенную ему работу на заднем фоне (согласно своему названию).

Я советую следующую стратегию при использовании мультитредовых способностей Win32: позвольте основному треду делать все, что связанно с пользовательским интерфейсом, а остальным делать тяжелую работу в фоновом режиме. В этом случае, основной тред - Правитель, другие треды - его помощники. правитель поручает им определенные задания, в то время как сам общается с публикой. Его помощники послушно выполняют работу и докладывают об этом Правителю. Если бы Правитель
делал всю работу сам, он бы не смог уделять достаточно внимания народу или прессе. Это похоже на окно, которое занято продолжительной работой в основном треде: оно не отвечает пользователю, пока работа не будет выполнена. Такая программа может быть улучшена созданием дополнительного треда, который возьмет
часть работы на себя и позволит основной ветви отвечать на команды пользователя.

Мы можем создать тред с помощью вызова функции CreateThread, которая имеет следующий синтаксис:

function CreateThread _
(byval lpThreadAttributes as LPSECURITY_ATTRIBUTES, _
byval dwStackSize as DWORD, _
byval lpStartAddress as LPTHREAD_START_ROUTINE, _
byval lpparameter as PVOID, _
byval dwCreationFlags as DWORD, _
byval lpThreadId as PDWORD) as HANDLE

Функция CreateThread похожа на Createprocess.

• lpThreadAttributes --> Вы можете использовать NULL, если хотите, чтобы у треда были установки безопасности по умолчанию.
• dwStackSize --> укажите размер стека треда. Если вы хотите, чтобы тред имел такой же размер стека, как и у основного, используйте NULL в качестве параметра.
• lрStartAddress --> Адрес функции треда. Эта функция будет выполнять предназначенную для треда работу. Эта функция должна получать один и только один 32-битный
параметр и возвращать 32-битное значение.
• lparameter --> Параметр, который вы хотите передать функции треда.
• dwCreationFlags --> 0 означает, что тред начинает выполняться сразу же после его создания. Для обратного можно использовать флаг CREATE_SUSPEND.
• lpThreadId --> CreateThread поместит сюда ID созданного треда.

Если вызов CreateThread прошел успешно, она возвращает хэндл созданного треда, в противном случае она возвращает NULL.

Функция треда запускается так скоро, как только заканчивается вызов CreateThread, если только вы не указали флаг CREATE_SUSpENDED. В этом случае тред будет заморожен до вызова функции ResumThread.

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

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

Теперь давайте рассмотрим методы коммуникации между тредами. Вот три из них:

• Использование глобальных переменных
• Windows-сообщения
• События

Треды разделяют ресурсы процесса, включая глобальные переменные, поэтому треды могут использовать их для того, чтобы взаимодействовать друг с другом. Тем не менее, этот метод должен использоваться осторожно. Синхронизацию нужно внимательно спланировать. Например, если два треда используют одну и ту же структуру из 10 членов, что произойдет, если Windows вдруг передаст управление от одного треда другому, когда структура обновлена еще только наполовину. Другой тред получит неправильную информацию! Не сделайте никакой ошибки, мультитредовые программы тяжелее отлаживать и поддерживать. Этот тип багов случается непредсказуемо и их очень трудно отловить.

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

        #define   WM_MYCUSTOMMSG WM_USER + &h100

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

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

• Тред с пользовательским интерфейсом ----> глобальная переменная(ные) ----> рабочий тред
• рабочий тред ----> windows-сообщение ----> Тред с пользовательским интерфейсом

Фактически, мы будем использовать этот метод в нашем примере.

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

Пример:

Код:

' Треды
#include "windows.bi"
#include "l15_thread.bi"
declare function WinMain ( byval hInst as HINSTANCE, _
byval hPrevInst as HINSTANCE, _
  byval szCmdLine as LPSTR, _
  byval iCmdShow as integer ) as integer
declare sub ThreadProc(byval param as uinteger)

' начало программы
dim shared hwnd as HANDLE ' хэндл окна
end WinMain( GetModuleHandle( NULL ) ,NULL,GetCommandLine() , SW_SHOWNORMAL ) ' вызвать основную функцию
' здесь заканчивается программа

' процедура окна
function  WndProc _
(byval hwnd as HWND, _ ' хэндл окна
byval uMsg as UINT, _ ' сообщение
byval wParam as WPARAM, _ ' дополнительный  параметр сообщений
byval lParam as LPARAM) as LRESULT ' дополнительный параметр сообщений

function = 0
select case uMsg  'начинаем обработку сообщений
case WM_DESTROY ' если пользователь закрывает окно
PostQuitMessage(0) ' выходим из программы
exit function
case WM_COMMAND ' если получили сообщение WM_COMMAND
if lParam = 0 then ' если равно 0, значит идут сообщения от меню
    select case loword(wParam) ' начинаем обрабатывать элементы меню
case IDM_CREATE_THREAD ' если в меню выбрали Create Thread
ThreadProc()
case IDM_EXIT
DestroyWindow(hWnd)
end select
end if
case WM_FINISH ' если сообщение WM_FINISH
    MessageBox(NULL,SuccessString,AppName,MB_OK)
end select
function = DefWindowProc(hWnd,uMsg,wParam,lParam) ' Дефаултная функция обработки окна
end function

'функция WinMain
function WinMain _
(byval hInst as HINSTANCE, _ ' хэндл программы
byval hPrevInst as HINSTANCE, _ 'в win32 всегда 0
byval szCmdLine as LPSTR, _  'указатель на командную строку
byval iCmdShow as integer ) as integer  ' состояние окна при первом появлении

dim wc as WNDCLASSEX ' структура параметров окна
dim wMsg as MSG  ' структура сообщений

 'структура класса окна wc
' заполняем структуру wc
wc.cbSize = SIZEOF( WNDCLASSEX )  ' размер структуры WNDCLASSEX
wc.style = CS_HREDRAW or CS_VREDRAW  ' Стиль окна
wc.lpfnWndProc = @WndProc ' Адрес процедуры окна WndProc
wc.cbClsExtra = NULL  ' резервирование  дополнительных байт за концом структуры
wc.cbWndExtra = NULL
wc.hInstance = hInst  ' хэндл модуля
wc.hbrBackground = cast(HGDIOBJ, COLOR_WINDOW+1) ' Цвет фона
wc.lpszMenuName = @MyMenu ' хэндл меню
wc.lpszClassName = @ClassName ' имя класса окна
wc.hIcon = LoadIcon( NULL,IDI_APPLICATION ) ' Хэндл иконки
wc.hIconSm = wc.hIcon 'Хэндл маленькой иконки
wc.hCursor = LoadCursor( NULL,IDC_ARROW) ' Хэндл курсора


' регистрация нашего класса окна
if(RegisterClassEx(@wc) = FALSE) then
MessageBox(0,"Не могу зарегистрировать класс окна","Ошибка",0)
end 1
end if

' Создадим окно
hwnd = CreateWindowEx _
(NULL, _ ' дополнительные стили
ClassName, _ ' строка с именем класса окна
AppName, _ ' строка с именем окна
WS_OVERLAPPEDWINDOW, _ ' стиль окна
CW_USEDEFAULT, _ ' X
CW_USEDEFAULT, _ ' Y
CW_USEDEFAULT, _ ' ширина окна
CW_USEDEFAULT, _ ' высота окна
NULL, _ ' хэндл родительского окна
NULL, _ ' хэндл меню
hInst, _ ' хэндл модуля
NULL) ' указатель на структуру данных

ShowWindow( hwnd,iCmdShow) ' отобразить наше окно на десктопе
UpdateWindow( hwnd) ' обновить клиентскую область

while( GetMessage( @wMsg, NULL, 0, 0 ) <> FALSE )  'цикл сообщений
TranslateMessage( @wMsg )
DispatchMessage( @wMsg )
wend
function = wMsg.wParam
end function

sub ThreadProc()
dim in as integer
    dim de as integer = 600000000
    
do
        in +=1
        de -=1
in -=3
loop until de = 1
    postMessage _ ' добавим в очередь сообщений
    (hwnd, _ ' хэндл окна
    WM_FINISH, _ ' сообщение WM_FINISH
    NULL,NULL)
    end sub

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


Последний раз редактировалось: electrik (Вт Май 23, 2017 5:44 pm), всего редактировалось 1 раз(а)

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Re: платформа Win32 - Туториалы Iczelion'a на русском, адаптированные для FreeBasic. Урок 15 - Треды (ветви)

Сообщение  electrik в Пн Июн 29, 2009 6:51 pm

Win32 API. Урок 15. Треды (ветви) - продолжение

файл thread.bas

Код:

' треды
#include "windows.bi"
#include "thread.bi"
declare function WinMain ( byval hInst as HINSTANCE, _
byval hPrevInst as HINSTANCE, _
  byval szCmdLine as LPSTR, _
  byval iCmdShow as integer ) as integer
declare sub ThreadProc()

' начало программы
dim shared hwnd as HANDLE ' хэндл окна
dim shared hThread as handle ' хэндл треда
dim shared ThreadID as uInteger ' идентификатор треда
end WinMain( GetModuleHandle( NULL ) ,NULL,GetCommandLine() , SW_SHOWNORMAL ) ' вызвать основную функцию
' здесь заканчивается программа

' процедура окна
function  WndProc _
(byval hwnd as HWND, _ ' хэндл окна
byval uMsg as UINT, _ ' сообщение
byval wParam as WPARAM, _ ' дополнительный  параметр сообщений
byval lParam as LPARAM) as LRESULT ' дополнительный параметр сообщений

function = 0
select case uMsg  'начинаем обработку сообщений
case WM_DESTROY ' если пользователь закрывает окно
PostQuitMessage(0) ' выходим из программы
exit function
case WM_COMMAND ' если получили сообщение WM_COMMAND
if lParam = 0 then ' если равно 0, значит идут сообщения от меню
    select case loword(wParam) ' начинаем обрабатывать элементы меню
case IDM_CREATE_THREAD ' если в меню выбрали Create Thread
hThread = CreateThread _ ' создадим тред
(NULL, _ ' атрибуты  безопасности
NULL, _ ' размер стека
cPtr(LPTHREAD_START_ROUTINE,@ThreadProc), _ ' указатель на функцию
0, _ ' параметр передаваемый функции
0, _ ' дополнительные флаги
@ThreadID) ' указатель на переменную, куда поместится идентификатор треда
CloseHandle _ ' закроем хэндл
(hThread) ' хэндл треда
case IDM_EXIT
DestroyWindow(hWnd)
end select
end if
case WM_FINISH ' если сообщение WM_FINISH
    MessageBox(NULL,SuccessString,AppName,MB_OK)
end select
function = DefWindowProc(hWnd,uMsg,wParam,lParam) ' Дефаултная функция обработки окна
end function


'функция WinMain
function WinMain _
(byval hInst as HINSTANCE, _ ' хэндл программы
byval hPrevInst as HINSTANCE, _ 'в win32 всегда 0
byval szCmdLine as LPSTR, _  'указатель на командную строку
byval iCmdShow as integer ) as integer  ' состояние окна при первом появлении

dim wc as WNDCLASSEX ' структура параметров окна
dim wMsg as MSG  ' переменная сообщений

 'структура класса окна wc
' заполняем структуру wc
wc.cbSize = SIZEOF( WNDCLASSEX )  ' размер структуры WNDCLASSEX
wc.style = CS_HREDRAW or CS_VREDRAW  ' Стиль окна
wc.lpfnWndProc = @WndProc ' Адрес процедуры окна WndProc
wc.cbClsExtra = NULL  ' резервирование  дополнительных байт за концом структуры
wc.cbWndExtra = NULL
wc.hInstance = hInst  ' хэндл модуля
wc.hbrBackground = cast(HGDIOBJ, COLOR_WINDOW+1) ' Цвет фона
wc.lpszMenuName = @MyMenu ' хэндл меню
wc.lpszClassName = @ClassName ' имя класса окна
wc.hIcon = LoadIcon( NULL,IDI_APPLICATION ) ' Хэндл иконки
wc.hIconSm = wc.hIcon 'Хэндл маленькой иконки
wc.hCursor = LoadCursor( NULL,IDC_ARROW) ' Хэндл курсора


' регистрация нашего класса окна
if(RegisterClassEx(@wc) = FALSE) then
MessageBox(0,"Не могу зарегистрировать класс окна","Ошибка",0)
end 1
end if

' Создадим окно
hwnd = CreateWindowEx _
(NULL, _ ' дополнительные стили
ClassName, _ ' строка с именем класса окна
AppName, _ ' строка с именем окна
WS_OVERLAPPEDWINDOW, _ ' стиль окна
CW_USEDEFAULT, _ ' X
CW_USEDEFAULT, _ ' Y
CW_USEDEFAULT, _ ' ширина окна
CW_USEDEFAULT, _ ' высота окна
NULL, _ ' хэндл родительского окна
NULL, _ ' хэндл меню
hInst, _ ' хэндл модуля
NULL) ' указатель на структуру данных

ShowWindow( hwnd,iCmdShow) ' отобразить наше окно на десктопе
UpdateWindow( hwnd) ' обновить клиентскую область

while( GetMessage( @wMsg, NULL, 0, 0 ) <> FALSE )  'цикл сообщений
TranslateMessage( @wMsg )
DispatchMessage( @wMsg )
wend
function = wMsg.wParam
end function

sub ThreadProc(byval param as uinteger)
dim in as integer
    dim de as integer = 600000000
   
do
        in +=1
        de -=1
in -=3
loop until de = 1
    postMessage _ ' добавим в очередь сообщений
    (hwnd, _ ' хэндл окна
    WM_FINISH, _ ' сообщение WM_FINISH
    NULL,NULL)
    end sub

файл thread.bi

Код:

#define ClassName "Win32FreeBasicThreadClass" ' класс окна
#define AppName "Win32 FreeBasic MultiThreading Example" ' название программы
#define MyMenu "FirstMenu"
#define SuccessString "The calculation is completed!" ' эту строку покажем после завершения расчета
#define WM_FINISH WM_USER + &h100
#define IDM_CREATE_THREAD 32000
#define IDM_EXIT 32001

файл thread.rc

Код:

#include "thread.bi"
FirstMenu MENU
{
POPUP "&Thread"
{
MENUITEM "&Create thread",IDM_CREATE_THREAD
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
}

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

#define WM_FINISH WM_USER + &h100

Вам не обязательно добавлять к WM_USER 100h, но будет лучше сделать это. Сообщение WM_FINISH имеет значение только в пределах нашей программы. Когда основное окно получает WM_FINISH, она реагирует на это показом окна с сообщением о том, что подсчет закончен.

Вы можете создать несколько тредов, выбрав "Create Thread" несколько раз. В этом примере применяется односторонняя коммуникация, то есть только тред может уведомлять основное окно о чем-либо. Если вы хотите, чтоб основной тред слал команды рабочему, вы должны сделать следующее:

• добавить пункт меню "Kill Thread".
• добавить глобальную переменную, используемую в качестве флага. TRUE = остановить тред, FALSE = продолжить тред.
• Изменить ThreadProc так, чтобы та проверяла в цикле значение флага.

Когда пользователь выберет "Kill Thread", основная программа установит флаг в TRUE. Когда Threadproc видит, что значение флага равно TRUE, она выходит из цикла и возвращается, что заканчивает действие треда.

[C] Iczelion, пер. Aquila.

electrik

Сообщения : 391
Дата регистрации : 2008-09-02
Возраст : 36
Откуда : галактика Млечный путь, система Солнечная, планета Земля, страна россия, город Санкт Петербург

Посмотреть профиль

Вернуться к началу Перейти вниз

Предыдущая тема Следующая тема Вернуться к началу


 
Права доступа к этому форуму:
Вы не можете отвечать на сообщения