qt中的多线程

阅读数:352 评论数:0

跳转到新版页面

分类

C/C++

正文

一、基本概念

在 Qt 中,QThread 类用于表示一个线程。你可以通过继承 QThread 或者将对象移动到线程来实现多线程编程。推荐的方法是将对象移动到线程,因为它更符合 Qt 的信号槽机制。

二、创建和启动线程

1、继承QThread

这种方法通过继承 QThread 并重写 run 方法来实现线程任务。

#include <QThread>
#include <QDebug>

class MyThread : public QThread {
    Q_OBJECT

protected:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Thread running" << i;
            QThread::sleep(1);
        }
    }
};

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

    MyThread thread;
    thread.start();
    thread.wait(); // 等待线程结束

    return app.exec();
}

2、将对象移动到线程

这种方法更推荐,因为它更符合 Qt 的信号槽机制,并且更容易管理对象的生命周期。

#include <QThread>
#include <QObject>
#include <QDebug>

class Worker : public QObject {
    Q_OBJECT

public slots:
    void doWork() {
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Worker running" << i;
            QThread::sleep(1);
        }
    }
};

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

    QThread workerThread;
    Worker worker;

    worker.moveToThread(&workerThread);
    QObject::connect(&workerThread, &QThread::started, &worker, &Worker::doWork);
    QObject::connect(&workerThread, &QThread::finished, &worker, &QObject::deleteLater);

    workerThread.start();
    workerThread.wait(); // 等待线程结束

    return app.exec();
}

三、线程间通信

Qt 的信号槽机制可以跨线程工作。你可以使用信号槽在线程之间传递数据。

#include <QThread>
#include <QObject>
#include <QDebug>

class Worker : public QObject {
    Q_OBJECT

public slots:
    void doWork() {
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Worker running" << i;
            emit progress(i);
            QThread::sleep(1);
        }
        emit finished();
    }

signals:
    void progress(int value);
    void finished();
};

class Controller : public QObject {
    Q_OBJECT

public slots:
    void onProgress(int value) {
        qDebug() << "Progress:" << value;
    }

    void onFinished() {
        qDebug() << "Task finished!";
    }
};

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

    QThread workerThread;
    Worker worker;
    Controller controller;

    worker.moveToThread(&workerThread);
    QObject::connect(&workerThread, &QThread::started, &worker, &Worker::doWork);
    QObject::connect(&worker, &Worker::progress, &controller, &Controller::onProgress);
    QObject::connect(&worker, &Worker::finished, &controller, &Controller::onFinished);
    QObject::connect(&worker, &Worker::finished, &workerThread, &QThread::quit);
    QObject::connect(&workerThread, &QThread::finished, &worker, &QObject::deleteLater);

    workerThread.start();

    return app.exec();
}

1、信号与槽的连接类型

qt支持以下几种连接类型:

(1)自动连接(Qt::AutoConnection,默认)

根据信号发送者和接收者是否属于同一个线程,自动选择连接方式:

  • 同线程:采用直接连接(Qt::DirectConnection)。
  • 跨线程:采用队列连接(Qt::QueuedConnection)。
(2)直接连接(Qt::DirectConnection)

 

  • 信号直接调用槽函数。
  • 槽函数在信号发送者的线程中执行。
  • 适用于同一线程中对象间的通信,或者线程间信号发射者明确知道槽函数线程上下文的情况。
(3)队列连接(Qt::QueuedConnection)

 

  • 信号被放入接收者所在线程的事件队列。
  • 槽函数在接收者对象所属线程中执行。
  • 需要确保接收者线程有一个运行的事件循环。
(4)阻塞队列连接(Qt::BlockingQueuedConnection)

 

  • 类似队列连接,但发送信号的线程会阻塞,直到槽函数执行完成。
  • 发送线程必须与接收线程不同,否则会导致死锁。
  • 使用场景:跨线程同步操作。
(5)唯一连接(Qt::UniqueConnection)

 

  • 确保同一信号与槽的连接只存在一次。
  • 通常与其他连接类型组合使用,例如:Qt::AutoConnection | Qt::UniqueConnection

2、强制让槽函数在主线程中执行

(1)QMetaObject::invokeMethod

QMetaObject::invokeMethod 可以强制在指定的线程中执行函数。要确保在主线程中执行,可以如下操作:

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QObject>

class Worker : public QObject {
    Q_OBJECT
public:
    void doWork() {
        emit workDone();
    }

signals:
    void workDone();
};

class MainObject : public QObject {
    Q_OBJECT
public slots:
    void onWorkDone() {
        qDebug() << "Slot executed in thread:" << QThread::currentThread();
    }

    void invokeSlotInMainThread() {
        // 强制在主线程执行槽函数
        QMetaObject::invokeMethod(this, "onWorkDone", Qt::QueuedConnection);
    }
};

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

    Worker worker;
    MainObject mainObject;

    // 验证主线程
    qDebug() << "Main thread:" << QThread::currentThread();

    // 将信号连接到一个强制调用的方法
    QObject::connect(&worker, &Worker::workDone, &mainObject, &MainObject::invokeSlotInMainThread, Qt::QueuedConnection);

    // 模拟在工作线程中发射信号
    QThread workerThread;
    worker.moveToThread(&workerThread);

    QObject::connect(&workerThread, &QThread::started, &worker, &Worker::doWork);
    QObject::connect(&workerThread, &QThread::finished, &workerThread, &QThread::deleteLater);

    workerThread.start(); // 启动线程

    return app.exec();
}
(2)通过QCoreApplication::postEvent自定义事件

可以使用 QCoreApplication::postEvent 将任务封装成事件,发送到主线程的事件循环中执行。

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QObject>
#include <QEvent>

class CustomEvent : public QEvent {
public:
    static constexpr QEvent::Type EventType = static_cast<QEvent::Type>(QEvent::User + 1);

    CustomEvent(std::function<void()> func) : QEvent(EventType), m_func(std::move(func)) {}

    void execute() { m_func(); }

private:
    std::function<void()> m_func;
};

class MainObject : public QObject {
    Q_OBJECT
protected:
    void customEvent(QEvent *event) override {
        if (event->type() == CustomEvent::EventType) {
            auto *customEvent = static_cast<CustomEvent *>(event);
            customEvent->execute();
        }
    }

public slots:
    void handleTask() {
        qDebug() << "Task executed in thread:" << QThread::currentThread();
    }
};

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

    MainObject mainObject;

    // 模拟从子线程发送任务
    QThread workerThread;
    QObject::connect(&workerThread, &QThread::started, [&]() {
        QCoreApplication::postEvent(&mainObject, new CustomEvent([&mainObject]() {
            mainObject.handleTask();
        }));
    });
    QObject::connect(&workerThread, &QThread::finished, &workerThread, &QThread::deleteLater);

    workerThread.start();
    return app.exec();
}
(3)使用全局单例队列和主线程定时器

通过全局任务队列,利用主线程定时器定期拉取任务执行。

#include <QCoreApplication>
#include <QThread>
#include <QDebug>
#include <QObject>
#include <QTimer>
#include <queue>
#include <mutex>
#include <functional>

std::queue<std::function<void()>> taskQueue;
std::mutex queueMutex;

void postTaskToMainThread(std::function<void()> task) {
    std::lock_guard<std::mutex> lock(queueMutex);
    taskQueue.push(task);
}

void processMainThreadTasks() {
    std::lock_guard<std::mutex> lock(queueMutex);
    while (!taskQueue.empty()) {
        auto task = taskQueue.front();
        taskQueue.pop();
        task();
    }
}

class Worker : public QObject {
    Q_OBJECT
public:
    void doWork() {
        postTaskToMainThread([]() {
            qDebug() << "Task executed in thread:" << QThread::currentThread();
        });
    }
};

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

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, processMainThreadTasks);
    timer.start(100); // 定期处理任务

    Worker worker;
    QThread workerThread;
    worker.moveToThread(&workerThread);

    QObject::connect(&workerThread, &QThread::started, &worker, &Worker::doWork);
    QObject::connect(&workerThread, &QThread::finished, &workerThread, &QThread::deleteLater);

    workerThread.start();
    return app.exec();
}

 

 

 

 

四、线程安全

在多线程编程中,确保线程安全非常重要。Qt 提供了 QMutexQReadWriteLockQSemaphore 等类来实现线程同步。

1、使用QMutex保护共享数据

#include <QThread>
#include <QMutex>
#include <QDebug>

class Counter {
public:
    void increment() {
        QMutexLocker locker(&mutex);
        ++value;
        qDebug() << "Counter value:" << value;
    }

private:
    int value = 0;
    QMutex mutex;
};

class Worker : public QThread {
    Q_OBJECT

public:
    Worker(Counter *counter) : counter(counter) {}

protected:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            counter->increment();
            QThread::sleep(1);
        }
    }

private:
    Counter *counter;
};

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

    Counter counter;
    Worker worker1(&counter);
    Worker worker2(&counter);

    worker1.start();
    worker2.start();

    worker1.wait();
    worker2.wait();

    return app.exec();
}

五、线程池

Qt 提供了 QThreadPoolQRunnable 来管理和执行多个线程任务。线程池可以重复使用线程,从而减少线程创建和销毁的开销。

#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

class Task : public QRunnable {
public:
    void run() override {
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Task running" << i;
            QThread::sleep(1);
        }
    }
};

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

    QThreadPool pool;
    Task *task1 = new Task();
    Task *task2 = new Task();

    pool.start(task1);
    pool.start(task2);

    pool.waitForDone();

    return app.exec();
}

六、QThread::sleep

QThread::sleep() 是一个静态方法,用于让当前线程暂停执行一段时间。它有几个不同的版本,分别用于秒、毫秒和微秒级别的暂停。

1、秒级暂停

QThread::sleep(2); // 暂停当前线程2秒

2、毫秒级暂停

QThread::msleep(500); // 暂停当前线程500毫秒

3、微秒级暂停

QThread::usleep(1000); // 暂停当前线程1000微秒(1毫秒)



相关推荐

在Qt应用程序中定制桌面图标,通常涉及到两个方面: 应用程序图标:这是应用程序在操作系统中显示的图标,例如在Windows的任务栏或MacOS的Dock中。 桌面快捷方式图标:这是用户可以双击

一、基本使用 1、从Qt官方网站下载并安装Qt installer Framework https://download.qt.io/official_releases/qt-installer-fr