Qt 中的多线程管理方法详解及示例

在 Qt 中进行多线程编程有多种方式,每种方式都有其独特的特点和适用场景。

1. 重写 QThreadrun 方法

特点

  • 必须继承 QThread 类,并重写 run 方法。
  • 线程启动时会自动调用 run 方法中的代码。
  • 适用于需要自定义复杂线程逻辑的情况。

使用场景: 适合需要自定义线程行为、复杂的连续性操作,如图像处理、复杂计算。

示例:图像处理线程

cpp 复制代码
#include <QThread>
#include <QDebug>

class ImageProcessingThread : public QThread {
    Q_OBJECT

protected:
    void run() override {
        // 假设进行一些复杂的图像处理操作
        for (int i = 0; i < 100; ++i) {
            qDebug() << "Processing image:" << i;
            QThread::sleep(1);
        }
    }
};

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

    ImageProcessingThread thread;
    thread.start();
    thread.wait(); // 等待线程完成

    return app.exec();
}

2. 使用 moveToThread 方法

特点

  • 常与 QObject 类的子类一起使用。
  • 适用于需要在另一个线程中执行槽函数或处理事件的对象。

使用场景: 适用于需要将对象的槽函数或事件处理放到另一个线程中执行,如网络请求处理、定时任务。

示例:网络请求处理

cpp 复制代码
#include <QCoreApplication>
#include <QTcpServer>
#include <QTcpSocket>
#include <QThread>
#include <QDebug>

// 客户端处理类
class ClientHandler : public QObject {
    Q_OBJECT

public:
    // 构造函数,传入 socket 描述符
    ClientHandler(qintptr socketDescriptor, QObject *parent = nullptr)
        : QObject(parent), socketDescriptor(socketDescriptor) {}

public slots:
    // 处理客户端请求
    void handleClient() {
        QTcpSocket socket;
        if (!socket.setSocketDescriptor(socketDescriptor)) {
            qDebug() << "设置 socket 描述符失败";
            return;
        }

        qDebug() << "客户端已连接: " << socketDescriptor;
        socket.write("来自服务器的问候\n");
        socket.waitForBytesWritten();

        while (socket.waitForReadyRead()) {
            QByteArray data = socket.readAll();
            qDebug() << "收到客户端消息:" << data;
            socket.write("回显: " + data);
            socket.waitForBytesWritten();
        }

        qDebug() << "客户端已断开: " << socketDescriptor;
        socket.disconnectFromHost();
    }

private:
    qintptr socketDescriptor; // 保存客户端 socket 描述符
};

// 自定义 TCP 服务器类
class MyTcpServer : public QTcpServer {
    Q_OBJECT

protected:
    // 重写 incomingConnection 函数来处理新的连接
    void incomingConnection(qintptr socketDescriptor) override {
        QThread *thread = new QThread; // 为每个连接创建一个新线程
        ClientHandler *handler = new ClientHandler(socketDescriptor);
        handler.moveToThread(thread);

        connect(thread, &QThread::started, handler, &ClientHandler::handleClient);
        connect(handler, &ClientHandler::destroyed, thread, &QThread::quit);
        connect(thread, &QThread::finished, thread, &QThread::deleteLater);

        thread.start();
    }
};

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

    MyTcpServer server;
    if (!server.listen(QHostAddress::Any, 12345)) {
        qDebug() << "服务器启动失败:" << server.errorString();
        return 1;
    }

    qDebug() << "服务器已启动,端口:" << server.serverPort();
    return app.exec();
}

#include "main.moc"

客户端代码(Client)

cpp 复制代码
#include <QCoreApplication>
#include <QTcpSocket>
#include <QDebug>

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

    QTcpSocket socket;
    socket.connectToHost("127.0.0.1", 12345);

    if (!socket.waitForConnected(3000)) {
        qDebug() << "连接服务器失败:" << socket.errorString();
        return 1;
    }

    qDebug() << "已连接到服务器";
    socket.write("来自客户端的问候\n");
    socket.waitForBytesWritten();

    if (socket.waitForReadyRead(3000)) {
        QByteArray data = socket.readAll();
        qDebug() << "收到服务器消息:" << data;
    }

    socket.disconnectFromHost();
    return app.exec();
}

3. 使用 QtConcurrent

特点

  • 不需要显式创建和管理线程。
  • 适合用于并行处理大批量数据或多任务处理。

使用场景: 适用于并行处理容器数据、并行函数执行,如批量数据处理、并行计算。

示例:并行处理数据

我们将定义一个简单的函数 processItem 来处理每个元素。然后使用 QtConcurrent::map 并行处理 QList 容器中的每个元素。

cpp 复制代码
#include <QCoreApplication>
#include <QtConcurrent/QtConcurrent>
#include <QDebug>
#include <QList>
#include <QFuture>
#include <QFutureWatcher>

// 定义处理每个元素的函数
void processItem(int &item) {
    item *= 2; // 示例操作:将每个元素乘以2
    QThread::sleep(1); // 模拟耗时操作
    qDebug() << "处理项目:" << item << " 线程:" << QThread::currentThread();
}

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

    // 创建一个包含初始数据的列表
    QList<int> list = {1, 2, 3, 4, 5};
    
    // 使用 QtConcurrent::map 并行处理列表中的每个元素
    QFuture<void> future = QtConcurrent::map(list, processItem);

    // QFutureWatcher 用于监视 QFuture 的完成状态
    QFutureWatcher<void> watcher;
    QObject::connect(&watcher, &QFutureWatcher<void>::finished, [](){
        qDebug() << "所有项目已处理完毕";
        QCoreApplication::quit();
    });

    watcher.setFuture(future); // 将 QFutureWatcher 绑定到 QFuture

    return app.exec();
}

4. 使用 QThreadPoolQRunnable

特点

  • 提供线程池管理,适合重复使用的任务管理。
  • 提供了任务队列,自动管理线程的创建和销毁。

使用场景: 适用于任务队列、多任务执行,如后台任务处理、批量任务执行。

示例:后台任务处理

cpp 复制代码
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <QDebug>

// 定义一个简单的任务类,继承自 QRunnable
class MyTask : public QRunnable {
public:
    void run() override {
        qDebug() << "任务执行在线程:" << QThread::currentThread();
        // 示例任务操作,这里可以是任何需要在后台执行的任务
        for (int i = 0; i < 5; ++i) {
            qDebug() << "任务" << taskId << "执行步骤" << i;
            QThread::sleep(1); // 模拟耗时操作
        }
    }

    void setTaskId(int id) {
        taskId = id;
    }

private:
    int taskId;
};

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

    QThreadPool threadPool;
    threadPool.setMaxThreadCount(5); // 设置线程池中最大线程数

    // 创建并提交多个任务到线程池
    for (int i = 0; i < 3; ++i) {
        MyTask *task = new MyTask();
        task->setTaskId(i + 1); // 设置任务ID,用于区分任务
        threadPool.start(task); // 启动任务,线程池会自动分配线程执行任务
    }

    // 等待所有任务完成
    threadPool.waitForDone();

    qDebug() << "所有任务已完成";

    return app.exec();
}

例如处理文件、网络请求等复杂任务。

cpp 复制代码
#include <QCoreApplication>
#include <QRunnable>
#include <QThreadPool>
#include <QDebug>
#include <QFile>
#include <QTcpSocket>

// 定义一个处理文件的任务
class FileTask : public QRunnable {
public:
    FileTask(const QString &fileName) : m_fileName(fileName) {}

    void run() override {
        qDebug() << "处理文件任务:" << m_fileName << "在线程:" << QThread::currentThread();

        QFile file(m_fileName);
        if (file.open(QIODevice::ReadOnly)) {
            qDebug() << "文件内容:" << file.readAll();
            file.close();
        } else {
            qDebug() << "无法打开文件:" << m_fileName;
        }
    }

private:
    QString m_fileName;
};

// 定义一个处理网络请求的任务
class NetworkTask : public QRunnable {
public:
    NetworkTask(const QString &hostName, quint16 port) : m_hostName(hostName), m_port(port) {}

    void run() override {
        qDebug() << "处理网络请求任务:" << m_hostName << ":" << m_port << "在线程:" << QThread::currentThread();

        QTcpSocket socket;
        socket.connectToHost(m_hostName, m_port);
        if (socket.waitForConnected()) {
            socket.write("GET / HTTP/1.0\r\n\r\n");
            if (socket.waitForReadyRead()) {
                qDebug() << "服务器响应:" << socket.readAll();
            }
            socket.disconnectFromHost();
        } else {
            qDebug() << "无法连接到服务器:" << m_hostName << ":" << m_port;
        }
    }

private:
    QString m_hostName;
    quint16 m_port;
};

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

    QThreadPool threadPool;
    threadPool.setMaxThreadCount(3); // 设置线程池中最大线程数

    // 创建并提交文件处理任务
    FileTask *fileTask = new FileTask("example.txt");
    threadPool.start(fileTask);

    // 创建并提交多个网络请求任务
    NetworkTask *task1 = new NetworkTask("www.example.com", 80);
    NetworkTask *task2 = new NetworkTask("api.example.com", 443);

    threadPool.start(task1);
    threadPool.start(task2);

    // 等待所有任务完成
    threadPool.waitForDone();

    qDebug() << "所有任务已完成";

    return app.exec();
}
  • 这个扩展示例展示了如何创建并提交不同类型的任务到线程池中,并且每个任务在后台线程中独立运行。
  • FileTask 处理文件任务,读取并输出文件内容。
  • NetworkTask 处理网络请求任务,连接到指定主机和端口,并输出服务器响应。

5. 使用信号和槽进行多线程操作

特点

  • 简化线程间的通信。
  • 自动处理线程间的数据传输。

使用场景: 适用于跨线程通信、线程间数据交换,如定时任务、多线程数据更新。

示例:多线程数据更新

cpp 复制代码
#include <QCoreApplication>
#include <QObject>
#include <QTimer>
#include <QDebug>
#include <QThread>

// 定义一个 Worker 类,负责执行定时任务并发送数据更新信号
class Worker : public QObject {
    Q_OBJECT

public:
    Worker() {
        timer.setInterval(1000); // 设置定时器间隔为 1 秒
        connect(&timer, &QTimer::timeout, this, &Worker::doWork); // 定时器超时信号连接到槽函数
        timer.start(); // 启动定时器
    }

signals:
    // 定义一个信号,用于发送数据更新信号
    void dataUpdated(const QString &data);

private slots:
    // 槽函数,模拟定时任务执行
    void doWork() {
        // 模拟生成数据
        QString newData = QString("数据更新时间:%1").arg(QDateTime::currentDateTime().toString());
        qDebug() << "Worker 发送数据更新信号:" << newData;

        // 发送数据更新信号到主线程
        emit dataUpdated(newData);
    }

private:
    QTimer timer; // 定时器对象
};

// 定义一个 Controller 类,作为主线程的控制器,处理数据更新信号
class Controller : public QObject {
    Q_OBJECT

public:
    Controller() {
        // 连接 Worker 的数据更新信号到处理函数
        connect(&worker, &Worker::dataUpdated, this, &Controller::handleDataUpdated);
    }

public slots:
    // 槽函数,处理数据更新信号
    void handleDataUpdated(const QString &data) {
        qDebug() << "Controller 接收到数据更新:" << data << " 在线程:" << QThread::currentThread();
        // 在这里进行主线程的数据更新操作,例如更新 UI
        emit updateUI(data); // 发送信号到主线程更新 UI
    }

signals:
    // 定义一个信号,用于更新主线程的 UI
    void updateUI(const QString &data);

private:
    Worker worker; // Worker 对象,负责定时任务和数据生成
};

// 定义一个 UI 类,负责接收 Controller 发送的更新信号并更新 UI
class UI : public QObject {
    Q_OBJECT

public:
    UI() {
        // 连接 Controller 的更新 UI 信号到处理函数
        connect(&controller, &Controller::updateUI, this, &UI::updateUI);
    }

public slots:
    // 槽函数,更新 UI
    void updateUI(const QString &data) {
        qDebug() << "UI 更新数据:" << data << " 在线程:" << QThread::currentThread();
        // 在这里进行实际的 UI 更新操作,例如在界面上显示数据
    }

private:
    Controller controller; // Controller 对象,负责数据处理和信号传递
};

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

    UI ui; // 创建 UI 对象,负责接收和显示数据

    return app.exec();
}

#include "main.moc"

代码解释

  • Worker 类:负责定时任务的执行。在构造函数中设置定时器 timer 的间隔为 1 秒,并将定时器的 timeout 信号连接到 doWork 槽函数。每次定时器超时时,会生成一个带有当前时间的数据,并发送 dataUpdated 信号到主线程。
  • Controller 类:作为主线程的控制器,连接 WorkerdataUpdated 信号到 handleDataUpdated 槽函数。当接收到 dataUpdated 信号时,会在槽函数中处理数据更新操作,并通过 updateUI 信号发送到主线程。
  • UI 类:负责接收 Controller 发送的 updateUI 信号,并在 updateUI 槽函数中实现实际的 UI 更新操作。在这个示例中,简单地打印接收到的数据和线程信息。
相关推荐
tyler_download11 分钟前
golang 实现比特币内核:实现基于椭圆曲线的数字签名和验证
开发语言·数据库·golang
小小小~11 分钟前
qt5将程序打包并使用
开发语言·qt
hlsd#11 分钟前
go mod 依赖管理
开发语言·后端·golang
小春学渗透13 分钟前
Day107:代码审计-PHP模型开发篇&MVC层&RCE执行&文件对比法&1day分析&0day验证
开发语言·安全·web安全·php·mvc
杜杜的man16 分钟前
【go从零单排】迭代器(Iterators)
开发语言·算法·golang
亦世凡华、16 分钟前
【启程Golang之旅】从零开始构建可扩展的微服务架构
开发语言·经验分享·后端·golang
测试界的酸菜鱼30 分钟前
C# NUnit 框架:高效使用指南
开发语言·c#·log4j
GDAL30 分钟前
lua入门教程 :模块和包
开发语言·junit·lua
李老头探索32 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
CSXB9933 分钟前
三十四、Python基础语法(文件操作-上)
开发语言·python·功能测试·测试工具