Russian Qt Forum
Ноябрь 23, 2024, 03:29 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2 3   Вниз
  Печать  
Автор Тема: [РЕШЕНО] Потоки и модель данных, или немного эзотерики.  (Прочитано 19004 раз)
ctin
Гость
« : Май 25, 2013, 18:19 »

Здравствуйте!
Везде читал, что работа с моделью данных небезопасна в отношении потоков.
Пришел момент столкнуться с этим, когда поток обрабатывает модель данных. Решил проверить, сделал тестовую программу.
Сабж: создается глобальный потомок QAbstractListModel, который устанавливается в qml файл как свойство:
Код:
    qmlRegisterType<ModuleDataModel>("CustomComponents", 1, 0, "ModuleDataModel");
    viewer.rootContext()->setContextProperty("pModel", QVariant::fromValue(pModel));
Заполняем модель
Код:
    pModel->refreshModelData(8);
    // 8 - количество строк

После этого видим закономерный список с делегатами, соответствующими данным в модели.
Теперь самое интересное: создаем класс, который в отдельном потоке будет обходить модель и считывать её значения:
Код:
TClass::TClass(ModuleDataModel *pModel, bool readOnly) :
    QObject(0), m_pModel(pModel), m_readOnly(readOnly)
{
    QtConcurrent::run(this, &TClass::__process);
}
void TClass::__process()
{
    static int counter = 0;
    qDebug() << "started!" << counter++;
    while(qApp)
    {
        Sleepy::usleep(1);
        int size = m_pModel->m_moduleDataItems.size();
        for(int i = 0;  i < size;  i++)
        {
            qreal val = m_pModel->data(i, ModuleDataModel::DeviceValueRole).toReal();
            if(!m_readOnly)
            {
                m_pModel->setData(i, val ? 0 : 10, ModuleDataModel::DeviceValueRole);
            }
        }

    }
}
И перед запуском GUI создаем четыре экземпляра этого класса:
Код:
    ....main....

    TClass tclass1(pModel, false), tclass2(pModel, false), tclass3(pModel, true), tclass4(pModel, true);
    QmlApplicationViewer viewer;
    ....

    viewer.showExpanded();
    return app->exec();
}

и.... всё работает! Программа выводит ожидаемое
Цитировать
started! 2
started! 0
started! 1
started! 3
и неожиданное отсутствие ошибок.

Буду признателен советам, предложениям!
« Последнее редактирование: Май 26, 2013, 17:49 от ctin » Записан
Bepec
Гость
« Ответ #1 : Май 25, 2013, 20:40 »

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

PS покажите где вы это читали Веселый В контексте Qt пожалуйста.
Записан
ctin
Гость
« Ответ #2 : Май 25, 2013, 22:28 »

Ну наверное потому, что реализацию методов data и setData пишу я, так как это абстрактные функции.
что-то типа

Код:
QVariant ModuleDataModel::data(const int pos, int role) const
{
    if(pos > (m_moduleDataItems.size() - 1))
        return QVariant();
    const ModuleDataItem &moduleDataItem = m_moduleDataItems.at(pos);
    switch(role)
    {
    case DeviceValueRole:   return QVariant::fromValue(moduleDataItem.deviceValue);
    case UserValueRole:     return QVariant::fromValue(moduleDataItem.userValue);
    ....
    }
}

bool ModuleDataModel::setData(const int index1, const QVariant &value, const int role)
{
    Q_ASSERT(index1 >= 0);
    Q_ASSERT(index1 < m_moduleDataItems.size());
    ModuleDataItem moduleDataItem = getItem(index1);
    switch(role)
    {
    case DeviceValueRole:   moduleDataItem.deviceValue = value.toUInt();    break;
    case UserValueRole:     moduleDataItem.userValue = value.toUInt();      break;
    }
    ....
    m_moduleDataItems.replace(index1, moduleDataItem);
    emit dataChanged(index(index1), index(index1));
    return true;
}

Таким образом, работая с одной и той же моделью в двух и более потоках я просто обязан ломать модель не константной функцией setData, а именно в строке
    
Код:
m_moduleDataItems.replace(index1, moduleDataItem);

p.s. мьютексы не использовал.
    
« Последнее редактирование: Май 25, 2013, 22:30 от ctin » Записан
Bepec
Гость
« Ответ #3 : Май 25, 2013, 23:37 »

Вы не понимаете.Система сигнал слотов вызывает ваши эти функции последовательно из очереди событий потока.

Т.е. фактически вы пишите функцию, которая последовательно обрабатывает данные. Это система сигнал-слотов Qt. Почитайте о ней внимательнее...

Смотрите:
Сигнал-слотовая система потокобезопасна.
Модель построена на сигнал-слотовой системе.
Вывод - модель потокобезопасна.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #4 : Май 26, 2013, 00:39 »

Пиздец какой-то.
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #5 : Май 26, 2013, 09:59 »

Таки добрался к компу. Модели (да и вообще, все наследники QObject) НЕ потокобезопасны, они реентрантны. Это значит, что их можно использовать только из 1го потока в 1 момент времени.
Сигнал/слоты тут вообще нипричем, сигналом модель лишь уведомляет view об изменениях, а view уже дергает ф-ии модели напрямую. Вывод - модель и вью должны быть в одном потоке или все методы модели необходимо делать потокобезопасными.
Записан
Странник
Гость
« Ответ #6 : Май 26, 2013, 10:01 »

Вы не понимаете.Система сигнал слотов вызывает ваши эти функции последовательно из очереди событий потока.

Т.е. фактически вы пишите функцию, которая последовательно обрабатывает данные. Это система сигнал-слотов Qt. Почитайте о ней внимательнее...

Смотрите:
Сигнал-слотовая система потокобезопасна.
Модель построена на сигнал-слотовой системе.
Вывод - модель потокобезопасна.
с какой бы радости сеттеры модели вызывались посредством сигнал-слотов? это обычные публичные методы, представление (или сам программист) должно дергать их напрямую. упоминаний о потокобезопасности классов моделей я тоже что-то не припомню. раньше в документации в описании класса вроде бы писали thread-safe, буде таковое имеется.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #7 : Май 26, 2013, 10:02 »

Таким образом, работая с одной и той же моделью в двух и более потоках я просто обязан ломать модель не константной функцией setData, а именно в строке
    
Код:
m_moduleDataItems.replace(index1, moduleDataItem);
Как читающий может догадаться кто такой ModuleDataItem? Возможно replace сводится к оператору = который в свою очередь разбивается на атомарные операции присвоения. Вообще трудно что-то сказать если Вы кормите только огрызками кода  Улыбающийся
Записан
thechicho
Гость
« Ответ #8 : Май 26, 2013, 13:38 »

//НЕ потокобезопасны, они реентрантны. Это значит, что их можно использовать только из 1го потока в 1 момент времени.
можете подробнее объяснить? разве что-то вообще можно использовать в разных потоках в 1 момент времени? в плане безопасности. нужно же лочить все равно... или, если потокобезопасны, то обращение к таким переменным автоматически лочиться чтоле? а реентрантные нужно вручную лочить, при одновременном доступе к ним из разных потоков?

под лочить имею в виду
Код:
    mutex.lock();
     number *= 5;
     number /= 4;
     mutex.unlock();
Записан
ctin
Гость
« Ответ #9 : Май 26, 2013, 17:48 »

Предпочитаю QMutexLocker, так как не знаешь где сделаешь выход из функции.

Верес, функция модели setData вызывается напрямую, по этому да. Таки "реентрантны".
Igors, совершенно верно во всех отношениях. К сожалению маленький опыт приводит к таким вот оплошностям.

Итог: работа с присвоением в функции setData идет на атомарном уровне, по этому не происходит сбоя. Всем большое спасибо.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Май 26, 2013, 18:00 »

Итог: работа с присвоением в функции setData идет на атомарном уровне,
Ну может и не на атомарном, напр
Код
C++ (Qt)
// внутри оператора =
m_double = second.m_double;
 
Присваивание double может быть не атомарно, но это не ведет ни к какому краху - просто получается неверный результат (что намного хуже для отлова)
Записан
thechicho
Гость
« Ответ #11 : Май 26, 2013, 18:35 »

//Предпочитаю QMutexLocker, так как не знаешь где сделаешь выход из функции
как это не знаешь? у вас функция на миллион строк? а что именно лочить то, надеюсь, знаете? или не знаете, поэтому и предпочитаете?)
Записан
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 3260


Просмотр профиля
« Ответ #12 : Май 26, 2013, 21:29 »

//НЕ потокобезопасны, они реентрантны. Это значит, что их можно использовать только из 1го потока в 1 момент времени.
можете подробнее объяснить? разве что-то вообще можно использовать в разных потоках в 1 момент времени? в плане безопасности. нужно же лочить все равно... или, если потокобезопасны, то обращение к таким переменным автоматически лочиться чтоле? а реентрантные нужно вручную лочить, при одновременном доступе к ним из разных потоков?

под лочить имею в виду
Код:
    mutex.lock();
     number *= 5;
     number /= 4;
     mutex.unlock();
Из разных потоков можно использовать потокобезопасные методы/классы. Пример - функция QObject::connect() потокобезопасна, ее можно звать из любого потока без всяких блокировок объектов.
А вот методы QString'а дергать из разных потоков не стоит. Но если заблокировать доступ к объекту, то можно дергать поочереди (сначала один поток поменял, потом второй). Это реентрантный класс.
А вот всякие QWidget'ы вообще нельзя трогать из негуевого потока - это нереентрантный класс, даже навесив блокировки.

Код:
как это не знаешь? у вас функция на миллион строк? а что именно лочить то, надеюсь, знаете? или не знаете, поэтому и предпочитаете?)
Ну, например, есть такая вещь, как исключения. И они могут вылетать в самых неожиданных местах (напоминаю, что даже new тровает исключение в случае нехватки памяти). То есть, даже банальное объявление переменной может быть чревато внезапным выходом из ф-ии. И штуки типа мьютекслокеров помогают написать код, который сможет работать после неудачно пролетевшего исключения, а не оставить мьютекс залоченным навеки.
Записан
ctin
Гость
« Ответ #13 : Май 26, 2013, 23:32 »

Igors, спасибо, запомню. Хотя если речь о том, что 4-х байтный double может слететь на 16-битной оси - это нормально.
Вопрос стоял в том, почему программа не вылетает, оказалось что в атомарности процесса присваивания используемой структуры.

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


Авварон
Да, я использовал QMetaObject::invokeMetod с аргументом Qt::QueuedConnection когда это было возможно и обоснованно (к примеру setText в QLabel). Но иногда это не выгодно. Например когда есть важность в последовательности действий. Например когда в функции ты считываешь параметры прибора, а потом исходя из этих параметров снимаешь данные, и эти же параметры где-то выгребаются по таймеру.
Конечно, есть ещё Qt::BlockingQueuedConnection, но пока это круто для меня.
Пришлось делать тестовое ПО с несколькими потоками и одной моделью, привязанной к гую.
« Последнее редактирование: Май 27, 2013, 09:55 от ctin » Записан
thechicho
Гость
« Ответ #14 : Май 27, 2013, 11:17 »

//Пример - функция QObject::connect() потокобезопасна, ее можно звать из любого потока без всяких блокировок объектов.
сорри, можете пояснить этот пример? я думал защищать от одновременно доступа нужно переменные. как может быть одновременный доступ к методу? и в данном случае QObject::connect() к чему одновременный доступ будет? ведь объекты же разные и переменные у них разные будут? или как?

//А вот методы QString'а дергать из разных потоков не стоит. Но если заблокировать доступ к объекту, то можно дергать поочереди (сначала один поток поменял, потом второй).

можете пример показать, где это нужно? просто не вижу смысла вызывать у одной переменной QString методы из разных потоков. хотя у меня опыт небольшой, мне приходилось лочить только список, когда разные потоки могли одновременно к нему обращаться и считывать элемент списка. ну еще и булевы переменные лочил.
Записан
Страниц: [1] 2 3   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.075 секунд. Запросов: 23.