Изменить:
Похоже, что многие детали реализации изменились со времен 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 позволит вам запускать задачи в потоке пула потоков, поэтому система может настроить количество одновременных потоков во время выполнения.
Edit:
При повторном чтении я понимаю, что вы не собираетесь использовать SendMessage()
, но PostMessage()
- ну, тогда некоторые из вышеперечисленных не применимы, но я оставлю это на месте. Однако в вашем вопросе есть еще несколько моментов, на которые я хочу ответить:
При одновременном запуске до 16 потоков (и большая часть обработки дочернего потока занимает от <1 секунды до ~ 10 секунд), будет ли Window Messages лучшим дизайном? </p>
Если вы публикуете сообщение из каждой темы один раз в секунду или даже более длительный период, тогда дизайн в порядке. Чего не следует делать, так это отправлять сотни и более сообщений в поток в секунду, поскольку очередь сообщений Windows имеет конечную длину и пользовательские сообщения не должны слишком сильно мешать нормальной обработке сообщений (ваша программа может перестать отвечать на запросы).
где дочерний поток публикует сообщение Windows (состоящее из записи из нескольких строк)
Сообщение в окне не может содержать запись. Он содержит два параметра, один типа WPARAM
, другой типа LPARAM
. Вы можете привести указатель на такую запись только к одному из этих типов, поэтому нужно как-то управлять временем жизни записи. Если вы распределяете его динамически, вам также необходимо его освободить, что может привести к ошибкам. Если вы передаете указатель на запись в стеке или в поле объекта, вам необходимо убедиться, что она остается действительной при обработке сообщения, что труднее для отправленных сообщений, чем для отправленных сообщений.
Вы предлагаете обернуть код, который я публикую в сетке, в блок TCriticalSection (войти и выйти)? Или мне не нужно беспокоиться о безопасности потоков, так как я пишу в сетку в основном потоке (хотя и в функции обработчика сообщений окна)?
Нет необходимости делать это, поскольку вызов PostMessage()
будет немедленно возвращен, поэтому в этом случае синхронизация не требуется . Вам определенно нужно будет беспокоиться о безопасности потоков, к сожалению, вы не можете знать , когда . Вы должны убедиться, что доступ к данным является потокобезопасным, с помощью всегда блокируя данные для доступа, используя объекты синхронизации. На самом деле не существует способа добиться этого для записей, к данным всегда можно получить прямой доступ.