使用QThreadPool处理多个连接

时间:2017-08-10 15:27:54

标签: c++ multithreading qt sockets threadpool

考虑一种情况,您需要与设备保持256个tcp连接,仅用于偶尔发送命令。我想并行执行此操作(它需要阻塞直到它得到响应),我正在尝试使用QThreadPool用于此目的,但我有一些疑问,如果可能的话。

我尝试使用QRunnable,但我不确定套接字在线程之间的行为方式(套接字只应在创建它们的线程中使用?)

我也担心这个解决方案的效率,如果有人可以提出一些替代方案,不一定使用QT,我会很高兴。

下面我发布了一些代码片段。

class Task : public QRunnable {

    Task(){
        //creating TaskSubclass instance and socket in it
    }

private:
    TaskSubclass               *sub;

    void run() override {
        //some debug info and variable setting...
        sub->doSomething( args );
        return;
    }
};

class TaskSubclass {
    Socket         *sock;           // socket instance
    //...
    void doSomething( args )
    {
        //writing to socket here
    }
}

class MainProgram : public QObject{
    Q_OBJECT
private:
    QThreadPool *pool;
    Task *tasks;

public:
    MainProgram(){
        pool = new QThreadPool(this);
        //create tasks here
    }

    void run(){
        //decide which task to start
        pool->start(tasks[i]);
    }
};

3 个答案:

答案 0 :(得分:0)

我最喜欢的解决方案是使用select()多路复用您的套接字。这样你就不需要创建额外的线程了,这是一种“非常POSIX”的方式。

例如,请参阅本教程:

http://www.binarytides.com/multiple-socket-connections-fdset-select-linux/

或相关问题:

Using select(..) on client

答案 1 :(得分:0)

正如OMD_AT已经指出最好的解决方案是使用Select()并让内核为你完成工作: - )

这里有一个Async方法和Syncron多线程方法的例子。

在此示例中,我们创建了10个与google webservice的连接并向服务器发出简单的get请求,我们会测量每种方法中所有连接从Google服务器接收响应所需的时间。

请注意您应该使用更快的网络服务器进行真正的测试,例如本地主机,因为网络延迟会对结果产生很大影响。

#include <QCoreApplication>
#include <QTcpSocket>
#include <QtConcurrent/QtConcurrentRun>
#include <QElapsedTimer>
#include <QAtomicInt>

class Task : public QRunnable
{
    public:
        Task() : QRunnable() {}
        static QAtomicInt counter;
        static QElapsedTimer timer;
        virtual void run() override
        {
            QTcpSocket* socket = new QTcpSocket();
            socket->connectToHost("www.google.com", 80);
            socket->write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n");
            socket->waitForReadyRead();
            if(!--counter) {
                 qDebug("Multiple Threads elapsed: %lld nanoseconds", timer.nsecsElapsed());
            }
        }
};

QAtomicInt Task::counter;
QElapsedTimer Task::timer;

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    // init
    int connections = 10;
    Task::counter = connections;
    QElapsedTimer timer;

    /// Async via One Thread (Select)

    // handle the data
    auto dataHandler = [&timer,&connections](QByteArray data) {
        Q_UNUSED(data);
        if(!--connections) qDebug("  Single Threads elapsed: %lld nanoseconds", timer.nsecsElapsed());
    };

    // create 10 connection to google.com and send an http get request
    timer.start();
    for(int i = 0; i < connections; i++) {
        QTcpSocket* socket = new QTcpSocket();
        socket->connectToHost("www.google.com", 80);
        socket->write("GET / HTTP/1.1\r\nHost: www.google.com\r\n\r\n");
        QObject::connect(socket, &QTcpSocket::readyRead, [dataHandler,socket]() {
            dataHandler(socket->readAll());
        });
    }


   /// Async via Multiple Threads

    Task::timer.start();
    for(int i = 0; i < connections; i++) {
        QThreadPool::globalInstance()->start(new Task());
    }

    return app.exec();
}

打印:

Multiple Threads elapsed: 62324598 nanoseconds
  Single Threads elapsed: 63613967 nanoseconds

答案 2 :(得分:0)

虽然答案已被接受,但我想与大家分享一下

我从您的问题中理解的内容: 有256个当前有效的连接,有时您会发送请求(&#34;命令&#34;当您命名时它)其中一个并等待响应。同时,你想让这个过程多线程,虽然你说&#34;它需要阻塞直到它得到响应&#34;,我假设你暗示阻塞了一个处理请求 - 响应的线程过程,但不是主线程。

如果我确实理解了这个问题,我建议使用Qt:

#include <functional>

#include <QObject>          // need to add "QT += core" in .pro
#include <QTcpSocket>       // QT += network
#include <QtConcurrent>     // QT += concurrent 
#include <QFuture>           
#include <QFutureWatcher>

class CommandSender : public QObject
{
public:
    // Sends a command via connection and blocks 
    // until the response arrives or timeout occurs
    // then passes the response to a handler
    // when the handler is done - unblocks
    void SendCommand(
        QTcpSocket* connection,
        const Command& command,
        void(*responseHandler)(Response&&))
    {
        const int timeout = 1000;   // milliseconds, set it to -1 if you want no timeouts 

        // Sending a command (blocking)
        connection.write(command.ToByteArray());    // Look QByteArray for more details
        if (connection.waitForBytesWritten(timeout) {
            qDebug() << connection.errorString() << endl;
            emit error(connection);
            return;
        }

        // Waiting for a response (blocking)
        QDataStream in{ connection, QIODevice::ReadOnly };
        QString message;
        do {
            if (!connection.waitForReadyRead(timeout)) {
                qDebug() << connection.errorString() << endl;
                emit error(connection);
                return;
            }
            in.startTransaction();
            in >> message;
        } while (!in.commitTransaction());

        responseHandler(Response{ message }); // Translate message to a response and handle it
    }

    // Non-blocking version of SendCommand
    void SendCommandAsync(
        QTcpSocket* connection,
        const Command& command,
        void(*responseHandler) (Response&&))
    {
        QFutureWatcher<void>* watcher = new QFutureWatcher<void>{ this };
        connect(watcher, &QFutureWatcher<void>::finished, [connection, watcher] ()
        {
           emit done(connection);
           watcher->deleteLater();
        });

        // Does not block,
        // emits "done" when finished
        QFuture<void> future
            = QtConcurrent::run(this, &CommandSender::SendCommand, connection, command, responseHandler);
        watcher->setFuture(future);
    }

signals:
    void done(QTcpSocket* connection);
    void error(QTcpSocket* connection);
}

现在,您可以使用从线程池中获取的单独线程向套接字发送命令:引擎盖QtConcurrent::run()使用Qt为您提供的QThreadPool的全局实例。该线程阻塞,直到它得到响应,然后用responseHandler处理它。同时,管理所有命令和套接字的主线程保持畅通无阻。只需捕获done()信号,该信号表示已收到并成功处理了响应。

有一点需要注意:异步版只有在线程池中有空闲线程时才会发送请求,否则会等待它。当然,这是任何线程池的行为(这正是这种模式的重点),但不要忘记这一点。

此外,我正在编写没有Qt的代码,因此可能包含一些错误。

编辑:事实证明,这不是线程安全的,因为套接字在Qt中不可重入。

您可以做的是将互斥锁与套接字关联,并在每次执行其功能时将其锁定。这可以轻松地创建QTcpSocket类的包装器。如果我错了,请纠正我。