Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
Форум программистов > Delphi: WinAPI и системное программирование > Пример простого многопоточного приложения. |
Автор: Петрович 17.7.2007, 15:07 | ||
Пример посторения простого многопоточного приложения. Рожден о причине большого числа вопросов о построении многопоточных приложений в Delphi. Цель данного примера - продемонстрировать как правильно строить многопоточное приложение, с выносом длительной работы в отдельный поток. И как в таком приложении обеспечить взаимодействие основного потока с рабчим для передачи данных из формы (визуальных компонентов) в поток и обратно. Пример не прретендует на полноту, он лишь демонстрирует наиболее простые способы взаимодействия потоков. Позволяя пользователю "быстренько слепить" (кто бы знал как я этого не люблю) правильно работающее многопоточное приложение. В нем все подробно (на мой взгляд) прокоментированно, но, если будут вопросы, задавайте. Но еще раз предостерегаю: Потоки - дело не простое. Если Вы не представляете как все это работает, то есть огромная опасность что часто у Вас все будет работать нормально, а иногда программа будет вести себя более чем странно. Поведение неправильно написанной многопотчной программы очень сильно зависит от большого кол-ва факторов, которые порою невозможно воспроизвести при отладке. Короче, читайте документацию, и статью http://forum.vingrad.ru/index.php?showtopic=60076&view=findpost&p=480303 ![]() Итак пример. Для удобства поместил и код, и прикрепил архив с кодом модуля и формы
А вообще, примеру предшествовали следующие мои рассуждения на тему.... Во первых: ВАЖНЕЙШЕЕ правило многопоточного программирования на Delphi: В контексте не основного потока нельзя, обращаться к свойствам и методам форм, да и вообще всех компонентов которые "растут" из tWinControl. Это означает (несколько упрощенно) что ни в методе Execute унаследованного от TThread, ни в других методах/процедурах/функциях вызываемых из Execute, нельзя напрямую обращаться ни к каким свойствам и методам визуальных компонентов. Как делать правильно. Тут единых рецептов нет. Точнее, вариантов так много и разных, что в зависимости от конкретного случая нужно выбирать. Поэтому к статье и отсылают. Прочитав и поняв ее, программист сможет понять и как лучше сделать в том или ином случае. Если коротенько на пальцах: Чаще всего, многопоточным приложение становится либо когда надо делать какую либо длительную работу, либо когда можно одновременно делать несколько дел, не сильно нагружающих процессор. В первом случае, реализация работы внутри основного потока приводит к «торможению» пользовательского интерфейса – пока делается работа, не выполняется цикл обработки сообщений. Как следствие – программа не реагирует на действия пользователя, и не прорисовывается форма, например после ее перемещения пользователем. Во втором случае, когда работа подразумевает активный обмен с внешним миром, то во время вынужденных «простоев». В ожидании получения/отправки данных, можно параллельно делать еще что-то, например, опять же другие посылать/принимать данные. Существуют и другие случаи, но реже. Впрочем, это и не важно. Сейчас не об этом. Теперь, как все это пишется. Естественно рассматривается некий наиболее частый случай, несколько обобщенный. Итак. Работа, выносимая в отдельный поток, в общем случае имеет четыре сущности (уж и не знаю как назвать точнее): 1. Исходные данные 2. Собственно сама работа (она может зависеть от исходных данных) 3. Промежуточные данные (например, информация о текущем состоянии выполнения работы) 4. Выходные данные (результат) Чаще всего для считывания и вывода большей части данных используются визуальные компоненты. Но, как было сказано выше – нельзя из потока напрямую обращаться к визуальным компонентам. Как же быть? Разработчики Delphi предлагают использовать метод Synchronize класса TThread. Здесь я не буду описывать то, как его применять – для этого есть вышеупомянутая статья. Скажу лишь, что его применение, даже правильное, не всегда оправдано. Имеются две проблемы: Во первых, тело метода вызванного через Synchronize всегда выполняется в контексте основного потока, и поэтому, пока оно выполняется, опять же не выполняется цикл обработки оконных сообщений. Следовательно, оно должно выполняться быстро, иначе, мы получим все те же проблемы что и при однопоточной реализации. В идеале, метод вызываемый через Synchronize вообще должен использоваться только для обращения к свойствам и методам визуальных объектов. Во вторых, выполнение метода через Synchronize, это «дорогое» удовольствие, вызванное необходимостью двух переключений между потоками. Причем, обе проблемы взаимосвязаны, и вызывают противоречие: с одной стороны, для решения первой, надо «размельчать» методы вызываемые через Synchronize, а с другой, их тогда чаще приходится вызывать, теряя драгоценный процессорный ресурс. Поэтому, как всегда, надо подходить разумно, и для разных случаев, использовать разные способы взаимодействия потока с внешним миром: Исходные данные Все данные которые передаются в поток, и не изменяются во время его работы, нужно передавать еще до его запуска, т.е. при создании потока. Для их использования в теле потока, нужно сделать их локальную копию (обычно в полях потомка TThread). Если есть исходные данные которые могут меняться во время работы потока, то доступ к таким данным нужно осуществлять либо через синхронизируемые методы (методы вызываемые через Synchronize), либо через поля объекта-потока (потомка TThread). Последнее требует определенной осторожности. Промежуточные и выходные данные Здесь, опять же есть несколько способов (в порядке моих предпочтений): - Метод асинхронной отсылки сообщений главному окну приложению. Используется обычно для отсылки основному окну приложения сообщений о состоянии протекания процесса, с передачей незначительного объема данных (например, процента выполнения) - Метод синхронной отсылки сообщений главному окну приложению. Используется обычно для тех же целей что и асинхронная отсылка, но позволяет передать больший объем данных, без создания отдельной копии. - Синхронизируемые методы, по возможности, объединяя в один метод передачу как можно большего объема данных. Можно использовать и для получения данных с формы. - Через поля объекта-потока, обеспечением взаимоисключающего доступа. Подробнее, можно почитать в статье. Эх. Коротенько опять не получилось |
Автор: MetalFan 17.7.2007, 15:46 |
отличный пример! все достаточно подробно расписано) буду отсылать новичков сюда есть конечно некоторые неточности... (см пост выше) я бы сказал так: ВАЖНЕЙШЕЕ правило многопоточного программирования: обеспечить недопустимость одновременного обращения к одной и той же памяти (на запись и чтение) из разных потоков. |
Автор: Петрович 18.7.2007, 10:58 | ||||
Нет не означает. Если подробнее то так: 1. tWinControl изначально написан так что он не реентерабелен. А это значит, что его и его наследников нельзя считать потокобезопасными классами. Вообще то, отчасти, это особенность Windows GUI ![]() 2. tControl - реентерабельный класс, т.е. он потокобезопасен. Однако, классы которые его наследуют не обязательно являются потокобезопасными. Тут все зависит от того как написаны классы наследники. Например, есть в природе базовый клас "птицы", объектам которого вроде как положено летать. Однако, некоторые объекты наследники класса "птицы", такие как например "курица", летать не умеют. ![]()
В принципе правильно, но для "непосвещенных" не очевидна связь этого правила с запретом использования визуальных компонент в контексте потока. Для них даже само понятие "контекст потока" и то не понятно. Кроме того, помимо "одновременного обращения к одной и той же памяти", есть еще много разных "недопустимостей"..... Кстати, в этом примере, я как раз даже не затронул вопрос защиты доступа к разделяемой между потоками памяти. Сначала вроде хотел, а потом решил что пример и так уже "здоровенный" получился. А если в него еще что-то добавить, то он сильно потеряет в прозрачности. Просто, это была попытка помочь новичкам в решении лишь одной простейшей проблемы: обращение к методам и свойствам визуальных компонент из неосновного потока. Практика показывает что это первая проблема с которой сталкиваются программисты при написании своего первого многопоточного приложения на Delphi. Уж слишком серьезная тема, много разных важных аспектов..... Лучше конечно если программист прочитает указанную выше статью. Там конечно затронуто гораздо больше аспектов, нюансов, и пр. |
Автор: Alexeis 18.7.2007, 13:12 | ||
Вот это то как раз и плохо. Т.е. можно быть уверенным что все будет в порядке только при обращении к своим классам, записям, массивам и атомарным типам. Классы VCL и другие чужие классы всегда потенциально опасны с этой точки зрения. |
Автор: firedanger 18.10.2014, 18:25 |
Уважаемый коллега! Не подскажите о тонкостях организации многопоточного консольного приложения в Delphi. Необходимо организовать параллельную работу многопоточного приложения на многоядерном процессоре (для конкретики 4 ядра) в режиме распараллеливания по данным (SPMD-модель). Алгоритм в кратце таков: корневой процесс считывает данные из файла, рассылает их по потокам. Затем каждый поток обрабатывает свою порцию данных. После чего корневой процесс собирает данные и выводит их в файл результатов. P.S. Был опыт параллельного программирования на вычислительном кластере параллельной архитектуры в системе MPI (Message Passing Interface). Но это было давно. Заранее спасибо. |