Quantcast
Channel: सत्यं वद धर्मं चर »потоки
Viewing all articles
Browse latest Browse all 2

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

$
0
0

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

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

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

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

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

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

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

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

/* Инициализация */
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() имеет смысл проверять, имеются ли запросы на соединение.
Решение в лоб:

#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:

// 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);
}

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

#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;
};

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

© 2014 सत्यं वद धर्मं चर. Все права защищены. Перепубликация материалов без разрешения автора запрещена.

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


Viewing all articles
Browse latest Browse all 2

Latest Images

Trending Articles





Latest Images