Qt, libfcgi и многопоточность

Предотвращение блокирования событий при использовании libfcgi в многопоточном режиме

Для создания приложений на C/C++ есть библиотека libfcgi. Не буду вдаваться в дискуссию, зачем нужны приложения FastCGI на C/C++/подставить нужный язык, когда Python/PHP/Perl/подставить нужное гораздо удобнее. Отмечу лишь, что по работе понадобилось написать FastCGI-приложение на (в основном из-за наличия нескольких высокопроизводительных библиотек, написанных на , но не суть).

Строго говоря, libfcgi, хотя и является официальной библиотекой от создателей протокола, не лучший вариант для поддержки FastCGI — API, предоставляемое библиотекой, сильно ограничено (в плане функциональности) и недостаточно гибко.

Грубо говоря, приложение, использующее libfcgi, может работать в двух режимах:

  1. Последовательная обработка запросов: приложение получило запрос, обработало его, отправило результат, получило следующий запрос. Схема хороша, если обработка запроса тривиальна и/или не занимает много времени. Скорее всего, под большой нагрузкой масштабируется плохо, ибо чем длиннее очередь запросов, тем дольши придётся ждать клиенту.
  2. Многопоточная обработка запросов: приложение создаёт определённое количество потоков, каждый из которых будет обрабатывать свой запрос. При средних нагрузках эта схема работает лучше (если один поток занят, свободный поток займётся выполнением запроса). Но в предельном случае (когда все заняты) имеем те же проблемы, что и в первом варианте. Схему можно немного изменить, создавая по мере необходимости, но всё равно плодить до бесконечности нельзя.

Использование потоков не всегда уместно/хорошо: многопоточные программы, как правило, сложнее для разработки и отладки; кроме того, потоки потребляют системные ресурсы (например, память для стека) и при большом количестве потоков эти издержки могут быть существенными (например, для Linux/IA-32 размер стека для потока по умолчанию составляет 2 мегабайта).

К сожалению, при использовании libfcgi отказаться от потоков очень трудно — дело в том, что библиотека использует блокирующие операции при работе с сокетами.

В общем случае алгоритм работы с libfcgi следующий:

[-]
View Code C
/* Инициализация */
FCGX_Init();
int socket = FCGX_OpenSocket(":9001", 256);

/* ... */

// Собственно работа
while (true) {
    FCGX_Request* req = new FCGX_Request;
    FCGX_InitRequest(req, socket, 0);

    int rc = FCGX_Accept_r(req);
    if (rc >= 0) {
        /* Здесь создаём и запускаем поток, отвечающий за обработку запроса */
    }
}

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

Поэтому перед вызовом FCGX_Accept_r() имеет смысл проверять, имеются ли запросы на соединение.
Решение в лоб:

[-]
View Code C++
#include <QtCore/QCoreApplication>
#include <sys/select.h>

/* ... */

int rc;
do {
    QCoreApplication::processEvents();
    struct timeval tv = { 0, 500 };
    fd_set r;
    FD_ZERO(&r);
    FD_SET(socket, &r);

    rc = ::select(socket+1, &r, 0, 0, &tv);
} while (!rc);

FCGX_Request* req = new FCGX_Request;
FCGX_InitRequest(req, socket, 0);
int sock = FCGX_Accept_r(req);
/* ... */

Но более корректно будет использовать QSocketNotifier:

[-]
View Code C++ (QT)
// core.h
#include <QtCore/QObject>

class QSocketNotifier;

class Core : public QObject {
    Q_OBJECT
public:
    explicit Core(QObject *parent = 0);
    void run(void);

private:
    QSocketNotifier* m_notifier;

private slots:
    void connectionPending(int socket);
};

// core.cpp

Core::Core(QObject *parent) : QObject(parent), m_notifier(0)
{
    FCGX_Init();
    int sock = FCGX_OpenSocket(":9001", 256);
    this->m_notifier = new QSocketNotifier(sock, QSocketNotifier::Read);
    QObject::connect(this->m_notifier, SIGNAL(activated(int)), this, SLOT(connectionPending(int)));
}

void Core::connectionPending(int socket)
{
    QSocketNotifier* notifier = qobject_cast<QSocketNotifier*>(this->sender());
    Q_CHECK_PTR(notifier);

    notifier->setEnabled(false);

    FCGX_Request* req = new FCGX_Request;
    FCGX_InitRequest(req, socket, 0);
    int s = FCGX_Accept_r(req);
    if (s >= 0) {
        /* Здесь создаём и запускаем поток */
    }

    notifier->setEnabled(true);
}

В качестве альтернативы созданию потоков можно предложить использовать очередь запросов.

[-]
View Code C++
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#include <QtCore/QQueue>
#include <QtCore/QWaitCondition>

class Queue {
public:
    void addToQueue(FCGX_Request* r, bool broadcast = false)
    {
        QMutexLocker locker(&this->mx);
        this->queue.enqueue(r);
        if (broadcast) {
            this->cv.wakeAll();
        }
        else {
            this->cv.wakeOne();
        }
    }

    FCGX_Request* getFromQueue(void)
    {
        this->mx.lock();
        while (this->queue.isEmpty()) {
            this->cv.wait(&this->mx);
        }

        FCGX_Request* result = this->queue.dequeue();
        this->mx.unlock();
        return result;
    }
private:
    QMutex mx;
    QWaitCondition cv;
    QQueue<FCGX_Request*> queue;
};

Но это уже совсем другая история.

Автор: ; опубликовано в: Qt; метки: FastCGI, Qt, потоки
8
Янв
2012

RSS Комментарии к статье «Qt, libfcgi и многопоточность» (2)  »

  1. В любом случае использование потоков уменьшает время выполнения запросов, хоть и программно это сложнее организовать, и памяти требуется больше. А время выполнения очень многое значит.

    • Далеко не всегда. Если обработка запросов в основном состоит в операциях ввода/вывода, работа без потоков через неблокирующие сокеты обычно оказывается быстрее.

Пожалуйста, не используйте эту форму для комментирования! Данная форма предназначена исключительно для ботов.

Оставить комментарий к записи «Qt, libfcgi и многопоточность»

Ваш e-mail не будет опубликован. Обязательные поля помечены *

*

Можно использовать следующие HTML-теги и атрибуты: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Оставляя комментарий, вы выражаете своё согласие с Правилами комментирования.

Подписаться, не комментируя

गते गते पारगते पारसंगते बोधि स्वाहा