Qt多线程QThreads保持TCP连接并被重用

时间:2015-04-30 19:03:42

标签: c++ multithreading qt qthread

我真的不确定如何解决这个问题,所以我先解释一下。 我需要运行一些线程,每个线程通过TCPSocket连接到某个应用程序,到目前为止没问题。该应用程序非常耗时,这就是为什么我希望它在多个线程上并行运行并且每个线程都与它进行通信。一旦计算完成,我想将结果发送到收集结果的另一个线程。 因此我写了一个工人阶级:

class Worker : public QObject {
    Q_OBJECT

public:
    Worker();
    Worker(int port);
    ~Worker();
    QTcpSocket* sock;
    void insert();

public slots:
    void connect();
    void process(const int &id, const QString &param, const int &arity);

signals:
    void ready();
    void finished(const int &id, const int &consistent, const QString &result);
    void error(QString err);
};

现在superThread应该处理一个巨大的文件并需要将它传播到线程周围,然后接收并处理结果。到目前为止,我的方法是另一个在main()中连接的superThread,如下所示:

QThread* superThread = new QThread();
supWorker* super = new supWorker();
for (int i = 0; i < nrWorkers; i++){
    Worker* worker = new Worker(portRange+i);
    QThread* workerThread = new QThread();
    QThread::connect(workerThread, SIGNAL(started()), worker, SLOT(connect()));
    worker->moveToThread(workerThread);
    workerThread->start();
    QThread::connect(super, SIGNAL(process(int, QString, int)), worker, SLOT(process(int,QString,int)));
    QThread::connect(worker, SIGNAL(finished(int, int, QString)), super, SLOT(handleResult(int, int, QString)));
}

这种方式的问题显然是我只能将SIGNAL发送到所有连接的线程。我想要superThread做的只是向一个线程发送参数。我不知道如何处理连接,以便只有一个工作线程接收它?

任何帮助或建筑创意都非常感谢,提前感谢。

1 个答案:

答案 0 :(得分:2)

不确定我的想法是否100%正确,但为什么不将工作线程数组传递给超级线程,保留一个索引来表示当前活动线程的索引,只将信号连接到那个,调度需要时发出信号,等待完成,断开信号,推进索引并重复?如果将序列化信号分派给线程是您真正想要的,那么这可能会有效。

修改

好的,我实际上是在努力制作一个基于Qt的示例,该示例实现了所需的工作流程并将其放在github上。

#pragma once

#include <QThread>
#include <QApplication>
#include <QMetaType>
#include <QTimer>

#include <vector>
#include <memory>
#include <cstdio>
#include <algorithm>

struct Work
{
    int m_work;
};

struct Result
{
    int m_result;
    int m_workerIndex;
};

Q_DECLARE_METATYPE(Work);
Q_DECLARE_METATYPE(Result);

class Worker : public QThread
{
    Q_OBJECT

public:
    Worker(int workerIndex) : m_workerIndex(workerIndex)
    {
        moveToThread(this);
        connect(this, SIGNAL(WorkReceived(Work)), SLOT(DoWork(Work)));
        printf("[%d]: worker %d initialized\n", reinterpret_cast<int>(currentThreadId()), workerIndex);
    }

    void DispatchWork(Work work)
    {
        emit WorkReceived(work);
    }

public slots:
    void DoWork(Work work)
    {
        printf("[%d]: worker %d received work %d\n", reinterpret_cast<int>(currentThreadId()), m_workerIndex, work.m_work);
        msleep(100);
        Result result = { work.m_work * 2, m_workerIndex };
        emit WorkDone(result);
    }

signals:
    void WorkReceived(Work work);
    void WorkDone(Result result);

private:
    int m_workerIndex;
};

class Master : public QObject
{
    Q_OBJECT

public:
    Master(int workerCount) : m_activeWorker(0), m_workerCount(workerCount)
    {
        printf("[%d]: creating master thread\n", reinterpret_cast<int>(QThread::currentThreadId()));
    }
    ~Master()
    {
        std::for_each(m_workers.begin(), m_workers.end(), [](std::unique_ptr<Worker>& worker)
        {
            worker->quit();
            worker->wait();
        });
    }


public slots:
    void Initialize()
    {
        printf("[%d]: initializing master thread\n", reinterpret_cast<int>(QThread::currentThreadId()));
        for (int workerIndex = 0; workerIndex < m_workerCount; ++workerIndex)
        {
            auto worker = new Worker(workerIndex);
            m_workers.push_back(std::move(std::unique_ptr<Worker>(worker)));
            connect(worker, SIGNAL(WorkDone(Result)), SLOT(WorkDone(Result)));
            worker->start();
        }
        m_timer = new QTimer();
        m_timer->setInterval(500);
        connect(m_timer, SIGNAL(timeout()), SLOT(GenerateWork()));
        m_timer->start();
    }
    void GenerateWork()
    {
        Work work = { m_activeWorker };
        printf("[%d]: dispatching work %d to worker %d\n", reinterpret_cast<int>(QThread::currentThreadId()), work.m_work, m_activeWorker);
        m_workers[m_activeWorker]->DispatchWork(work);
        m_activeWorker = ++m_activeWorker % m_workers.size();
    }
    void WorkDone(Result result)
    {
        printf("[%d]: received result %d from worker %d\n", reinterpret_cast<int>(QThread::currentThreadId()), result.m_result, result.m_workerIndex);
    }
    void Terminate()
    {
        m_timer->stop();
        delete m_timer;
    }

private:
    int m_workerCount;
    std::vector<std::unique_ptr<Worker>> m_workers;
    int m_activeWorker;
    QTimer* m_timer;
};

QtThreadExample.cpp:

#include "QtThreadExample.hpp"
#include <QTimer>

int main(int argc, char** argv)
{
    qRegisterMetaType<Work>("Work");
    qRegisterMetaType<Result>("Result");
    QApplication application(argc, argv);
    QThread masterThread;
    Master master(5);
    master.moveToThread(&masterThread);
    master.connect(&masterThread, SIGNAL(started()), SLOT(Initialize()));
    master.connect(&masterThread, SIGNAL(terminated()), SLOT(Terminate()));
    masterThread.start();
    // Set a timer to terminate the program after 10 seconds
    QTimer::singleShot(10 * 1000, &application, SLOT(quit()));
    application.exec();
    masterThread.quit();
    masterThread.wait();
    printf("[%d]: master thread has finished\n", reinterpret_cast<int>(QThread::currentThreadId()));
    return 0;
}

通常,解决方案实际上不是从主线程本身发出信号,而是从工作线程发出信号 - 这样您就可以获得每个线程的唯一信号,并且可以在事件循环中异步处理工作。然后发回一个线程完成的信号。样本可以而且应该根据您的需要进行重构,但一般来说,它会在Qt中使用索引和信令线程逐一展示生产者/消费者模式。我正在使用通用计时器在主线程(Master::m_timer)中生成工作 - 我想在您的情况下,您将使用来自套接字,文件或其他内容的信号生成工作。然后我在一个活动的工作线程上调用一个方法,该方法向工作线程的事件循环发出一个信号,以开始执行工作,然后发出有关完成的信号。这是一般性描述,看一下样本,试一试,如果您有任何跟进问题,请告诉我。

我想如果你使用Qt对象,这个工作相当不错,但是在传统意义上的消费者/生产者模式中,信号/插槽实际上会让生活变得更加艰难,标准的C ++ 11管道带有{{{ 1}}和一个主线程调用condition_variable::notify_one()并且工作线程只是等待条件变量会更容易,但是Qt对所有I / O东西都有很好的包装。所以试试这个并决定。

下面是示例的示例输出,我猜线程日志记录表明实现了所需的效果: enter image description here

还有一点需要注意,因为std::condition_variable本身就运行一个事件循环,如果你没有GUI,你实际上可以让你所有的I / O对象和主类都存在于主线程中并从那里发出信号,从而消除了对单独主线程的需求。当然,如果你有一个GUI,你不会想用这些东西加重它。