Подтвердить что ты не робот

Лучше ли использовать TThread "Синхронизировать" или использовать "Сообщения окна" для IPC между основным и дочерним потоками?

У меня довольно простое многопоточное приложение VCL gui, написанное на Delphi 2007. Я выполняю некоторую обработку в нескольких дочерних потоках (до 16 одновременных), которые должны обновлять элемент управления сеткой в ​​моей основной форме (просто отправляя строки в сетка). Ни один из дочерних потоков никогда не разговаривает друг с другом.

Моя первоначальная конструкция включала вызов TThread "Synchronize" для обновления формы управления сеткой в ​​текущем потоке. Тем не менее, я понимаю, что вызов Synchronize выполняется по существу, как если бы он был основным при вызове. При одновременном выполнении до 16 потоков (и большая часть обработки дочерних потоков занимает от 1 секунды до ~ 10 секунд) будут ли окна-сообщения лучшими?

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

Любые мнения о наилучшем методе МПК в этой ситуации? Окно сообщений или "Синхронизировать"?

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

4b9b3361

Ответ 1

Edit:

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

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

Спасибо за исправление. Поэтому возьмите детали в исходном ответе с солью.

Но это не влияет на основные моменты. Я все еще утверждаю, что вся идея Synchronize() является смертельно ошибочной, и это будет очевидно в тот момент, когда вы пытаетесь удержать несколько ядер современной машины. Не синхронизируйте свои потоки, пусть они работают до тех пор, пока они не закончатся. Попытайтесь минимизировать все зависимости между ними. Особенно при обновлении GUI нет абсолютно никаких оснований ждать завершения этого. Использует ли 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 позволит вам запускать задачи в потоке пула потоков, поэтому система может настраивать количество параллельных потоков во время выполнения.

Edit:

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

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

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

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

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

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

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

Ответ 2

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

(Queue доступно с D8).

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

Ответ 3

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

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

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

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

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

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

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