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 更新操作。在这个示例中,简单地打印接收到的数据和线程信息。
相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner12 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner13 天前
DicomViewer (目录调整) 2
qt
xcyxiner13 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00615 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术15 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript