Я вынужден использовать pthread_cond_broadcast (через pthread_cond_signal), чтобы гарантировать, что * мой * stream проснулся?

В контексте взаимодействия некоторого streamа QT GUI (stream pthread) с некоторым кодом C я наткнулся на следующую проблему: я запускаю stream QT Gui и, прежде чем мой stream C возобновит его путь, мне нужно убедиться, что все графические объекты внутри streamа QT Gui были построены, и они являются действительными QObjects (поскольку код C вызывает QObject:connect() на них).

Введение откладывается, ожидание выполняется через pthread_cond_wait() + переменную условия + связанный мьютекс в streamе C:

 int isReady=0; pthread_mutex_lock(&conditionReady_mutex); while(isReady==0) { pthread_cond_wait(&conditionReady_cv, &conditionReady_mutex); } pthread_mutex_unlock(&conditionReady_mutex); 

С другой стороны, stream QT Gui создает свои графические объекты, а затем сигнализирует об этом:

 pthread_mutex_lock(&conditionReady_mutex); isReady=1; pthread_cond_broadcast(&conditionReady_cv); pthread_mutex_unlock(&conditionReady_mutex); 

Базовый материал, как видите. Но возникает вопрос: в streamе Qt Gui я использовал pthread_cond_broadcast() , чтобы убедиться, что мой stream C проснулся, наверняка. Да, в моем текущем приложении у меня есть только stream C и stream Qt Gui, а pthread_cond_signal() должен выполнять работу по пробуждению streamа C (так как гарантировано пробудить хотя бы один stream, а stream C является единственным).

Но, в более общем контексте, допустим, у меня три streamа C, но я хочу, чтобы один (или два) из них разбудил. A (две) конкретные streamи. Как я могу это гарантировать?

Если я использую pthread_cond_signal() , это может просто пробудить только третий stream, который полностью пропустит эту точку, поскольку один stream, представляющий для меня интерес, не проснулся. OTOH, пробуждая все streamи, даже те, которые не нужны, через pthread_cond_broadcast() , который будет излишним.

Есть способ сообщить pthread_cond_signal() какой stream просыпаться?

Или я должен ввести дополнительные переменные условия, чтобы иметь более тонкую детализацию по группам streamов, которые разбужены с помощью pthread_cond_broadcast() ?

Спасибо.

Да, если вам нужен какой-то stream, который нужно разбудить, вам нужно либо транслировать пробуждение, либо использовать отдельную переменную условия для этого streamа.

Небезопасные манеры

Вызов любого общего метода QWidget из другого streamа является небезопасным, поскольку поведение Qt и лежащего в основе кода C ++ в неопределенном виде. Он может начать ядерный первый удар. Вы были предупреждены. Таким образом, вы не можете вызывать QWidget::update() , QLabel::setText(...) или любые другие подобные методы из отдельного streamа, если только вы не используете безопасную межстраничную связь, предлагаемую Qt.

Неправильно

 // in a separate thread widget->update(); widget->setText("foo"); 

Правильно

 // in a separate thread // using QMetaObject::invokeMethod(widget, "update") is unoptimal QCoreApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority); QMetaObject::invokeMethod(widget, "setText", Q_ARG(QString, "foo")); 

На Interthread Communication

Тривиальный способ общения с streamом, который содержит некоторые QObject, включает в себя отправку событий ему через статический метод QCoreApplication::postEvent() . Нить GUI является распространенным примером streamа, в котором находится целая куча QObjects. Использование postEvent() всегда безопасно для streamов. Этот метод не заботится о том, из какого streamа он вызван.

Существуют различные статические методы, разбросанные по Qt, которые внутренне используют postEvent() . Их ключевая подпись заключается в том, что они тоже будут статичными! Семейство QMetaObject::invokeMethod(...) является таким примером.

Когда вы подключаете сигналы к слотам в QObjects, которые живут в разных streamах, используется соединение в очереди. При таком соединении каждый раз, когда QMetaCallEvent сигнал, создается QMetaCallEvent и отправляется в целевой объект QObject, используя – вы догадались, что это правильно – QCoreApplication::postEvent() .

Таким образом, подключение сигнала от QObject в некотором негладном streamе к слоту QWidget::update() безопасно, поскольку внутри такого соединения используется postEvent() для распространения сигналов через барьер streamа. Цикл событий в streamе GUI подберет его и выполнит фактический вызов QWidget::update() на вашей метке. Полностью поточно-безопасный.

Оптимизации

Единственная проблема с использованием соединения сигнального слота или вызова метода по streamам заключается в том, что Qt не знает, что ваши обновления должны быть сжаты. Каждый раз, когда вы выделяете свой сигнал, подключенный к QWidget::update() в отдельном streamе, или каждый раз, когда вы вызываете invokeMethod , в очередь событий отправляется событие.

Идиоматический, если не документированный, способ обновления в streamах:

 QApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority); 

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

Итак, как насчет наших различных вызовов, связанных с виджетами, таких как, например, QLabel::setText() ? Наверное, было бы здорово, если бы мы как-то сжимали их? Да, это, безусловно, возможно, но мы должны ограничиться публичными Qt-интерфейсами. Qt здесь не предлагает ничего очевидного.

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

Вот как:

 QCoreApplication::removePostedEvents(label, QEvent::MetaCall); QMetaObject::invokeMethod(label, "setText", Q_ARG(QString, "foo")); 

Обратите внимание, что по соображениям производительности мы используем нормализованное представление слота: никаких дополнительных пробелов, и все const Type & преобразуются в просто Type .

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

Пост скриптум

Код C не должен вызывать QObject::connect , я думаю, что это признак плохого дизайна. Если вы хотите обмениваться данными с кодом C на код Qt, используйте статический QCoreApplication::postEvent(...) (подходящим образом завернутый / подверженный C, конечно). Чтобы сообщить об этом другим способом, вы можете вручную указать очередь событий в своих streamах. Как минимум, «очередь событий» будет только фиксированной структурой, но дело в том, чтобы использовать мьютекс для защиты доступа к нему.

Другим признаком плохого дизайна является то, что код, внешний по отношению к GUI-classу, зависит от отдельных элементов пользовательского интерфейса внутри этого classа. Вы должны предоставить набор сигналов и слотов в общем classе пользовательского интерфейса (скажем, ваш MainWindow ) и перенаправить их соответствующим элементам пользовательского интерфейса. Вы можете connect() сигналы к сигналам, но не слоты к слотам – вам придется закодировать слоты пересылки вручную.