Как обмениваться данными между моделью и представлением? - PullRequest
0 голосов
/ 17 февраля 2009

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

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

EDIT:

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

Ответы [ 2 ]

2 голосов
/ 17 февраля 2009

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

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

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

Редактировать: После вашего редактирования я бы даже порекомендовал как можно больше устранить блокировку, учитывая, что вы хотите отображать много данных с максимально возможной скоростью, но изменяйте только 5% от итоговые данные.

Если вы изменяете алгоритм с изменения ячейки на создание новой ячейки на основе измененных данных, и вы вообще не изменяете свою модель, а создаете новую модель, копируя только умные указатели в неизмененные ячейки и создавая новые ячейки для остальных, тогда вам не понадобится блокировка любого из нескольких тысяч объектов. Готовая модель может быть передана в поток визуализации, и новая модель может быть создана немедленно. Аналогично для потока визуализации и объекта, который он создает из модели, - его можно передать в поток графического интерфейса и создать новый объект из текущей модели. Некоторые ячейки будут частью нескольких моделей, некоторые ячейки будут частью только одной модели. Объекты для создания визуализации и рендеринга на выходной дисплей также могут иметь общие ячейки. Интеллектуальные указатели будут обеспечивать правильное освобождение ячеек при удалении последней ссылки на них - в зависимости от того, в каком потоке это происходит.

Единственная блокировка, которая останется в вашей программе, - это одна блокировка верхнего уровня на поток для синхронизации доступа к текущей модели (или другим аналогичным объектам верхнего уровня). Поскольку выполняемые операции будут очень короткими, задержка больше не должна быть проблемой. В дополнение к этому этот дизайн будет максимально использовать несколько процессоров или ядер за счет немного большего потребления памяти и большего количества циклов ЦП. Однако это лучший способ повысить производительность программного обеспечения на текущем и будущем оборудовании, которое становится все более параллельным.

2 голосов
/ 17 февраля 2009

Я большой поклонник архитектуры отправки сообщений / рассылки сообщений. Это основной метод, который MFC / Win32 предоставляет для обмена данными между потоками. Эта архитектура основана на событиях сообщений потока, поэтому, когда принимающий поток обрабатывает сообщение потока, он обрабатывает данные, явно обновленные для связи между потоками (см. Пример ниже).

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

PostThreadMessage(msg, void* param, int paramSize)
{
    lock(msgQueueLock);

    // may wish to copy param
    char paramCpy = malloc
    msgQueue.Queue(msg, pparam, paramSize); 

    unlock(msgQueueLock);
}

тогда основной цикл любого потока просто

// thread's msg pump
while (1)
{
    // can also use condition var to wait for queue to change...
    lock(msgQueueLock);
    HandleMsgLocally(msgQueue.Deque())
    unlock(msgQueueLock);
}

В любом случае, возвращаясь к MVC, если что-то изменится в вашей модели, он может опубликовать ваше мнение для обновления определенного поля следующим образом:

// Well known msg name
int msgName = MODEL_FIELD_A_UPDATED

...

void Model::UpdateFieldA(int newVal)
{
    int* valToCommunicate = new int(newVal)
    PostThreadMessage(MODEL_FIELD_A_UPDATED, valToCommunicate, sizeof(int))
}


...
void HandleMsgLocally(...void * param,)
{
    if (msg == MODEL_FIELD_A_UPDATED)
    {
       int* val = reinterpret_cast<int*>(param);
       //... process param
       delete val;
    }
}

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

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

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

...