Изменение поведения работы Qt без изменения кода библиотеки
Начиная с Qt 4.8 операции с HTTP стали многопоточными. Как известно, до Qt 5 не было возможности задать свой диспетчер событий для создаваемых потоков; в Qt 5 такая возможность появилась. Тем не менее, пока что нет возможности сказать Qt, что для всех создаваемых потоков нужно использовать такой-то диспетчер событий: без вызова QThread::setEventDispatcher()
будет использоваться диспетчер событий по умолчанию. Для Linux это либо QEventDispatcherUNIX
, использующий select()
, либо QEventDispatcherGLib
, использующий poll()
. Производительность этих диспетчеров примерно одинакова и далека от идеальной.
Хорошее представление о стеке HTTP в Qt даёт эта статья. В приведённых диаграммах видно, что Qt создаёт отдельный поток для выполнения HTTP-запросов (поток может выполнять до шести параллельных соединений к одному серверу).
В принципе, шесть параллельных соединений — это не повод использовать epoll()
вместо select()
; тем не менее, мне была интересна реализация вмешательства во внутреннюю кухню Qt, не трогая при этом код библиотеки.
Приведённый ниже код является демонстрацией реализации; код является рабочим, но тщательно не тестировался, поэтому использовать его только на свой страх и риск.
Когда QNetworkAccessManager (точнее, один из классов стека HTTP) создаёт поток, он даёт ему имя: bearerThread
, httpThread
, Thread (pooled)
; возможно, что используются и другие имена. Имена задаются посредством вызова QObject::setObjectName()
; как видно из документации, при изменении имени объекта вызывается (испускается?) сигнал objectNameChanged(const QString& objectName)
.
Тут нужно сделать отступление: задать потоку диспетчер событий можно только до сигнала QThread::started()
. Фактически, objectNameChanged()
является единственным сигналом, который гарантированно вызывается до запуска потока. Поэтому нужно каким-то образом перехватить данный сигнал, убедиться, что он вызывается из класса, являющегося или порождённым от QThread, почел чего создать свой диспетчер событий и установить его потоку. Проблема только в том, что нет документированного метода перехвата вызова сигналов, в результате чего придётся немного покопаться во внутренностях moc
.
Если посмотреть на реализацию сигнала, сгенерированную moc
, то видно, что она сводится к вызову метода QMetaObject::activate()
. Что интересно, этот метод абсолютно не документирован. В коде видно, что реализация метода ссылается на интересную структуру под именем qt_signal_spy_callback_set
. Дальше дело техники.
В qobject_p.h есть такие замечательные объявления:
struct QSignalSpyCallbackSet
{
typedef void (*BeginCallback)(QObject *caller, int signal_or_method_index, void **argv);
typedef void (*EndCallback)(QObject *caller, int signal_or_method_index);
BeginCallback signal_begin_callback,
slot_begin_callback;
EndCallback signal_end_callback,
slot_end_callback;
};
void Q_CORE_EXPORT qt_register_signal_spy_callbacks(const QSignalSpyCallbackSet &callback_set);
extern QSignalSpyCallbackSet Q_CORE_EXPORT qt_signal_spy_callback_set;
Пометка «for Qt Test» сама по себе подсказывает, где нужно смотреть примеры использования данной возможности.
Функция qt_signal_spy_callback_set()
реализована в qcoreapplication.cpp
с пометкой «Support for introspection»
BeginCallback
вызывается перед вызовом сигналов и слотов, EndCallback
— после.
В testlib/qsignaldumper.cpp находим пример использования данной функции.
В результате получаем такой код:
#include <QtCore/QMetaMethod>
#include <QtCore/QThread>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <QtCore/private/qobject_p.h>
#include <QtCore/private/qmetaobject_p.h>
#include <QtCore/private/qeventdispatcher_unix_p.h>
#include <stdio.h>
static void signal_begin_callback(QObject* caller, int signal_index, void** argv)
{
Q_ASSERT(caller != 0);
Q_UNUSED(argv);
const QMetaObject* mo = caller->metaObject();
Q_ASSERT(mo != 0);
QMetaMethod member = QMetaObjectPrivate::signal(mo, signal_index);
Q_ASSERT(member.isValid());
if (!strcmp(mo->className(), "QThread") && member.name() == "objectNameChanged") {
QThread* thr = qobject_cast<QThread*>(caller);
thr->setEventDispatcher(new QEventDispatcherUNIX());
}
QByteArray res;
res.append(mo->className()).append("::").append(member.name());
printf("%s\n", res.constData());
fflush(stdout);
}
class MyApplication : public QCoreApplication {
Q_OBJECT
public:
MyApplication(int& argc, char** argv) : QCoreApplication(argc, argv) {}
int exec(void)
{
QSignalSpyCallbackSet callbacks = { 0, 0, 0, 0 };
callbacks.signal_begin_callback = signal_begin_callback;
qt_register_signal_spy_callbacks(callbacks);
QNetworkAccessManager* mgr = new QNetworkAccessManager(this);
QNetworkRequest req(QUrl(QLatin1String("http://www.google.com/")));
QNetworkReply* reply = mgr->get(req);
QObject::connect(reply, SIGNAL(finished()), this, SLOT(finished()));
return QCoreApplication::exec();
}
public Q_SLOTS:
void finished(void)
{
QSignalSpyCallbackSet callbacks = { 0, 0, 0, 0 };
qt_register_signal_spy_callbacks(callbacks);
QCoreApplication::quit();
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
MyApplication a(argc, argv);
return a.exec();
}
Файл проекта:
QT -= gui
TARGET = spytest
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += main.cpp
У меня Qt собрана с поддержкой GLib, поэтому с закомментированным вызовом thr->setEventDispatcher(new QEventDispatcherUNIX());
получается такая картина:
QThread::started
QEventDispatcherGlib::aboutToBlock
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QFile::aboutToClose
QObject::destroyed
QFile::aboutToClose
QObject::destroyed
QFile::aboutToClose
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QGenericEngine::configurationAdded
QGenericEngine::configurationAdded
QGenericEngine::updateCompleted
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QNetworkConfigurationManagerPrivate::configurationAdded
QNetworkConfigurationManagerPrivate::onlineStateChanged
QObject::destroyed
QObject::destroyed
QObject::destroyed
QNetworkConfigurationManagerPrivate::configurationAdded
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QThread::objectNameChanged
QNetworkReplyHttpImpl::startHttpRequest
QEventDispatcherGlib::aboutToBlock
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QThread::started
QEventDispatcherGlib::aboutToBlock
QThread::objectNameChanged
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QThread::started
QObject::destroyed
QHostInfoResult::resultsReady
QObject::destroyed
QTcpSocket::stateChanged
QTcpSocket::stateChanged
QTcpSocket::hostFound
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QTcpSocket::stateChanged
QTcpSocket::connected
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QTcpSocket::readyRead
QHttpNetworkReply::headerChanged
QHttpThreadDelegate::downloadMetaData
QHttpNetworkReply::readyRead
QHttpThreadDelegate::downloadData
QHttpNetworkReply::dataReadProgress
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QHttpNetworkReply::finished
QHttpThreadDelegate::downloadFinished
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QObject::destroyed
QObject::destroyed
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QNetworkReplyHttpImpl::metaDataChanged
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QNetworkReplyHttpImpl::readyRead
QNetworkReplyHttpImpl::downloadProgress
QNetworkReplyHttpImpl::downloadProgress
QNetworkReplyHttpImpl::readChannelFinished
QNetworkReplyHttpImpl::finished
QNetworkAccessManager::finished
Видно, что везде после вызова QThread::started()
проявляется QEventDispatcherGlib
. Раскомментируем вызов setEventDispatcher()
и получим что-то такое:
QThread::started
QEventDispatcherUNIX::awake
QEventDispatcherUNIX::aboutToBlock
QFile::aboutToClose
QObject::destroyed
QFile::aboutToClose
QObject::destroyed
QFile::aboutToClose
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QObject::destroyed
QEventDispatcherUNIX::awake
QGenericEngine::configurationAdded
QGenericEngine::configurationAdded
QGenericEngine::updateCompleted
QEventDispatcherUNIX::awake
QNetworkConfigurationManagerPrivate::configurationAdded
QNetworkConfigurationManagerPrivate::onlineStateChanged
QNetworkConfigurationManagerPrivate::configurationAdded
QObject::destroyed
QEventDispatcherUNIX::aboutToBlock
QEventDispatcherUNIX::awake
QEventDispatcherUNIX::aboutToBlock
QObject::destroyed
QObject::destroyed
QThread::objectNameChanged
QThread::started
QEventDispatcherUNIX::awake
QEventDispatcherUNIX::aboutToBlock
QNetworkReplyHttpImpl::startHttpRequest
QEventDispatcherUNIX::awake
QEventDispatcherGlib::aboutToBlock
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QThread::objectNameChanged
QEventDispatcherUNIX::aboutToBlock
QThread::started
QObject::destroyed
QHostInfoResult::resultsReady
QObject::destroyed
QEventDispatcherUNIX::awake
QTcpSocket::stateChanged
QTcpSocket::stateChanged
QTcpSocket::hostFound
QEventDispatcherUNIX::aboutToBlock
QTcpSocket::stateChanged
QTcpSocket::connected
QEventDispatcherUNIX::awake
QEventDispatcherUNIX::aboutToBlock
QEventDispatcherUNIX::awake
QEventDispatcherUNIX::aboutToBlock
QTcpSocket::readyRead
QHttpNetworkReply::headerChanged
QHttpThreadDelegate::downloadMetaData
QHttpNetworkReply::readyRead
QHttpThreadDelegate::downloadData
QHttpNetworkReply::dataReadProgress
QEventDispatcherUNIX::awake
QHttpNetworkReply::finished
QHttpThreadDelegate::downloadFinished
QEventDispatcherUNIX::awake
QEventDispatcherUNIX::awake
QObject::destroyed
QObject::destroyed
QEventDispatcherUNIX::aboutToBlock
QNetworkReplyHttpImpl::metaDataChanged
QNetworkReplyHttpImpl::readyRead
QNetworkReplyHttpImpl::downloadProgress
QEventDispatcherGlib::awake
QEventDispatcherGlib::aboutToBlock
QNetworkReplyHttpImpl::downloadProgress
QNetworkReplyHttpImpl::readChannelFinished
QNetworkReplyHttpImpl::finished
QNetworkAccessManager::finished
Видим, что теперь используется QEventDispatcherUNIX
. поэтму будем считать, что proof of concept удался.
© 2014 सत्यं वद धर्मं चर. Все права защищены. Перепубликация материалов без разрешения автора запрещена.
При использовании материалов блога наличие активной не закрытой от индексирования ссылки на источник обязательно.