qt 创建线程的方式有几种

1.概要

2.内容

在 Qt 中,创建线程主要有以下几种方式:

1. 派生于 QThread

这是 Qt 中最常用的线程创建方式。你可以通过继承 QThread 类并重写其 run() 方法来定义线程的执行内容。然后,通过调用 start() 方法来启动线程。

class MyThread : public QThread  
{  
    Q_OBJECT  
  
protected:  
    void run() override  
    {  
        // 在这里执行你的代码  
    }  
};  
  
// 使用  
MyThread *myThread = new MyThread(this);  
myThread->start();

2. 使用 Qt 的 QRunnable 类

QRunnable 是一个轻量级的抽象,用于表示一个可以并行执行的任务。你可以将 QRunnable 对象提交给 QThreadPool 来异步执行。

class MyRunnable : public QRunnable  
{  
public:  
    void run() override  
    {  
        // 在这里执行你的代码  
    }  
};  
  
// 使用  
MyRunnable *myRunnable = new MyRunnable();  
QThreadPool::globalInstance()->start(myRunnable);

3. 使用 Qt 的 moveToThread 方法

这种方法不需要子类化 QThread,只需要子类化一个 QObject。你可以使用 moveToThread() 方法将 QObject 实例移动到一个新的线程中,然后启动该线程。

class Worker : public QObject  
{  
    Q_OBJECT  
  
public slots:  
    void doWork(const QString &parameter)  
    {  
        // 在这里执行你的代码  
    }  
};  
  
// 使用  
QThread workerThread;  
Worker worker;  
workerThread.start();  
worker.moveToThread(&workerThread);  
connect(&workerThread, &QThread::finished, &worker, &QObject::deleteLater);  
// 连接信号和槽来执行工作

4. 使用 QtConcurrent::run()

QtConcurrent::run() 允许你在一个新的线程中执行一个函数。这是一种便捷的方式来运行一个函数而不阻塞主线程。

void myFunction()  
{  
    // 在这里执行你的代码  
}  
  
// 使用  
QtConcurrent::run(myFunction);

5. 使用 C++11 的标准线程库

虽然这不是 Qt 特有的方法,但你可以使用 C++11(或更高版本)的标准线程库来创建和管理线程。例如,使用 std::thread 类来创建线程。这种方式与 Qt 的集成可能不如使用 Qt 自带的线程类那么紧密,但它提供了更多的灵活性和对底层线程的直接控制。

#include <thread>  
  
void myFunction()  
{  
    // 在这里执行你的代码  
}  
  
// 使用  
std::thread myThread(myFunction);  
myThread.join(); // 等待线程完成

总结

以上就是在 Qt 中创建线程的几种主要方式。你可以根据具体的需求和场景选择合适的方法。在选择时,应考虑到任务的性质、执行频率、持续时间以及资源使用情况等因素。同时,务必注意线程同步和通信的问题,以避免数据竞争和其他并发问题。

3.关联知识

1.qt 创建一个线程

在 Qt 中创建一个线程通常涉及几个步骤,包括子类化 QThread,重写其 run() 方法,并在需要时启动线程。下面是一个简单的例子,展示了如何在 Qt 中创建和使用一个线程:

  1. 子类化 QThread
    首先,你需要创建一个继承自 QThread 的类,并重写其 run() 方法。run() 方法将包含在新线程中执行的代码。

  2. 实现 run() 方法
    在 run() 方法中,编写你希望在新线程中执行的代码。

  3. 启动线程
    创建你的 QThread 子类的实例,并调用其 start() 方法来启动线程。

下面是一个简单的示例,演示了这些步骤:

#include <QCoreApplication>  
#include <QThread>  
#include <QDebug>  
  
// 1. 子类化 QThread  
class MyThread : public QThread  
{  
    Q_OBJECT  
  
protected:  
    // 2. 实现 run() 方法  
    void run() override {  
        for (int i = 0; i < 5; ++i) {  
            qDebug() << "Thread running:" << i;  
            QThread::sleep(1); // 使线程休眠一秒  
        }  
    }  
};  
  
int main(int argc, char *argv[])  
{  
    QCoreApplication a(argc, argv);  
  
    // 3. 创建并启动线程  
    MyThread thread;  
    thread.start();  
      
    // 等待线程完成(可选)  
    thread.wait();  
  
    return a.exec();  
}  
  
#include "main.moc" // 如果你使用的是单个文件的方式,需要这一行来生成 MOC 代码

注意事项

  1. 线程同步
    如果多个线程需要访问共享数据,你需要使用适当的同步机制(如互斥锁 QMutex)来避免数据竞争和条件竞争。

  2. 线程间通信
    Qt 提供了信号和槽机制,这是线程间通信的一种有效方式。注意,当向另一个线程的对象发送信号时,槽函数将在接收对象的线程中执行。

  3. QObject 的线程相关性
    QObject 及其子类不是线程安全的,这意味着它们的实例应该只在创建它们的线程中使用。如果需要跨线程使用 QObject,考虑使用 QCoreApplication::postEvent() 或信号槽机制。

  4. 管理线程生命周期
    确保在程序退出前适当地管理和终止线程。如果线程在应用程序退出时仍在运行,它可能会导致未定义行为或程序崩溃。

通过遵循这些步骤和注意事项,你可以在 Qt 应用程序中安全和有效地使用多线程。

2.使用 Qt 的 moveToThread 方法 

moveToThread 是 Qt 中用于将对象移动到另一个线程的方法。这在需要执行耗时操作而不阻塞主线程(通常是 GUI 线程)时非常有用。下面是一个简单的例子,演示如何使用 moveToThread 方法将工作负载移到另一个线程。

示例代码

假设我们有一个耗时的任务,我们希望在一个单独的线程中执行它。我们可以按照以下步骤来实现:

  1. 创建一个继承自 QObject 的工作类,并在其中定义耗时任务。
  2. 创建一个 QThread 对象。
  3. 将工作对象移动到新线程。
  4. 在新线程中启动耗时任务。

以下是完整的示例代码:

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

// 工作类,继承自 QObject
class Worker : public QObject {
    Q_OBJECT

public slots:
    void doWork() {
        // 耗时任务,例如模拟一个长时间运行的任务
        for (int i = 0; i < 5; ++i) {
            qDebug() << "Working in thread:" << QThread::currentThread();
            QThread::sleep(1); // 模拟耗时操作
        }
        emit workFinished();
    }

signals:
    void workFinished();
};

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

    // 创建工作对象
    Worker worker;

    // 创建并启动新线程
    QThread workerThread;
    worker.moveToThread(&workerThread);

    // 连接信号和槽
    QObject::connect(&workerThread, &QThread::started, &worker, &Worker::doWork);
    QObject::connect(&worker, &Worker::workFinished, &workerThread, &QThread::quit);
    QObject::connect(&workerThread, &QThread::finished, &workerThread, &QObject::deleteLater);

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

    // 主线程继续执行其他任务
    qDebug() << "Main thread:" << QThread::currentThread();

    return app.exec();
}

#include "main.moc"

代码解释

  1. 创建工作类
    • Worker 类继承自 QObject,并在其中定义了一个 doWork 槽函数,模拟一个耗时任务。
    • doWork 函数中,我们使用 QThread::sleep(1) 来模拟耗时操作。
    • 定义了一个 workFinished 信号,当任务完成时发射该信号。
  2. 创建工作对象和新线程
    • 在 main 函数中,创建 Worker 对象和 QThread 对象。
  3. 移动工作对象到新线程
    • 使用 moveToThread 方法将 Worker 对象移动到 workerThread 线程。
  4. 连接信号和槽
    • 连接 workerThread 的 started 信号到 worker 的 doWork 槽,以便线程启动时开始工作。
    • 连接 worker 的 workFinished 信号到 workerThread 的 quit 槽,以便工作完成时线程退出。
    • 连接 workerThread 的 finished 信号到其自身的 deleteLater 槽,以便线程结束时自动删除线程对象。
  5. 启动线程
    • 调用 workerThread.start() 启动新线程。
  6. 主线程继续执行
    • 主线程可以继续执行其他任务,而不会被耗时操作阻塞。

注意事项

  • 跨线程通信必须使用信号和槽机制,而不是直接调用槽函数。
  • 确保在对象移动到另一个线程之前,不要对其进行任何进一步的直接操作。
  • 线程结束后,记得清理资源,避免内存泄漏。

这个示例展示了如何使用 moveToThread 将耗时任务移到后台线程,从而保持主线程的响应性。

3.使用 Qt 的 moveToThread 方法移动到一个线程中,如何给这个线程添加任务 

在 Qt 中,使用 moveToThread 方法将一个对象移动到一个新的线程后,你可以通过信号和槽机制来给这个线程中的对象添加任务。这是因为直接跨线程调用槽函数是不安全的,可能会导致线程竞争和数据不一致等问题。因此,Qt 强制要求所有的跨线程通信都必须通过信号和槽来进行。

下面是一个扩展的示例,展示了如何给一个已经移动到新线程的对象添加任务:

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

// 工作类,继承自 QObject
class Worker : public QObject {
    Q_OBJECT

public slots:
    void doWork(const QString &taskDescription) {
        // 执行传入的任务
        qDebug() << "Working on task:" << taskDescription << "in thread:" << QThread::currentThread();
        // 模拟耗时操作
        QThread::sleep(2);
        emit workFinished();
    }

signals:
    void workFinished();
};

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

    // 创建工作对象
    Worker worker;

    // 创建并启动新线程
    QThread workerThread;
    worker.moveToThread(&workerThread);

    // 连接信号和槽
    QObject::connect(&workerThread, &QThread::started, [&worker]() {
        worker.doWork("Initial Task");
    });
    QObject::connect(&worker, &Worker::workFinished, &workerThread, &QThread::quit);
    QObject::connect(&workerThread, &QThread::finished, &workerThread, &QObject::deleteLater);

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

    // 在主线程中等待一段时间后添加新任务
    QTimer::singleShot(3000, [&worker]() {
        // 注意:这里使用 lambda 表达式是因为我们需要在主线程中发送信号
        // 而 doWork 需要在 workerThread 中执行
        QMetaObject::invokeMethod(&worker, "doWork", Q_ARG(QString, "New Task from Main Thread"));
    });

    // 主线程继续执行其他任务
    qDebug() << "Main thread:" << QThread::currentThread();

    return app.exec();
}

#include "main.moc"

在这个示例中,我们做了以下几点修改:

  1. 给 Worker 类添加了一个带参数的 doWork 槽函数,这样我们就可以传递任务描述给它。
  2. 在 main 函数中,我们使用 QTimer::singleShot 来在主线程中延迟 3 秒后发送一个新任务给 worker 对象。这里使用了 QMetaObject::invokeMethod 来确保跨线程调用是安全的。
  3. 我们连接了 workerThread 的 started 信号到一个 lambda 表达式,这个表达式在线程启动时立即调用 worker 的 doWork 方法,并传递一个初始任务描述。

注意,虽然 QMetaObject::invokeMethod 可以在这里用于跨线程调用,但通常建议在设计时就通过信号和槽来规划好跨线程通信,而不是在运行时动态调用槽函数。这样做可以使代码更加清晰和易于维护。

4.QThread::quit 

QThread::quit() 是 Qt 框架中 QThread 类的一个成员函数,用于请求线程退出。当调用这个函数时,线程并不会立即停止,而是会执行完当前事件循环中的所有事件后才退出。如果线程中没有事件循环(即没有调用 exec()),那么 quit() 将不会起任何作用,线程会继续执行直到它的 run() 方法完成。

在使用 QThread 时,通常的做法是:

  1. 创建一个继承自 QThread 的类,并重写 run() 方法以包含线程应该执行的代码。
  2. 或者,更常见且推荐的做法是,创建一个 QObject 子类来包含工作逻辑,然后将这个对象移动到一个 QThread 实例中。
  3. 在需要线程退出时,从适当的线程(通常是主线程或工作对象所在的线程)调用 quit()

当 quit() 被调用后,如果线程中有事件循环,它会退出该循环,并且如果线程是通过 QThread 的 start() 方法启动的,那么线程的 run() 方法也会结束,从而导致线程退出。

重要的是要注意,quit() 只是请求线程退出,它不会强制线程立即停止。如果线程中有长时间运行的任务或阻塞调用,那么线程可能不会立即响应 quit() 请求。在这种情况下,可能需要额外的机制来确保线程能够安全地中断和退出。

此外,QThread::quit() 与 QThread::exit() 是不同的。exit() 会立即退出线程的事件循环,并且可以选择性地返回一个退出码,而 quit() 只是请求退出事件循环,不涉及退出码。在大多数情况下,使用 quit() 就足够了,除非你需要从线程退出时传递特定的状态信息。

5.使用 Qt 的 moveToThread 方法创建线程,在另一个线程不断的用信号,给这个线程添加任务 

使用 Qt 的 moveToThread 方法可以将对象移动到另一个线程中运行,而利用信号和槽机制可以在不同线程之间安全地传递任务和数据。以下是一个示例,展示了如何在一个线程中不断地用信号给另一个线程添加任务。

首先,确保你已经安装了 Qt 并设置好开发环境。以下是一个完整的示例代码:

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

// Worker 类在另一个线程中执行任务
class Worker : public QObject {
    Q_OBJECT

public slots:
    void doWork(const QString &parameter) {
        qDebug() << "Working in thread:" << QThread::currentThread() << "with parameter:" << parameter;
        
        // 模拟长时间任务
        QThread::sleep(2);
        
        qDebug() << "Finished work in thread:" << QThread::currentThread();
    }
};

// MainObject 类在主线程中,负责发送信号
class MainObject : public QObject {
    Q_OBJECT

public:
    MainObject() {
        // 创建一个新的线程
        workerThread = new QThread;
        
        // 创建 Worker 对象
        worker = new Worker;
        
        // 将 Worker 对象移动到新的线程中
        worker->moveToThread(workerThread);
        
        // 启动线程
        workerThread->start();
        
        // 连接信号和槽
        connect(this, &MainObject::operate, worker, &Worker::doWork);
        connect(workerThread, &QThread::finished, worker, &QObject::deleteLater);
    }
    
    ~MainObject() {
        // 停止线程
        workerThread->quit();
        workerThread->wait();
        delete workerThread;
    }

signals:
    void operate(const QString &);

public:
    QThread *workerThread;
    Worker *worker;
};

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

    MainObject mainObject;

    // 使用 QTimer 每隔一段时间发送信号
    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, [&mainObject]() {
        static int counter = 0;
        mainObject.emit operate(QString("Task %1").arg(++counter));
    });
    timer.start(1000); // 每隔1秒发送一次信号

    return a.exec();
}

#include "main.moc"

代码说明

  1. Worker 类
    • 定义一个槽 doWork,用于在另一个线程中执行任务。
    • 使用 qDebug 打印当前线程信息和任务参数。
    • 模拟一个长时间任务(使用 QThread::sleep(2))。
  2. MainObject 类
    • 创建一个新的 QThread 对象和一个 Worker 对象。
    • 使用 moveToThread 将 Worker 对象移动到新的线程中。
    • 启动线程。
    • 连接 MainObject::operate 信号到 Worker::doWork 槽。
    • 在析构函数中,确保线程正确停止和清理。
  3. main 函数
    • 创建 QCoreApplication 对象。
    • 创建 MainObject 对象。
    • 使用 QTimer 每隔1秒发送一个信号,调用 MainObject::operate,传递任务参数。

注意事项

  • 使用 moveToThread 后,对象的所有槽都会在新的线程中执行。
  • 确保线程间的信号和槽连接是正确的,尤其是在主线程和工作线程之间传递数据时。
  • 在多线程编程中,要注意线程安全和对象生命周期管理,避免内存泄漏和访问冲突。

这个示例展示了如何使用 Qt 的信号和槽机制以及 moveToThread 方法在不同线程之间传递任务和数据,希望对你有所帮助。

6.QObject::deleteLater 

QObject::deleteLater() 是 Qt 框架中的一个成员函数,属于 QObject 类。这个函数的作用是安排调用此函数的对象在事件循环的下一个“空闲”时间被删除。换句话说,它不会立即删除对象,而是将对象的删除操作推迟到事件循环的下一个迭代中,从而确保删除操作在安全的上下文中执行。

使用场景

deleteLater() 通常用于以下场景:

  1. 跨线程删除对象:当对象在一个线程中被创建,但需要在另一个线程中被删除时,直接删除可能会导致线程安全问题。使用 deleteLater() 可以确保对象在正确的线程和事件循环中被删除。

  2. 延迟删除:有时候,你可能不希望立即删除一个对象,而是希望在某个事件或操作完成后才删除。这时,可以使用 deleteLater() 来安排对象的延迟删除。

  3. 避免内存泄漏:在复杂的应用程序中,确保所有对象都被正确删除以避免内存泄漏是非常重要的。使用 deleteLater() 可以帮助你管理对象的生命周期,确保对象在不再需要时被删除。

注意事项

  • 事件循环deleteLater() 依赖于事件循环来执行删除操作。如果对象所在线程没有事件循环,那么 deleteLater() 将不会起作用。因此,在调用 deleteLater() 之前,请确保对象所在线程有一个正在运行的事件循环。

  • 对象状态:在调用 deleteLater() 之后,对象仍然可以被访问,直到事件循环实际执行删除操作为止。因此,在调用 deleteLater() 后立即访问对象的状态或成员变量可能是不安全的。

  • 父对象与子对象:在 Qt 中,父对象负责删除其子对象。如果一个对象被设置为另一个对象的子对象,并且父对象被删除,那么子对象也会被自动删除。在这种情况下,通常不需要对子对象调用 deleteLater()

  • 多次调用:对同一个对象多次调用 deleteLater() 是安全的。Qt 会确保对象只被删除一次。

示例代码

以下是一个简单的示例,展示了如何使用 deleteLater()

#include <QCoreApplication>
#include <QObject>
#include <QTimer>
#include <QDebug>

class MyObject : public QObject {
    Q_OBJECT

public slots:
    void scheduleDeletion() {
        qDebug() << "Scheduling deletion...";
        deleteLater(); // 安排对象在事件循环的下一个空闲时间被删除
    }

    void doSomething() {
        qDebug() << "Doing something...";
        // ... 执行一些操作 ...
    }
};

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

    MyObject obj;

    QTimer::singleShot(1000, &obj, &MyObject::doSomething); // 1秒后执行doSomething
    QTimer::singleShot(2000, &obj, &MyObject::scheduleDeletion); // 2秒后安排删除对象

    return a.exec(); // 进入事件循环
}

#include "main.moc"

在这个示例中,MyObject 类有一个槽 scheduleDeletion(),它调用 deleteLater() 来安排对象的删除。在 main() 函数中,我们使用 QTimer::singleShot() 来安排在特定时间后调用这些槽。当应用程序进入事件循环时,它会在 2 秒后执行 scheduleDeletion(),然后在事件循环的下一个空闲时间删除 obj 对象。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值