Лучше использовать TThread's, “Синхронизируют” или используют сообщения Окна для IPC между основным и дочерним потоком?

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

21
задан Mick 27 November 2009 в 01:03
поделиться

3 ответа

Редактировать:

Похоже, что многие детали реализации изменились с Delphi 4 и 5 (версии Delphi, которые я все еще использую для большей части своей работы), и у Аллена Бауэра есть прокомментировал следующее:

Начиная с D6, TThread больше не использует SendMessage. Он использует потокобезопасную рабочую очередь, в которую помещается «работа», предназначенная для основного потока. Сообщение отправляется в основной поток, чтобы указать, что работа доступна, и фоновый поток блокируется при событии. Когда основной цикл сообщений собирается перейти в режим ожидания, он вызывает «CheckSynchronize», чтобы узнать, ожидает ли какая-либо работа. Если да, он его обрабатывает. После того, как рабочий элемент завершен, событие, при котором фоновый поток блокируется, устанавливается для указания завершения. На таймфрейме D2006 был добавлен метод TThread.Queue, который не блокируется.

Спасибо за исправление. Так что относитесь к деталям исходного ответа с недоверием.

Но на самом деле это не влияет на основные моменты. Я все еще утверждаю, что сама идея Synchronize () фатально ошибочна, и это станет очевидным в тот момент, когда кто-то попытается сохранить занятыми несколько ядер современной машины. Не «синхронизируйте» свои потоки, позвольте им работать, пока они не закончатся. Постарайтесь свести к минимуму все зависимости между ними. Особенно при обновлении графического интерфейса нет абсолютно причин ждать его завершения. Независимо от того, использует ли Synchronize () SendMessage () или PostMessage () , в результате получается одно и то же.


То, что вы здесь представляете, не является альтернативой вообще, поскольку Synchronize () использует SendMessage () внутри. Так что это скорее вопрос, какое оружие вы хотите использовать, чтобы выстрелить себе в ногу.

Synchronize () был с нами с момента появления TThread в Delphi 2 VCL, что на самом деле является позором, поскольку это одна из самых серьезных ошибок в конструкции VCL.

Как это работает? Он использует вызов SendMessage () окна, которое было создано в основном потоке, и устанавливает параметры сообщения для передачи адреса вызываемого метода объекта без параметров. Поскольку сообщения Windows будут обрабатываться только в потоке, который создал окно назначения и запустил его цикл обработки сообщений, это приостановит поток, обработает сообщение в контексте основного потока VCL, вызовет метод, и возобновить поток только после того, как метод завершит выполнение.

Так что же с ним не так (и что аналогичного плохого в использовании SendMessage () напрямую)? Несколько вещей:

  • Принуждение любого потока выполнять код в контексте другого потока приводит к двум переключениям контекста потока, что без нужды сжигает циклы ЦП.
  • Пока поток VCL обрабатывает сообщение для вызова синхронизированного метода, он не может обработать любое другое сообщение.
  • Когда более одного потока используют этот метод, они будут все блокировать и ждать возврата Synchronize () или SendMessage () . Это создает гигантское узкое место.
  • Тупиковая ситуация ждет своего часа. Если поток вызывает Synchronize () или SendMessage () , удерживая объект синхронизации, и поток VCL при обработке сообщения должен получить тот же объект синхронизации, который приложение заблокирует.
  • То же самое можно сказать о вызовах API, ожидающих дескриптора потока - с использованием WaitForSingleObject () или WaitForMultipleObjects () без каких-либо средств для обработки сообщений вызовет тупик, если потоку нужны эти способы для «синхронизации» с другим потоком.

Итак, что использовать вместо этого? Несколько вариантов, я опишу некоторые:

  • Используйте PostMessage () вместо SendMessage () (или PostThreadMessage () , если оба потока являются обоими не поток VCL). Однако важно не использовать в параметрах сообщения какие-либо данные, которые больше не будут действительны, когда сообщение поступит, поскольку отправляющий и принимающий потоки вообще не синхронизированы, поэтому необходимо использовать некоторые другие средства, чтобы убедиться, что любая строка, ссылка на объект или фрагмент памяти все еще действительны при обработке сообщения, даже если отправляющий поток может даже не

  • Создавайте потокобезопасные структуры данных, помещайте в них данные из ваших рабочих потоков и потребляйте их из основного потока. Используйте PostMessage () только для предупреждения потока VCL о том, что поступили новые данные для обработки, но не отправляйте сообщения каждый раз. Если у вас есть непрерывный поток данных, вы даже можете провести опрос потока VCL на предмет данных (возможно, используя таймер), но это только версия для бедных.

  • Не используйте инструменты низкого уровня вообще, любые Больше. Если вы хотя бы используете Delphi 2007, загрузите OmniThreadLibrary и начните думать в терминах задач, а не потоков. Эта библиотека имеет множество возможностей для обмена данными между потоками и синхронизации. Он также имеет реализацию пула потоков, что хорошо - количество потоков, которые вы должны использовать, зависит не только от приложения, но и от оборудования, на котором оно работает, поэтому многие решения можно принимать только во время выполнения. OTL позволит вам запускать задачи в потоке пула потоков, поэтому система может настроить количество параллельных потоков во время выполнения.

Изменить:

При повторном чтении я понимаю, что вы не собираетесь использовать SendMessage () , но PostMessage () - ну, в таком случае некоторые из вышеперечисленных неприменимы, но я оставлю это на месте. Однако в вашем вопросе есть еще несколько моментов, на которых я хочу остановиться:

При одновременном запуске до 16 потоков (и большая часть обработки дочернего потока занимает от <1 секунды до ~ 10 секунд) будут ли сообщения окна лучше?

Если вы публикуете сообщение из каждого потока один раз в второй или даже более длительный период, тогда дизайн в порядке. Чего вам не следует делать, так это публиковать сотни или более сообщений в потоке в секунду, потому что очередь сообщений Windows имеет конечную длину, а пользовательские сообщения не должны слишком сильно мешать нормальной обработке сообщений (ваша программа может перестать отвечать на запросы).

где дочерний поток отправляет сообщение Windows (состоящее из записи из нескольких строк)

Сообщение окна не может содержать запись. Он несет два параметра: один типа WPARAM , другой - типа LPARAM . Вы можете привести указатель на такую ​​запись только к одному из этих типов, поэтому время жизни записи нужно как-то управлять. Если вы распределяете его динамически, вам также необходимо его освободить, что может привести к ошибкам. Если вы передаете указатель на запись в стеке или в поле объекта, вам необходимо убедиться, что он все еще действителен при обработке сообщения, что сложнее для опубликованных сообщений, чем для отправленных сообщений.

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

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

36
ответ дан 29 November 2019 в 20:03
поделиться

Кстати, вы также можете использовать TThread.Queue () вместо TThread.Synchronize () . Queue () - это асинхронная версия, она не блокирует вызывающий поток:

( Очередь доступна с D8).

Я предпочитаю Synchronize () или Queue () , потому что это намного проще для понимания (для других программистов) и лучше OO, чем простая отправка сообщений (без контроля и возможности отладки!)

9
ответ дан 29 November 2019 в 20:03
поделиться

Хотя я уверен, что есть правильный и неправильный путь. Я написал код, использующий оба метода, и тот, к которому я продолжаю возвращаться, - это метод SendMessage, и я не уверен, почему.

Использование SendMessage и Synchronize на самом деле не имеет никакого значения. Оба работают по сути одинаково. Я думаю, что причина, по которой я продолжаю использовать SendMessage, заключается в том, что я чувствую больший контроль, но я не знаю.

Подпрограмма SendMessage заставляет вызывающий поток приостанавливаться и ждать, пока окно назначения не закончит обработку отправленного сообщения. Из-за этого основной поток приложения по существу синхронизируется с вызывающим дочерним потоком на время вызова. Вам не нужно использовать критический раздел в обработчике сообщений Windows.

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

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

Для простых вещей вы можете определить одно сообщение (wm_threadmsg1) и использовать поля wparam и lparam для передачи (целочисленных) сообщений о состоянии назад и вперед. Для более сложных примеров вы можете передать строку, передав ее через lparam и вернув ее тип в longint. A-la longint (pchar (myvar)) или используйте pwidechar, если вы используете D2009 или новее.

Если вы уже заставили его работать с методами Synchronize, я бы не стал беспокоиться о его переработке, чтобы внести изменения.

Для более сложных примеров вы можете передать строку, передав ее через lparam и вернув ее тип в longint. A-la longint (pchar (myvar)) или используйте pwidechar, если вы используете D2009 или новее.

Если вы уже заставили его работать с методами Synchronize, я бы не стал беспокоиться о его переработке, чтобы внести изменения.

Для более сложных примеров вы можете передать строку, передав ее через lparam и вернув ее тип в longint. A-la longint (pchar (myvar)) или используйте pwidechar, если вы используете D2009 или новее.

Если вы уже заставили его работать с методами Synchronize, я бы не стал беспокоиться о его переработке, чтобы внести изменения.

5
ответ дан 29 November 2019 в 20:03
поделиться
Другие вопросы по тегам:

Похожие вопросы: