Design Pattern, Qt Model/View и несколько потоков

Я создаю приложение, которое отображает рыночные данные и использует его в некоторых других формах. Я храню рыночные данные на карте. std::map<tickerId, StockData>. Позвольте мне привести один использованный случай использования этой карты.

  • сеть отправляет пакет данных, инкапсулирующий данные запаса в момент времени t. updatePrice(tickerId, latestPrice)
  • обновить данные запаса на карте. Теперь несколько потоков могут получить доступ/обновить данные. Таким образом, карта должна быть заблокирована для потокобезопасных операций. Вот первый вопрос: мне нужно также блокировать базовые данные для обновлений?
  • Существует множество применений новых данных о запасах, скажем, есть обновление цены на IBM, тогда мне нужно обновить стоимость IBM в моем портфолио. Также отобразите новые данные на экране. И может быть несколько других одновременных применений. updatePosition(tickerId, price) и updateStockScreen(tickerId, price). Кроме того, разделение обновлений Gui с обновлением позиции важно, поскольку графический интерфейс не является основной силой приложения.
  • Я просто обеспокоен тем, как реализовать этот тип дизайна. Я прочитал о модели/представлении дизайна в QT для отображения данных, но если View thread читает с той же карты, он должен быть заблокирован. Это приводит к медленному/неэффективному дизайну. Каждый раз, когда вид читается с модели, модель должна быть заблокирована. Является ли это предпочтительным графическим интерфейсом в реальном времени?
  • Подводя итог, я сохранил много разных объектов в качестве карт. И объекты обновляются в реальном времени. Мне нужно обновить их, а затем использовать их в разных местах. Было бы здорово, если бы кто-нибудь мог дать мне небольшой пример того, как реализовать такие проекты.

Некоторые ссылки на полезные книги также ценятся.

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

Спасибо Шив

Ответ 1

Звучит концептуально, как вы хотите, чтобы модель была на одном потоке, а вид на другой, на который я смотрел в одну точку.

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

Однако... если ваша модель читает-записывает через представление, это невозможно сделать правильно вообще из-за очереди в слотах уведомлений. Здесь архив рассылки, который я имел в списке рассылки qt-interest по теме:

http://blog.hostilefork.com/qt-model-view-different-threads/

"Краткая версия - это то, что я не думаю, что это возможно для модели. быть изменен в потоке без GUI... независимо от того, находится ли модель данные защищены с помощью блокировок чтения/записи. Если то, что я собираю правильно, тогда Qt, вероятно, должен утверждать, что модель и его представление имеет одинаковую близость потоков (похоже, это не так)"

Последующий unit test разработчик KDE подтвердил это.

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

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

Ответ 2

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

Вы не можете блокировать между rowCount/columnCount и data().

Qt работает последовательно, что означает, что на человеческом языке он задаст "насколько вы велики", а затем спросите "какие данные у вас есть в этой позиции", и они подвержены разрыву.

Рассмотрим:

int FilesQueue::rowCount(const QModelIndex &/*parent*/) const
{
    std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
    return filesQueue.size();
}
QVariant FilesQueue::data(const QModelIndex &index, int role) const
{
    std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
    if ( role == Qt::DisplayRole) {
        return filesQueue[index.row()]->getFilename();
    }
}

Qt выполнит следующие вызовы:

//...
obj->rowCount();
obj->data(...);
//...

И у меня была ошибка утверждения повсюду, потому что просто между rowCount() и data() существовал поток, который менял размер данных! Это нарушило программу. Итак, это произошло:

//...
obj->rowCount();
//another thread: filesQueue.erase(...)
obj->data(...);
//...

Мое решение проблемы состоит в том, чтобы проверить размер, опять же, в методе data():

QVariant FilesQueue::data(const QModelIndex &index, int role) const
{
    std::lock_guard<decltype(mainQueueMutex)> lg(mainQueueMutex);
    //solution is here:
    if(static_cast<int>(filesQueue.size()) <= index.row())
        return QVariant();
    if ( role == Qt::DisplayRole) {
        return filesQueue[index.row()]->getFilename();
    }
}

и у меня 3 часа в жизни, я никогда не вернусь:-)