Russian Qt Forum

Qt => Многопоточное программирование, процессы => Тема начата: Serr500 от Июнь 06, 2012, 15:15



Название: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 06, 2012, 15:15
Можно ли как-то сделать потокобезопасную QStandardItemModel?


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Bepec от Июнь 06, 2012, 17:54
Пару мутексов и всё нормально, не? :)


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Igors от Июнь 06, 2012, 18:19
Пару мутексов и всё нормально, не? :)
оптимизм * компетентность = константа
 :)


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 06, 2012, 20:43
Пару мутексов и всё нормально, не? :)
Не. Model-Based Widgets вызывают всяческие методы QStandardItemModel. Что именно защищать мьютексами?


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Igors от Июнь 07, 2012, 02:59
Интересный вопрос. Поскольку вьюшка держит указатель на модель (поправьте если не так) то сделать "в общем виде" не получается. С др стороны thread-safe контейнер - вполне реально и не так уж сложно. Может быть ограничиться проверкой флага "запись" и выбрасывать assert во всех методах вызываемых из UI, ну и перед отдачей ему управления выставить "чтение", с тем чтобы пишущие ждали пока UI отработает


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 07, 2012, 08:14
Мда... Что-то сложно всё получается... Попробую как-нибудь обойтись без обращения к модели из разных потоков...


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Bepec от Июнь 07, 2012, 16:14
Кхм. Я конечн многого могу не понимать, но чем потоконебезопасно обращение к модели из разных потоков, если контейнер данных защищён мутексами?
Все стандартные методы в своей модели не оперируют с данными. Максимум чтение.
А удаление/добавление/изменение производится с помощью переопределённых функций, которые и можно защитить мутексами, не?

update:
ОМММ... Мари рама...
Я всегда буду читать название темы 2 раза!
Я всегда буду читать название темы 2 раза!
Я всегда буду читать название темы 2 раза!


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Igors от Июнь 07, 2012, 16:54
Кхм. Я конечн многого могу не понимать, но чем потоконебезопасно обращение к модели из разных потоков, если контейнер данных защищён мутексами?
Ну а что такое "потокобезопасный контейнер"? Задумывались ли Вы почему таких классов особо не видно? Пример

Код
C++ (Qt)
for (it = vec.begin(); it != vec.end(); ++it)
// что-то делаем
 
// или даже просто так
T & val = vec[i];
 
Если др нитка полезет с vec.erase (resize, insert и.т.п. - запись) то ссылки и итераторы в тексте выше станут невалидными. Это вроде легко решается установкой локов, но это должно делаться на стороне вызывающего, не видно как вшить это в сам контейнер. Как он может знать когда возвращенная ссылка/итератор больше не нужна и можно разрешить  запись?

Ну и получается что надо лочить все обращения, а как это сделать если они внутри либы в 100 местах?


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: CuteBunny от Июнь 08, 2012, 09:37
Мда... Что-то сложно всё получается... Попробую как-нибудь обойтись без обращения к модели из разных потоков...

А если попробовать через QSharedPointer?


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 08, 2012, 10:04
А как Shared Pointer защитит от одновременных обращений из разных потоков? QSharedPointer это простенький контейнер со счётчиком ссылок, который удалит объект, когда счётчик обнулится.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: CuteBunny от Июнь 08, 2012, 10:07
А как Shared Pointer защитит от одновременных обращений из разных потоков? QSharedPointer это простенький контейнер со счётчиком ссылок, который удалит объект, когда счётчик обнулится.

Ну судя, по документации, операции, осуществляемые над значением, на которое указывает qsharedpointer, потокобезопасны, если я всё правильно понял.

Цитировать
QSharedPointer and QWeakPointer are thread-safe and operate atomically on the pointer value. Different threads can also access the QSharedPointer or QWeakPointer pointing to the same object at the same time without need for locking mechanisms.

It should be noted that, while the pointer value can be accessed in this manner, QSharedPointer and QWeakPointer provide no guarantee about the object being pointed to. Thread-safety and reentrancy rules for that object still apply.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: DmitryM от Июнь 08, 2012, 10:20
Все будет зависеть от того как будет использоваться моделька в разных потоках.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 08, 2012, 10:26
Ну судя, по документации, операции, осуществляемые над значением, на которое указывает qsharedpointer, потокобезопасны, если я всё правильно понял.
Правильно. То есть изменение указателя в QSharedPointer атомарно и потокобезопасно. Но указатель у меня не меняется. Этот указатель передаётся во вьюху, которая вызывает методы экземпляра класса, представленного указателем. Вьюха тем самым обращается к данным, содержащимся в модели. Из другого потока во время обращения к данным может быть вызван метод, эти данные изменяющий. В результате в процессе выполнения метода, вызванного из вьюхи, данные искажаются и приложение падает. (В моём случае вызывается метод поиска родителя, который уже удалён.)

P.S. Посмотрел сейчас на реализацию QAbstractItemModel. Контейнер там прячется где-то в приватной части класса и доступа к нему нет. То есть даже создать собственную потокобезопасную модель на его основе не удастся.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Авварон от Июнь 08, 2012, 10:47
P.S. Посмотрел сейчас на реализацию QAbstractItemModel. Контейнер там прячется где-то в приватной части класса и доступа к нему нет. То есть даже создать собственную потокобезопасную модель на его основе не удастся.

МОжет QStandardItemModel? лень смотретть в сорцы, но какой контейнер может быть в абстрактной модели?


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: DmitryM от Июнь 08, 2012, 11:00
Как вариант сделай какой-нибудь ThreadSafeTable и его помести в QAbstractItemModel


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 08, 2012, 11:16
МОжет QStandardItemModel? лень смотретть в сорцы, но какой контейнер может быть в абстрактной модели?
Упс... Незамученная очепятка...  :(

Как вариант сделай какой-нибудь ThreadSafeTable и его помести в QAbstractItemModel
Это мысль.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 08, 2012, 14:48
Немного задолбался, но сделал. Взял QAbstractItemModel, унаследовался от неё, создал собственный контейнер и защитил все обращения к элементам контейнера в переопределённых функциях с помощью QReadWriteLock. Падать перестала. Осталось аккуратно оттестировать и убедиться, что всё нормально. Плоховато я разбираюсь в Model/View, поэтому долго возился, постоянно посматривая в справку и книжку Бланше. Код не выкладываю, поскольку он специфичен для задачи и модель не совсем полнофункциональная - только для чтения (редактировать элементы нельзя). Да ещё и куча методов, работающих со специфичными для задачи данными, хранящимися в контейнере, которые собственно и порождают содержимое модели. Когда будет время, оформлю саму модель в виде отдельного класса, а этот сделаю его наследником.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Bepec от Июнь 09, 2012, 11:39
Правильное решение, я его и предлагал ;)

PS если всё грамотно защитить мутексами, то и не будет падать ;)


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 09, 2012, 13:19
Не хотелось создавать собственную модель, особенно учитывая слабое знание Model/View. Со стандартной было бы намного проще. Жаль, что не получилось. Убил целый день на создание, отладку и "вылизывание" кода.

Кстати, интересный факт. Я переопределил метод rowCount. Метод защищён мьютексом. Есть ещё метод, меняющий данные, который защищён тем же мьютексом. В документации сказано, что добавление и удаление строк нужно "оборачивать" в методы beginInsertRows/endInsertRows и beginRemoveRows/endRemoveRows соответственно. Выяснилось, что begin...Rows вызывают виртуальный метод rowCount, что приводило к блокировке потока и меня вначале немного озадачило. Пришлось вместо обычного мьютекса использовать рекурсивный. Может быть, эта информация избавит кого-нибудь от долгого поиска источника блокировки.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 13, 2012, 08:55
Думал, что всё хорошо, но отладка это опровергла. Даже потокобезопасная модель не спасла от ошибок. При очень быстром изменении содержимого модели программа свалилась с ошибкой SEGFAULT. Оказалось, что вьюха (QTreeView) хранит в себе индексы (QModelIndex). В этих индексах есть internalPointer, который указывает на элемент контейнера, связанный с узлом дерева. Вьюха ухитрилась в методе QTreeView::drawTree запросить родителя у уже удалённого элемента контейнера и, естественно, свалилась. Похоже, потокобезопасность и модель несовместимы. Придётся решать задачу с помощью сигналов и слотов.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Bepec от Июнь 14, 2012, 12:05
Кхм... Уважаемый. Помоему вы чтот не то творите :)

Уже более полугода, бегает, не выключаясь, многопоточная моделька (запись телеметрии с 30+ устройств). Как бы вы что-то не то делаете, не?

Вьюха - ничего не хранит. Она всё запрашивает у модели. Если вылетает - значит вы(создатель модели), где-то передали неправильные данные, позволяющие запросить несуществующий элемент.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 14, 2012, 14:49
src\gui\itemviews\qtreeview_p.h:
Код:
struct QTreeViewItem
{
    // . . .
    QModelIndex index; // we remove items whenever the indexes are invalidated
    // . . .
};

Код:
class Q_GUI_EXPORT QTreeViewPrivate : public QAbstractItemViewPrivate
{
    // . . .
    private :
    // . . .
        mutable QVector<QTreeViewItem> viewItems
    // . . .
}

src\gui\itemviews\qtreeview.cpp:
Код:
void QTreeView::drawTree(QPainter *painter, const QRegion &region) const
{
    Q_D(const QTreeView);
    const QVector<QTreeViewItem> viewItems = d->viewItems;
    // . . .
        // paint the visible rows
        for (; i < viewItems.count() && y <= area.bottom(); ++i) {
               // . . .
                drawRow(painter, option, viewItems.at(i).index);
               // . . .
         }
    // . . .
}

Код:
void QTreeView::drawRow(QPainter *painter, const QStyleOptionViewItem &option,
                        const QModelIndex &index) const
{
    // . . .
    const QModelIndex parent = index.parent();
    // . . .
}

Вопрос. Что будет, если во время выполнения цикла в QTreeView::drawTree всё содержимое модели будет разрушено из параллельного потока?


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Igors от Июнь 14, 2012, 15:56
Ну а почему бы просто "не пущать" тот paint? Напр так (набросок)

Код
C++ (Qt)
void MyTreeWidget::paintEvent( ...)
{
 MyModel * m = qobject_cast <MyModel *> model();
 if (m && !m->mLocker.tryLockForRead()) {    // если модель пишется
   m->mRepaintFlag = true;   // ставим флаг перерисовки после окончания записи
   return;                              // уходим
 }
 ...
}
 


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 15, 2012, 08:00
А где гарантия что виджет не полезет в модель в других местах? Фактически нужно будет создать новый виджет.

Задачу я решил слотами и сигналами. Существует потокобезопасный контейнер и отдельно от него модель. Контейнер при изменении содержимого генерирует сигналы, которые ловятся моделью. Модель - потомок QStandardItemModel и в ней хранится массив пар. Первый элемент пары - QSharedPointer на элемент контейнера, второй - QStandardItem*. Для моей задачи этого хватило. Два дня - полёт нормальный.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Igors от Июнь 15, 2012, 09:29
А где гарантия что виджет не полезет в модель в других местах? Фактически нужно будет создать новый виджет.
Нигде, нужно отловить/перекрыть все такие случаи, что может быть сложно

Задачу я решил слотами и сигналами. Существует потокобезопасный контейнер и отдельно от него модель. Контейнер при изменении содержимого генерирует сигналы, которые ловятся моделью. Модель - потомок QStandardItemModel и в ней хранится массив пар. Первый элемент пары - QSharedPointer на элемент контейнера, второй - QStandardItem*. Для моей задачи этого хватило. Два дня - полёт нормальный.
Критиковать чужие решения легко, но все же 2 структуры для хранения одних данных не производят хорошего впечатления. Также неясно - допустим главная нитка рисует строки. Не вижу как модель может быть  безопасно изменена. Все равно надо блокировать изменения в модели при отрисовке и наоборот. Как это решает QSharedPointer ?


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 15, 2012, 09:46
Нигде, нужно отловить/перекрыть все такие случаи, что может быть сложно
Вот и я о том же.

2 структуры для хранения одних данных не производят хорошего впечатления.
Хранятся не сам данные, а указатели на них. Согласен, что впечатление не очень хорошее, но иногда приходится искать компромисс между временем разработки и "элегантностью" кода. Кроме того, то что отображает модель, в явном виде в контейнере почти не хранится. Отображаемые данные строятся по данным контейнера. Проще говоря, генерируются текстовые строки из нетекстовых данных.

Также неясно - допустим главная нитка рисует строки. Не вижу как модель может быть  безопасно изменена. Все равно надо блокировать изменения в модели при отрисовке и наоборот. Как это решает QSharedPointer ?
Модель безопасно изменяется потому, что сигнал не будет обработан, пока нитка рисует строки. Может быть, я не очень понятно написал. Модель и вьюха находятся в одном потоке, поэтому метод отрисовки прерван не будет. Аналогично, обработчик сигнала не будет прерван отрисовкой.
QSharedPointer используется для того, чтобы гарантировать валидность указателя. При удалении элемента из контейнера элемент не разрушится, если он используется моделью. тем самым у нас не возникнет ситуации обращения к разрушенным данным.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Bepec от Июнь 15, 2012, 19:33
Кхм. Возможно скажу очень простую вещь.

Тебе мб стоит сделать отрисовку модели как у меня - блоками? :)

Человеческий глаз не успевает заметить изменения в 25мс. Собственно как построено.

Есть контейнер. Добавление/удаление из него идёт через мутексы из разных потоков.
Раз в 25 мс вызывается таймер и обновляет модель, используя значения предыдущего обновления и слот получения данных из контейнера с мутексом.
В результате отрисовывается не " в любой момент изменяемая модель", а только "статичное состояние модели на момент обновления".

Сумбурно наверно пояснил. Как мог ;)

Проще выражаясь - модель у тебя будет требовать данные с контейнера, только раз в 25(N) мс. Причём гарантированные данные, защищённые мутексами. Никакого своеволия не будет. Во всяком случае повторюсь - всё бачит.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Igors от Июнь 15, 2012, 19:46
Есть контейнер. Добавление/удаление из него идёт через мутексы из разных потоков.
Раз в 25 мс вызывается таймер и обновляет модель, используя значения предыдущего обновления и слот получения данных из контейнера с мутексом.
В результате отрисовывается не " в любой момент изменяемая модель", а только "статичное состояние модели на момент обновления".
Если я правильно понял, модель - еще один контейнер, данные в него перегружаются из первого. Поскольку это выполняется в главной нитке - нет конфликта с рисованием. Все-таки лок надо взять и интересно - а что же будет если первый контейнер изменяется непрерывно, так сказать "мутным потоком"?

Во всяком случае повторюсь - всё бачит.
Цитировать
Тестирование может показать наличие ошибок но не их отсутствие
:)


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 15, 2012, 21:22
Тебе мб стоит сделать отрисовку модели как у меня - блоками? :)
Человеческий глаз не успевает заметить изменения в 25мс.
Модель в нормальном состоянии у меня меняется гораздо реже. Быстрые изменения - это экстремальный случай, но он оказался возможным. В основном модель будет оставаться в неизменном состоянии более секунды.

Вариант отрисовки по таймеру интересный и применяется у меня в другом проекте. Правда, там не модель, а некое, скажем так, "состояние". Вначале я обновлял GUI при каждом изменении "состояния" и получил очень интересный эффект - обработчик уже завершился, а GUI ещё несколько секунд "разгребает" кучу сигналов и перерисовывается. Прикольно, но абсолютно непрактично.  :) Сделал обновление по таймеру раз в 250 мс - резко упала нагрузка на CPU и пропал вышеуказанный эффект.

Сумбурно наверно пояснил. Как мог ;)
Вполне понятно. Спасибо.  ;)

Если я правильно понял, модель - еще один контейнер, данные в него перегружаются из первого.
Правильно.

Все-таки лок надо взять
Не очень понял. Какой лок надо взять?

и интересно - а что же будет если первый контейнер изменяется непрерывно, так сказать "мутным потоком"?
В моей реализации будет очень худо - GUI не успеет разгрести сигналы об изменении модели. В случае Вереса всё будет нормально, просто отображение может запаздывать на время интервала таймера.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Bepec от Июнь 15, 2012, 23:04
Лок - mutex.lock() :)

Дабы не смущать другие потоками возможностью ошибок :) Мой вариант работоспособен на 97% :D

1% на мои кривые руки, 1% на плохую ОС, 1% на форс мажорные обстоятельства :D

PS кстати на основе этого можно неплохую защиту сделать для программ - как показывает практика, попытка стороннего доступа(инжектом, али подменой функции) вся модель падает :D


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Serr500 от Июнь 17, 2012, 14:46
Лок - mutex.lock() :)
Это понятно.  :) Я не понял, в каком месте его надо взять и чего защищать. Вроде, там ничего блочить не нужно.


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Igors от Июнь 17, 2012, 15:07
Это понятно.  :) Я не понял, в каком месте его надо взять и чего защищать. Вроде, там ничего блочить не нужно.
Детали реализации Верес держит в секрете (ну это его право), просто я полагаю что данные копируются из 1 контейнера (в который льют все нитки) в контейнер модели. Это копирование должно быть защищено локом. Здесь возникает проблема что UI может оказаться трудно прорваться к копированию если лок все время захвачен пишущими.

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


Название: Re: Потокобезопасная QStandardItemModel
Отправлено: Bepec от Июнь 17, 2012, 16:30
У меня реализация специфическая :D у меня данные могут только прибывать :) В удалении и прочем нет нужды.