Совет по преобразованию большого монолитного однопоточного приложения в многопоточную архитектуру?


Основным продуктом моей компании является большое монолитное приложение C++, используемое для обработки и визуализации научных данных. Его кодовая база насчитывает, возможно, 12 или 13 лет, и хотя мы провели работу по ее обновлению и поддержке (использование STL и Boost - когда я присоединился к большинству контейнеров, например, были полностью обновлены до Unicode и 2010 VCL и т. д.), остается одна очень существенная проблема: он полностью однопоточный. Учитывая, что это программа обработки и визуализации данных, это становится все большим и большим препятствием.

Я одновременно Разработчик и менеджер проекта для следующего выпуска, где мы хотим решить эту проблему, и это будет трудная работа в обеих областях. Я ищу конкретные, практические и архитектурные советы о том, как решить эту проблему.

Поток данных программы может выглядеть примерно так:

  • окно должно отображать данные
  • в методе paint он вызовет GetData метод, часто сотни раз для сотен битов данных в одной операции рисования
  • Это будет идти и вычислять или читать из файла или что-то еще требуется (часто довольно сложный поток данных-думайте об этом как о данных, проходящих через сложный граф, каждый узел которого выполняет операции)

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

Я ищу совета, как подойти к изменению этого. Практическая идея. Возможно, такие вещи, как:

  • шаблоны проектирования для асинхронного запроса данных?
  • хранение больших коллекций объектов, чтобы потоки могли безопасно читать и писать?
  • обработка недействительности наборов данных, когда что-то пытаешься прочесть его?
  • Существуют ли паттерны и методы для решения такого рода проблем?
  • О чем я должен спрашивать, о чем не подумал?

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

Конечная цель состоит в том, чтобы иметь полностью отзывчивую программу, где все вычисления и генерация данных выполняются в других потоках, и пользовательский интерфейс всегда реагирует. Возможно, мы не доберемся туда за один цикл разработки:)


Edit: я подумал, что должен добавить еще пару деталей о приложении:

  • это 32-разрядное настольное приложение для Windows. Каждая копия имеет лицензию. Мы планируем сохранить его настольным, локально работающим приложением
  • Мы используем Embarcadero (ранее Borland) C++ Builder 2010 для разработки. Это влияет на параллельные библиотеки мы можем использовать, так как большинство из них кажутся (?) должны быть написаны только для GCC или MSVC. К счастью, они активно развивают его, и его поддержка стандартов C++ намного лучше, чем раньше. Компилятор поддерживает эти компоненты Boost.
  • Его архитектура не так чиста, как должна быть, и компоненты часто слишком тесно связаны. Это еще одна проблема :)

Edit #2: Спасибо за ответы до сих пор!

  • я удивлен, что так много люди рекомендовали многопроцессорную архитектуру (это самый популярный ответ на данный момент), а не многопоточность. У меня сложилось впечатление, что это очень Unix-иш программная структура, и я ничего не знаю о том, как она разработана или работает. Есть ли хорошие ресурсы, доступные об этом, в Windows? Это действительно так часто встречается в Windows?
  • С точки зрения конкретных подходов к некоторым предложениям многопоточности, существуют ли шаблоны проектирования для асинхронного запроса и потребления данных, или threadaware или асинхронные системы MVP, или как спроектировать систему, ориентированную на решение задач, или статьи и книги, а также деконструкции после выпуска, иллюстрирующие то, что работает, и то, что не работает? Конечно, мы можем разработать всю эту архитектуру сами, но полезно работать с тем, что другие делали раньше, и знать, каких ошибок и ловушек следует избегать.
  • Один аспект, который не затрагивается ни в одном ответе, - это управление проектом. Мое впечатление-это оценка того, сколько времени это займет. и сохранять хороший контроль над проектом, когда делаешь что-то настолько неопределенное, как это, может быть трудно. Это одна из причин, по которой я ищу рецепты или практические советы по кодированию, я думаю, чтобы направлять и ограничивать направление кодирования, насколько это возможно.

Я еще не отметил ответ на этот вопрос - это не из-за качества ответов, что замечательно (и спасибо), а просто потому, что из-за объема этого я надеюсь на дополнительные ответы или обсуждение. Спасибо тем, у кого есть уже ответил!

15 31

15 ответов:

Итак, в вашем описании алгоритма есть подсказка о том, как действовать:

Часто довольно сложный поток данных-думайте об этом как о потоке данных через сложный граф, каждый узел которого выполняет операции

Я бы хотел, чтобы этот график потока данных был буквально структурой, которая выполняет работу. Ссылки в графе могут быть потокобезопасными очередями, алгоритмы в каждом узле могут оставаться практически неизменными, за исключением завернутых в поток, который берет работу на себя элементы из очереди и результаты депозитов на одном. Вы можете пойти еще дальше и использовать сокеты и процессы, а не очереди и потоки; это позволит вам распределиться по нескольким машинам, если это позволит повысить производительность.

Тогда ваш paint и другие методы GUI нужно разделить на две части: одна половина для очереди работы, а другая половина для рисования или использования результатов, когда они выходят из конвейера.

Это может быть непрактично, если приложение предполагает, что данные являются глобальными. Но если он хорошо содержится в классах, как это предполагает ваше описание, то это может быть самым простым способом его распараллеливания.

Перед вами стоит большая задача. Передо мной стояла аналогичная задача-15-летняя монолитная однопоточная кодовая база, не использующая преимущества многоядерности и т. д. Мы потратили много усилий, пытаясь найти дизайн и решение, которое было бы работоспособным и работало бы.

Сначала плохие новости. Это будет что-то среднее между непрактичным и невозможным, чтобы сделать ваше однопоточное приложение многопоточным. Однопоточное приложение полагается на его однопоточность-это способы, как тонкие и отвратительно. Один пример - если вычислительная часть требует ввода от части графического интерфейса. Графический интерфейс должен работать в основном потоке. Если вы попытаетесь получить эти данные непосредственно из вычислительного механизма, вы, вероятно, столкнетесь с тупиковыми и гоночными условиями, которые потребуют серьезных изменений для исправления. Многие из этих зависимостей не появятся на этапе проектирования или даже на этапе разработки, но только после того, как сборка выпуска будет помещена в жесткие условия.

Еще одна плохая новость. Программирование многопоточных приложений исключительно сложно. Это может показаться довольно простым, чтобы просто заблокировать вещи и делать то, что вы должны сделать, но это не так. Прежде всего, если вы блокируете все в поле зрения, вы в конечном итоге сериализуете свое приложение, сводя на нет все преимущества многопоточности, в то же время добавляя всю сложность. Даже если вы выходите за рамки этого, написание бездефектного MP-приложения достаточно сложно, но написание высокопроизводительного MP-приложения-это гораздо больше трудный. Вы могли бы учиться на работе в своего рода крещении огнем. Но если вы делаете это с производственным кодом, особенноунаследованным производственным кодом, вы подвергаете риску свой бизнес.

Теперь хорошие новости. У вас есть варианты, которые не включают в себя рефакторинг всего приложения и дадут вам большую часть того, что вы ищете. Один из вариантов, в частности, прост в реализации (в относительном выражении) и гораздо менее подвержен дефектам, чем создание вашего приложения полностью MP.

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

Это может показаться халтурой. А может, и так. Но это даст вам то, что вам нужно, не ставя стабильность и производительность ваша система в таком большом риске. Плюс есть скрытые преимущества. Один из них заключается в том, что невидимые копии движка вашего приложения будут иметь доступ к своему собственному виртуальному пространству памяти, что облегчит использование всех ресурсов системы. Он также хорошо масштабируется. Если вы работаете на 2-ядерном блоке, вы можете выпустить 2 копии вашего двигателя. 32 ядра? 32 экземпляра. Вы поняли идею.

  1. Не пытайтесь сделать многопоточным все, что есть в старом приложении. Многопоточность ради того, чтобы сказать, что она многопоточна, - это пустая трата времени и денег. Вы создаете приложение, которое что-то делает, а не памятник самому себе.
  2. профилируйте и изучайте свои потоки выполнения, чтобы выяснить, где приложение проводит большую часть своего времени. Профилировщик-отличный инструмент для этого, но так же просто шагать через код в отладчике. Вы находите самые интересные вещи в случайном порядке прогулки.
  3. Отделите пользовательский интерфейс от длительных вычислений. Используйте методы межпотоковой связи для отправки обновлений в пользовательский интерфейс из вычислительного потока.
  4. как побочный эффект #3: Подумайте хорошенько о повторном запуске: теперь, когда вычисление выполняется в фоновом режиме и пользователь может смурфовать в пользовательском интерфейсе, какие вещи в пользовательском интерфейсе должны быть отключены, чтобы предотвратить конфликты с фоновой операцией? Позволяет пользователю удалять набор данных во время выполнения вычислений на нем данные, вероятно, плохая идея. (Смягчение: вычисление создает локальный моментальный снимок данных) имеет ли смысл для пользователя выполнять несколько вычислительных операций одновременно? При правильном обращении это может стать новой функцией и помочь рационализировать усилия по переработке приложения. Если его игнорировать, это будет катастрофа.
  5. определите конкретные операции, которые являются кандидатами для включения в фоновый поток. Идеальным кандидатом обычно является одна функция или класс, который выполняет много работы (требует "много времени" для завершения-более нескольких секунд) с четко определенными входами и выходами, что не использует никаких глобальных ресурсов и не касается непосредственно пользовательского интерфейса. Оценивайте и устанавливайте приоритеты кандидатов, исходя из того, сколько работы потребуется для приведения их в соответствие с этим идеалом.
  6. Что касается управления проектами, делайте все шаг за шагом. Если у вас есть несколько операций, которые являются сильными кандидатами для перемещения в фоновый поток, и они не взаимодействуют друг с другом, они могут быть реализовываться параллельно несколькими разработчиками. Тем не менее, было бы неплохо, если бы все сначала приняли участие в одном преобразовании, чтобы все поняли, что искать, и установили свои шаблоны для взаимодействия с пользовательским интерфейсом и т. д. Проведите расширенную встречу на доске, чтобы обсудить дизайн и процесс извлечения одной функции в фоновый поток. Идите реализуйте это (вместе или раздайте части отдельным лицам), а затем снова соберитесь, чтобы собрать все вместе и обсудить открытия и болевые точки.
  7. Многопоточность-это головная боль и требует более тщательного обдумывания, чем прямое кодирование, но разделение приложения на несколько процессов создает гораздо больше головной боли, ИМО. Поддержка потоков и доступные примитивы хороши в Windows, возможно, лучше, чем некоторые другие платформы. Использовать их. В общем, не делайте больше того, что необходимо. Легко серьезно переосмыслить и усложнить проблему, бросив больше шаблонов и стандартных библиотек на него.
  8. Если никто из вашей команды раньше не выполнял многопоточную работу, выделите время, чтобы сделать эксперта, или средства, чтобы нанять его в качестве консультанта.

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

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

Всякий раз, когда вы получаете сообщение WM_PAINT, вы должны получить самую последнюю полную версию этой структуры и извлечь из нее. Если вы делаете это правильно, вы должны быть в состоянии обрабатывать несколько сообщений WM_PAINT в секунду, потому что код paint никогда не ссылается на ваши внутренние данные вообще. Он просто вращается через приготовленную структуру. Идея здесь заключается в том, что лучше быстро рисовать устаревшие данные, чем вешать пользовательский интерфейс.

Тем временем...

У вас должно быть 2 полных экземпляра этой структуры, приготовленной для показа. Один из них-это то, на что смотрит сообщение WM_PAINT. (назовите его cfd_A ) другой-это то, что вы передаете своей функции CookDataForDisplay (). (назовем его cfd_B ). Ваша функция CookDataForDisplay () выполняется в отдельном потоке и работает над созданием/обновлением cfd_B в фоновом режиме. Эта функция может занимать столько времени, сколько она хочет, потому что она не взаимодействует с дисплеем в любой способ. После того, как вызов возвращает cfd_B будет самой последней версией структуры.

Поменяйтесь cfd_A и cfd_B и функцию invalidaterect на вашем окне приложения.

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

Итак, ссылаясь вернемся к вашему примеру.

  • в методе paint он вызовет метод GetData, часто сотни раз для сотен битов данных в одной операции paint

Теперь это 2 потока, метод paint ссылается на cfd_A и работает в потоке пользовательского интерфейса. Между тем cfd_B строится фоновым потоком с использованием вызовов GetData.

Самый быстрый и грязный способ сделать это -

  1. возьмите текущий код WM_PAINT и вставьте его в функцию с именем PaintIntoBitmap ().
  2. создайте растровое изображение и память DC, это cfd_B.
  3. создайте поток, передайте ему cfd_B и вызовите PaintIntoBitmap ()
  4. когда этот поток завершится, замените cfd_B и cfd_A

Теперь ваш новый метод WM_PAINT просто берет предварительно отрисованное растровое изображение в cfd_A и рисует его на экране. Теперь ваш пользовательский интерфейс отключен от функции backend GetData ().

Теперь начинается настоящая работа, потому что быстрый и грязный способ не справляется изменение размера окна очень хорошо. Вы можете перейти оттуда, чтобы уточнить, что ваши cfd_A и cfd_B структуры немного в то время, пока вы не достигнете точки, где вы удовлетворены результатом.

Вы можете просто начать разбивать пользовательский интерфейс и рабочую задачу на отдельные потоки.

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

Пока все это происходит, у вас, конечно, есть индикатор выполнения говорит о сетчатых сплайнах, чтобы пользователь знал, что что-то происходит.

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

Похоже, что у вас есть несколько различных проблем, которые параллелизм может решить, но по-разному.

Производительность повышается за счет использования многоядерных процессорных архитектур

Вы не используете преимущества многоядерных процессорных архитектур, которые становятся столь распространенными. Распараллеливание позволяет разделить работу между несколькими ядрами. Вы можете написать этот код с помощью стандартных методов c++ divide and conquer, используя "функциональный" стиль программирования, в котором вы передайте работу отдельным потокам на этапе разделения. Паттерн Google MapReduce является примером этой техники. Intel имеет новую библиотекуCILK , чтобы предоставить вам поддержку компилятора C++ для таких методов.

Повышение быстродействия графического интерфейса за счет асинхронного представления документов

Отделяя операции графического интерфейса от операций с документами и помещая их в различные потоки, вы можете увеличить кажущуюся скорость отклика вашего приложения. Стандарт Модель-представление-контроллер или модель-представление-презентатор шаблоны проектирования являются хорошим местом для начала. Вам нужно распараллелить их, заставив модель информировать представление об обновлениях, а не предоставлять представление потоку, на котором вычисляется сам документ. Представление вызовет метод модели с просьбой вычислить определенное представление данных, и модель сообщит докладчику / контроллеру, когда информация будет изменена или появятся новые данные, которые будут переданы представлению для обновления. сам.

Оппортунистическое кэширование и предварительный расчет Это звучит так, как будто ваше приложение имеет фиксированную базу данных,но многие возможные представления данных с интенсивными вычислениями. Если вы провели статистический анализ того, какие представления чаще всего запрашиваются в каких ситуациях, вы можете создать фоновые рабочие потоки для предварительного расчета вероятных запрашиваемых значений. Может оказаться полезным поместить эти операции в низкоприоритетные потоки, чтобы они не мешали работе основного приложения. обработка.

Очевидно, что для реализации этого вам придется использовать мьютексы (или критические секции), события и, возможно, семафоры. Некоторые из новых объектов синхронизации в Vista могут оказаться полезными, например блокировка slim reader-writer, переменные условий или новый API пула потоков. Смотрите книгу Джо Даффи о параллелизме , чтобы узнать, как использовать эти основные методы.

Есть кое-что, о чем еще никто не говорил, но что весьма интересно.

Это называется futureС. Будущее-это обещание результата. .. давайте посмотрим на примере.

future<int> leftVal = computeLeftValue(treeNode); // [1]

int rightVal = computeRightValue(treeNode); // [2]

result = leftVal + rightVal; // [3]

Это довольно просто:

  1. Вы откручиваете поток, который начинает вычислять leftVal, беря его из пула, например, чтобы избежать проблемы инициализации.

  2. В то время как leftVal вычисляется, вы вычисляете rightVal.

  3. Вы добавляете два, это может блокировать если leftVal еще не вычислено, дождитесь окончания вычисления.

Большое преимущество здесь в том, что это просто: каждый раз, когда у вас есть одно вычисление, за которым следует другое, независимое, и вы затем соединяете результат, вы можете использовать этот шаблон.

Смотрите статью Херба Саттера О futures, они будут доступны в предстоящем C++0x, но уже есть библиотеки, доступные сегодня, даже если синтаксис, возможно, не так хорош, как я бы заставил вас поверить ;)

Если бы я тратил свои доллары на разработку, я бы начал с общей картины:

  1. Что я надеюсь сделать, и сколько я потрачу на это, и как я буду дальше продвигаться вперед? (Если ответ на этот вопрос таков, что мое приложение будет работать на 10% лучше на компьютерах quadcore, и я мог бы достичь того же результата , потратив на 1000 долларов больше на ПК клиента и потратив на 100 000 долларов меньше в этом году на исследования и разработки, то я бы пропустил все усилия).

  2. Зачем я это делаю многопоточность вместо массового параллельного распределения? Действительно ли я считаю, что потоки лучше процессов? Многоядерные системы также довольно хорошо управляют распределенными приложениями. И есть некоторые преимущества в системах, основанных на передаче сообщений, которые выходят за рамки преимуществ (и затрат!) резьбы. Следует ли мне рассмотреть процессный подход? Должен ли я рассматривать фон, работающий полностью как сервис, и графический интерфейс переднего плана? Поскольку мой продукт заблокирован узлом и лицензирован, я думаю, что сервисы меня бы вполне устроил (продавец). Кроме того, разделение материала на два процесса (фоновый сервис и передний план) просто может заставить вид перезаписи и переархивирования произойти, что я не мог бы быть вынужден делать, если бы я просто добавил поток в мой микс.

  3. Это просто для того, чтобы вы подумали: что, если бы вы переписали его как сервис (фоновое приложение) и графический интерфейс, потому что это было бы проще, чем добавить потоковую обработку, не добавляя также сбои, тупики и гонки условия?

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

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

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

Интересно, похожи ли проблемы с "дизайном", которые есть у этого приложения C++Builder, на мою болезнь приложений Delphi" RAD Spaghetti". Я обнаружил, что оптовый рефактор / перезапись (более года на каждое крупное приложение, с которым я это сделал), было минимальным количеством времени для меня, чтобы справиться с приложением "случайной сложности". И это без перебрасывания" нитей там, где это возможно " идеи. Я обычно пишу свои приложения только с потоками для последовательной связи и обработки сетевых сокетов. И, возможно, странная "рабочий поток-очередь".

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

Уоррен

Вот что я бы сделал...

Я бы начал с профилирования вашего и увидел:

1) Что такое медленный и что такое горячие пути 2), который вызывает повторные или глубоко вложенных

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

Вы можете использовать 2), чтобы узнать, где может находиться общее состояние, и получить более глубокое представление о том, насколько все запутано.

Я бы использовал хороший системный профилировщик и хороший профилировщик выборки (например, инструментарий Windows perforamnce toolkit или представления параллелизма профилировщика в Visual Studio 2010 Beta2 - они оба "свободны" прямо сейчас).

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

Если вы у вас нет такого хорошего инструмента рефакторинга, как VisualAssist, инвестируйте в него-оно того стоит. Если вы не знакомы с книгами Майкла пера или Кента Бека по рефакторингу, рассмотрите возможность их заимствования. Я бы гарантировал, что мои рефакторинги хорошо охвачены модульными тестами.

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

В boost я бы внимательно посмотрел на boost:: thread, asio библиотека и библиотека сигналов.

Я бы попросил помощи / руководства / слушающего уха, когда я застрял.

- Рик

Ну, я думаю, что вы ожидаете многого, основываясь на ваших комментариях здесь. Вы не собираетесь переходить от минут к миллисекундам с помощью многопоточности. Самое большее, на что вы можете надеяться, - это текущее количество времени, деленное на количество ядер. Тем не менее, вам немного повезло с C++. Я написал высокопроизводительные многопроцессорные научные приложения, и то, что вы хотите искать, - это самый смущающе параллельный цикл, который вы можете найти. В моем научном кодексе самое тяжелое-это вычисление. где-то между 100 и 1000 точками данных. Однако все точки данных могут быть вычислены независимо от других. Затем можно разделить цикл с помощью openmp. Это самый простой и эффективный путь. Если ваш компилятор не поддерживает openmp, то вам будет очень трудно Перенести существующий код. С openmp (если Вам повезет), вам, возможно, придется добавить только пару #прагм, чтобы получить 4-8x производительность. Вот пример StochFit

Я надеюсь, что это поможет вам понять и легко преобразовать ваше монолитное однопоточное приложение в многопоточное. Извините, что это для другого языка программирования, но тем не менее принципы, объясненные везде одинаковы.

Http://www.freevbcode.com/ShowCode.Asp?ID=1287

Надеюсь, это поможет.

Первое, что вы должны сделать, это отделить свой графический интерфейс от ваших данных, второе-создать многопоточный класс.

Шаг 1-отзывчивый графический интерфейс

Мы можем предположить, что изображение, которое вы создаете, содержится на холсте Таймажа. Вы можете поместить простой TTimer в свою форму и написать код следующим образом:
if (CurrenData.LastUpdate>CurrentUpdate)
    {
    Image1->Canvas->Draw(0,0,CurrenData.Bitmap);
    CurrentUpdate=Now();
    }

Хорошо! Я знаю! Немного грязновато, но быстро и просто.Суть в том, что:

  1. Вам нужен объект, созданный в основная нить
  2. объект копируется в нужном Вам виде, только когда это необходимо и безопасным способом (ОК, возможно, требуется лучшая защита для растрового изображения, но для семпличности...)
  3. объект CurrentData-это ваш фактический проект, однопоточный, который создает образ

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

Шаг 2 - Многопоточность

Я предлагаю вам реализовать класс, подобный следующему:

SimpleThread.h

typedef void (__closure *TThreadFunction)(void* Data);

class TSimpleThread : public TThread
{
public:
    TSimpleThread( TThreadFunction _Action,void* _Data = NULL, bool RunNow = true );
    void AbortThread();

    __property Terminated; 

protected:
    TThreadFunction ThreadFunction;
    void*           Data;

private:
    virtual void __fastcall Execute() { ThreadFunction(Data); };
};

SimpleThread.c

TSimpleThread::TSimpleThread( TThreadFunction _Action,void* _Data, bool RunNow)
             : TThread(true), // initialize suspended
               ThreadFunction(_Action), Data(_Data)
{
FreeOnTerminate = false;
if (RunNow) Resume();
}

void TSimpleThread::AbortThread()
{
Suspend(); // Can't kill a running thread
Free();    // Kills thread
}

Давайте объясним. Теперь в вашем простом потоковом классе вы можете создать такой объект:

TSimpleThread *ST;
ST=new TSimpleThread( RefreshFunction,NULL,true);
ST->Resume();
Давайте лучше объясним: теперь, в вашем собственном монолитном классе, вы создали поток. Далее: вы выводите функцию (т. е.: RefreshFunction) в отдельный поток. Область действия вашей функции та же, класс тот же, исполнение отдельно.

Мое предложение номер один, хотя уже очень поздно (извините за оживление старой нити, это интересно!)- этоПоиск однородных циклов преобразования , где каждая итерация цикла мутирует полностью независимую часть данных от других итераций.

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

Что Я mean by homogenous loops-это в основном тот, который преобразует данные очень простым способом, например:

for each pixel in image:
    make it brighter

Это очень просто рассуждать, и вы можете безопасно распараллелить этот цикл без каких-либо проблем, используя OMP или TBB или что-либо еще, и не запутываясь в синхронизации потоков. Достаточно одного взгляда на этот код, чтобы полностью понять его побочные эффекты.

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

Сначала вы должны тщательно понять побочные эффекты любого кода, который вы пытаетесь распараллелить (и я имею в виду тщательно!!!), поэтому поиск этих однородных петель дает вам изолированные области кодовая база вы можете легко рассуждать о побочных эффектах до такой степени, что вы можете уверенно и безопасно распараллеливать эти горячие точки. Это также улучшит ремонтопригодность кода, сделав его очень легким для понимания изменений состояния, происходящих в этом конкретном фрагменте кода. Сохраните мечту о многопоточном приложении uber, выполняющем все параллельно, на потом. А пока сосредоточьтесь на выявлении / извлечении критически важных для производительности однородных циклов с простыми потоками управления. и простые побочные эффекты. Это ваши приоритетные цели для распараллеливания с помощью простых распараллеленных циклов.

Признаюсь, я несколько уклонился от ваших вопросов, но большинство из них не нужно применять, если вы делаете то, что я предлагаю, по крайней мере, до тех пор, пока вы не проработаете свой путь до точки, где вы больше думаете о многопоточных проектах, а не просто распараллеливаете детали реализации. И вам, возможно, даже не нужно идти так далеко, чтобы иметь очень конкурентоспособный продукт с точки зрения спектакль. Если у вас есть большая работа в одном цикле, вы можете выделить аппаратные ресурсы, чтобы сделать этот цикл быстрее, вместо того, чтобы делать много операций, выполняемых одновременно. Если вам приходится прибегать к более асинхронным методам, например, если ваши горячие точки больше связаны с вводом-выводом, ищите подход async/wait, где вы запускаете асинхронную задачу, но делаете некоторые вещи в то же время, а затем ждете завершения асинхронной задачи(задач). Даже если в этом нет абсолютной необходимости, идея состоит в том, чтобы отсечь изолированные области ваша кодовая база, где вы можете со 100% уверенностью (или по крайней мере 99,9999999%) сказать, что многопоточный код является правильным.

Вы никогда не захотите играть с условиями гонки. Нет ничего более деморализующего, чем найти какое-то неясное состояние гонки, которое происходит только один раз в полнолуние на машине какого-то случайного пользователя, в то время как вся ваша команда QA не может воспроизвести его, только чтобы, 3 месяца спустя, столкнуться с ним самостоятельно, за исключением одного раза, когда вы запустили сборку выпуска без отладочной информации доступно, пока вы затем ворочаетесь во сне, зная, что ваша кодовая база может рассыпаться в любой момент, но так, что никто никогда не сможет последовательно воспроизвести. Так что расслабьтесь с многопоточными устаревшими кодовыми базами, по крайней мере сейчас, и придерживайтесь многопоточности изолированных, но критических разделов кодовой базы, где побочные эффекты мертвы просто рассуждать. И тестируйте дерьмо из него-в идеале применяйте подход TDD, когда вы пишете тест для кода, который вы собираетесь многопоточность, чтобы гарантировать, что он дает правильный вывод после завершения... хотя условия гонки-это те типы вещей, которые легко пролетают под радаром модульного и интеграционного тестирования, поэтому вам снова абсолютно необходимо понять всю совокупность побочных эффектов, которые происходят в данном фрагменте кода, прежде чем вы попытаетесь его многопоточить. Лучший способ сделать это-сделать побочные эффекты как можно более простыми для понимания с помощью простейших потоков управления, вызывающих только один тип побочных эффектов. на целую петлю.

Трудно дать вам правильные рекомендации. Но...

Самый простой выход, по моему мнению, заключается в преобразовании вашего приложения в ActiveX EXE, поскольку COM поддерживает потоковую обработку и т. д. встроенная прямо в него ваша программа автоматически станет многопоточным приложением. Конечно, вам придется внести довольно много изменений в свой код. Но это самый короткий и безопасный путь.

Я не уверен, но, вероятно, RichClient Toolset lib может сделать этот трюк для вас. На сайте автор написал:

Он также предлагает бесплатную загрузку регистрации / инстанцирование-возможности для ActiveX-DLL и нового, простого в использовании потокового подхода, который работает с именованными трубами под капюшон и работает поэтому также кросс-процесс.

Пожалуйста, проверьте это. кто знает, может быть, это правильное решение для ваших требований.

Что касается управления проектами, я думаю, что вы можете продолжать использовать то, что предусмотрено в вашей выбранной IDE, интегрировав ее с SVN через плагины.

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

При разработке этого программного обеспечения мы столкнулись с той же проблемой, что вы проиллюстрировали здесь. Чтобы решить эту проблему, мы преобразовали приложение в ActiveX EXE, и мы преобразовали все те части, которые должны выполняться параллельно в Библиотеки DLL ActiveX. Мы не использовали для этого никаких сторонних библиотек!

HTH