1
0
mirror of https://github.com/loginov-dmitry/multithread.git synced 2025-02-19 20:10:30 +02:00

мелкие исправления форматирования

This commit is contained in:
loginov-dmitry 2020-07-29 09:32:10 +03:00
parent 4e4ee65904
commit a82e6738f1

View File

@ -225,20 +225,20 @@ end.
Нажмите кнопку `btnRunInMainThread` для запуска функции `DoLongCalculations` из основного потока. Через несколько секунд появится сообщение с результатом вычислений. Длительность вычислений предсказать сложно, она зависит от характеристик процессора, а также от его текущего состояния. Приблизительное время – от 5 до 20 секунд. Обратите внимание, что после запуска вычислений вы не можете выполнять каких-либо действий с окном (нажимать кнопки, изменять размеры окна и т.д.) до момента окончания вычислений. Таким образом, запуск функции `DoLongCalculations` из основного приводит к подвисанию интерфейса пользователя.
А теперь нажмите кнопку `btnRunInParallelThread` для запуска функции `DoLongCalculations` из дополнительного потока. Вы увидите, что интерфейс пользователя не подвис, а продолжает реагировать на Ваши действия. Через несколько секунд появится сообщение с результатом вычислений. Таким образом, запуск функции `DoLongCalculations` из параллельного потока не приводит к подвисанию интерфейса пользователя.
А теперь нажмите кнопку `btnRunInParallelThread` для запуска функции `DoLongCalculations` из дополнительного потока. Вы увидите, что интерфейс пользователя не подвис, а продолжает реагировать на Ваши действия. Через несколько секунд появится сообщение с результатом вычислений. Таким образом, запуск функции `DoLongCalculations` из параллельного потока не приводит к подвисанию интерфейса пользователя.
Попробуйте нажать кнопку `btnRunInParallelThread` два раза друг за другом. Через несколько секунд (скорее всего дольше, чем до этого) появится два сообщения с результатами вычислений. Т.е. параллельно друг другу два дополнительных потока произведут вычисления, а затем выведут результат на экран.
Сложно предположить, сколько времени займут вычисления при выполнении двух параллельных потоков. Это зависит от того, сколько физических ядер у процессора, а также от того, на одном или на двух разных физических ядрах выполняются потоки. Если наши два потока будут выполняться на двух разных физических ядрах, то время их выполнения будет таким же, как время выполнения одного потока, который мы запускали это этого. Но если они будут выполняться на одном физическом ядре, то время их выполнения будет примерно в 2 или 3 раза больше (т.е. мы не получим реального распараллеливания вычислений, хотя интерфейс пользователя и не будет подвисать).
Сложно предположить, сколько времени займут вычисления при выполнении двух параллельных потоков. Это зависит от того, сколько **физических** ядер у процессора, а также от того, на одном или на двух разных физических ядрах выполняются потоки. Если наши два потока будут выполняться на двух разных физических ядрах, то время их выполнения будет таким же, как время выполнения одного потока, который мы запускали до этого. Но если они будут выполняться на одном физическом ядре, то время их выполнения будет примерно в 2 или 3 раза больше (т.е. мы не получим реального распараллеливания вычислений, хотя интерфейс пользователя и не будет подвисать).
Я не случайно выделил слово «физических», поскольку в современных процессорах есть понятие «логическое ядро» (можете почитать в Интернете про «Hyper-Threading»). Если оба потока будут работать на двух логических ядрах одного и того же физического ядра, то мы также не получим реального распараллеливания вычислений.
:warning: **Замечание по выдаче окна с сообщением из дополнительного потока**
В данном примере для выдачи пользователю сообщения из дополнительного потока используется функция MyShowMessage. В ней производится вызов стандартной WinAPI-функции MessageBox, которая отображает на экране окно с заданным текстовым сообщением. Хотя в Delphi и имеется похожая функция для вывода сообщений – ShowMessage, однако её нельзя вызывать из дополнительного потока, поскольку она не является «потокобезопасной», т.е. при обращении к ней из дополнительного потока могут происходить сбои в работе программы (может и не каждый раз).
В данном примере для выдачи пользователю сообщения из дополнительного потока используется функция `MyShowMessage`. В ней производится вызов стандартной WinAPI-функции `MessageBox`, которая отображает на экране окно с заданным текстовым сообщением. Хотя в Delphi и имеется похожая функция для вывода сообщений – `ShowMessage`, однако её нельзя вызывать из дополнительного потока, поскольку она не является «потокобезопасной», т.е. при обращении к ней из дополнительного потока могут происходить сбои в работе программы (может и не каждый раз).
Несмотря на то, что в данном примере код параллельного потока выдаёт пользователю окно с сообщением, я не рекомендую так делать в реальных программах. Важно помнить, что визуальное информирование пользователя должно выполняться из основного потока, тогда мы сможем избежать многих проблем.
:warning: **Замечания по использованию свойства TThread.FreeOnTerminate**
Обратите внимание, что в примере используется свойство FreeOnTerminate класса TThread. В начале метода TMyThread.Execute мы выставили ему значение True. Сделано этого для того, чтобы программа автоматически уничтожила объект TMyThread (т.е. вызвала деструктор объекта) сразу после завершения работы метода Execute. Поскольку мы не завели отдельной переменной для хранения ссылки на созданный объект TMyThread, то единственный способ уничтожить созданный объект – попросить его самостоятельно себя уничтожить сразу после завершения работы метода Execute. Для этого мы и воспользовались свойством FreeOnTerminate.
Обратите внимание, что в примере используется свойство `FreeOnTerminate` класса `TThread`. В начале метода `TMyThread.Execute` мы выставили ему значение True. Сделано этого для того, чтобы программа автоматически уничтожила объект `TMyThread` (т.е. вызвала деструктор объекта) сразу после завершения работы метода `Execute`. Поскольку мы не завели отдельной переменной для хранения ссылки на созданный объект `TMyThread`, то единственный способ уничтожить созданный объект – попросить его самостоятельно себя уничтожить сразу после завершения работы метода `Execute`. Для этого мы и воспользовались свойством `FreeOnTerminate`.
Это лишь один из способов управления временем жизни объекта потока. Другие способы будет представлены в следующих разделах.
:warning: **Запущенный поток не завершается при выходе из программы**
@ -248,17 +248,17 @@ end.
При программировании на языке Delphi программист должен самостоятельно думать о том, каким образом будет уничтожаться тот или иной созданный им объект.
При программировании дополнительных потоков мы должны определиться, каким образом будет завершаться работа метода Execute и каким образом будет уничтожаться созданный нами объект потока. При этом мы должны гарантированно завершить работу потока и уничтожить объект потока при выходе из программы. Если поток выполняет очень длительную важную задачу и его нельзя прерывать, то мы должны заблокировать возможность выхода из программы до момента завершения работы потока.
При программировании дополнительных потоков мы должны определиться, каким образом будет завершаться работа метода `Execute` и каким образом будет уничтожаться созданный нами объект потока. При этом мы должны гарантированно завершить работу потока и уничтожить объект потока при выходе из программы. Если поток выполняет очень длительную важную задачу и его нельзя прерывать, то мы должны заблокировать возможность выхода из программы до момента завершения работы потока.
В зависимости от задачи, которую решает дополнительный поток, код в методе Execute может выполняться однократно либо многократно.
В зависимости от задачи, которую решает дополнительный поток, код в методе `Execute` может выполняться однократно либо многократно.
Примеры однократного выполнения кода в методе Execute:
Примеры однократного выполнения кода в методе `Execute`:
1) Выполнение вычислений при нажатии пользователем кнопки. В этом случае вполне уместно при каждом нажатии пользователем кнопки создавать дополнительный поток и запускать его.
2) Выполнение запроса к базе данных и обработка данных при срабатывании таймера. Здесь следует пояснить, что таймер не должен срабатывать слишком часто, иначе расходы на запуск и уничтожение потока могут оказаться значительными. Также хочу отметить, что при срабатывании таймера не следует обращаться к базе данных из основного потока, лучше это делать из дополнительного потока, разумеется, в отдельном подключении.
3) Выполнение заданной функции или процедуры в контексте дополнительного потока с навешиванием модальной формы ожидания окончания выполнения заданной функции.
4) Можно придумать ещё сотню различных ситуаций, но такой необходимости у нас нет!
Многократное выполнение кода в методе Execute используется при периодическом выполнении какой-либо задачи, например:
Многократное выполнение кода в методе `Execute` используется при периодическом выполнении какой-либо задачи, например:
1) Раз в секунду выполняется запрос к БД и при наличии нового задания производится обмен с каким-либо устройством.
2) Раз в 15 секунд выполняется синхронизация данных с сервером.
3) 2 раза в секунду выполняется опрос состояния какого-либо устройства (например, фискального регистратора, топливно-раздаточной колонки, системы измерения уровня и т.д.).