在Qt中,可以通过以下几种常见方式来启动线程

在Qt中,可以通过以下几种常见方式来启动线程:

1. 继承QThread类并重写run()方法

  • 步骤一:创建自定义线程类
    • 首先,需要从QThread类继承创建一个自定义的线程类。例如:
#include <QThread>

class MyThread : public QThread
{
    Q_OBJECT
public:
    explicit MyThread(QObject *parent = nullptr);
    ~MyThread();

protected:
    void run() override;
};

MyThread::MyThread(QObject *parent) : QThread(parent)
{
}

MyThread::~MyThread()
{
}

void MyThread::run()
{
    // 在这里放置线程要执行的任务代码
    // 例如,可以是一个长时间运行的计算任务、文件读取任务等
    qDebug() << "线程正在执行任务";
}
  • 步骤二:创建线程对象并启动线程
    • 在需要启动线程的地方,创建自定义线程类的对象,然后调用start()方法来启动线程。例如:
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    MyThread *thread = new MyThread();
    thread->start();

    return a.exec();
}
- 当调用`start()`方法后,线程会进入就绪状态,一旦系统资源允许,就会开始执行`run()`方法中的代码,从而启动线程执行相应的任务。

2. 使用QThreadPool和QRunnable

  • 步骤一:创建可运行对象类(继承QRunnable)
    • 创建一个继承自QRunnable的类,在这个类中定义线程要执行的任务。例如:
#include <QThreadPool>
#include <QRunnable>

class MyRunnable : public QRunnable
{
public:
    MyRunnable();

    void run() override;
};

MyRunnable::MyRunnable()
{
}

void MyRunnable::run()
{
    // 在这里放置线程要执行的任务代码
    qDebug() << "通过QThreadPool启动的线程正在执行任务";
}
  • 步骤二:使用QThreadPool来调度线程执行任务
    • 在主程序中,先创建QThreadPool对象,然后将可运行对象类的实例添加到线程池中,由线程池来调度线程执行任务。例如:
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    MyRunnable *runnable = new MyRunnable();
    pool.start(runnable);

    // 可以根据需要设置线程池的一些属性,比如最大线程数等
    pool.setMaxThreadCount(5);

    return a.exec();
}
- QThreadPool会根据系统资源和自身的调度策略,安排可运行对象类中的任务在合适的线程中执行。这种方式适合处理大量的、相对独立的小任务,可以更有效地利用系统资源,并且便于管理线程的数量和执行情况。

3. 使用QtConcurrent::run()函数

  • 步骤一:定义要在线程中执行的函数
    • 首先需要定义一个普通函数,这个函数将作为线程要执行的任务。例如:
#include <QtConcurrent>

void myFunction()
{
    // 在这里放置线程要执行的任务代码
    qDebug() << "通过QtConcurrent::run()启动的线程正在执行任务";
}
  • 步骤二:使用QtConcurrent::run()启动线程执行任务
    • 在主程序中,使用QtConcurrent::run()函数来启动线程执行定义好的函数任务。例如:
int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);

    QtConcurrent::run(myFunction);

    return a.exec();
}
- QtConcurrent::run()函数会自动创建一个线程来执行指定的函数任务,并且会根据系统资源情况进行合理的调度。这种方式相对简单快捷,适合启动单个线程来执行一些简单的任务,不需要创建专门的线程类或可运行对象类。

以上就是在Qt中启动线程的几种常见方式,每种方式都有其特点和适用场景,可以根据具体的项目需求来选择合适的线程启动方式。

=========================================================

以下是一个更详细的使用QThreadPool和QRunnable启动线程的代码示例,展示了如何在实际场景中利用它们来执行多个任务:

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

// 自定义可运行对象类,继承自QRunnable
class MyRunnable : public QRunnable
{
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

    void run() override
    {
        // 模拟线程执行一个任务,这里简单打印任务信息
        qDebug() << "线程 " << QThread::currentThreadId() << " 正在执行任务 " << m_taskId;

        // 可以在这里添加实际的任务代码,比如长时间运行的计算、文件读取等操作

        // 模拟任务执行时间
        QThread::sleep(2);

        qDebug() << "线程 " << QThread::currentThreadId() << " 完成任务 " << m_taskId;
    }

private:
    int m_taskId;
};

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

    // 创建线程池对象
    QThreadPool pool;

    // 设置线程池的最大线程数
    pool.setMaxThreadCount(3);

    // 创建多个可运行对象实例并添加到线程池中启动线程执行任务
    for (int i = 1; i <= 5; ++i)
    {
        MyRunnable *runnable = new MyRunnable(i);
        pool.start(runnable);
    }

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

    qDebug() << "所有任务已完成,程序即将退出";

    return a.exec();
}

在上述代码中:

  1. 首先定义了MyRunnable类,它继承自QRunnable。在构造函数中接受一个任务编号taskId,用于区分不同的任务。重写的run方法中定义了每个任务的具体执行逻辑,这里只是简单模拟了任务执行情况,包括打印任务开始和完成信息,以及通过QThread::sleep模拟任务执行时间。

  2. main函数中:

    • 创建了QThreadPool对象pool
    • 设置了线程池的最大线程数为3,这意味着线程池最多同时启动3个线程来执行任务。
    • 通过循环创建了5MyRunnable对象实例,并将它们逐个添加到线程池中,由线程池来调度线程执行这些任务。
    • 最后调用pool.waitForDone等待所有任务完成,然后打印出所有任务已完成的信息。

通过这个示例,你可以看到如何使用QThreadPool和QRunnable来有效地管理和启动多个线程执行不同的任务,并且可以根据需要灵活设置线程池的相关属性,如最大线程数等。

========================================================================

在使用QThreadPool和QRunnable时,如何获取线程的执行结果?

在使用QThreadPool和QRunnable时,QRunnable本身并没有直接提供获取线程执行结果的机制,因为它主要关注的是定义可执行的任务。不过,我们可以通过一些额外的手段来获取线程的执行结果,以下是一种常见的实现方式:

1. 使用信号与槽机制(Signal-Slot Mechanism)

  • 步骤一:修改可运行对象类(MyRunnable)
    • 首先,在继承自QRunnable的可运行对象类中添加信号来表示任务完成以及携带执行结果(如果有具体结果需要返回的话)。例如,假设我们的任务是进行一个简单的数学计算并返回结果,可如下修改:
#include <QObject>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

class MyRunnable : public QRunnable, public QObject
{
    Q_OBJECT
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

    void run() override
    {
        // 模拟进行一个简单的数学计算任务
        int result = m_taskId * 2;

        // 发送任务完成信号,并携带结果
        emit taskFinished(result);

        qDebug() << "线程 " << QThread::currentThreadId() << " 完成任务 " << m_taskId;
    }

signals:
    void taskFinished(int result);

private:
    int m_taskId;
};
- 在上述代码中,我们在`MyRunnable`类中添加了`taskFinished`信号,用于在任务完成时发送,并携带计算得到的结果。
  • 步骤二:在主线程中连接信号与槽
    • 在主线程中,创建MyRunnable对象并添加到QThreadPool启动线程执行任务之前,需要连接taskFinished信号到一个槽函数,以便在任务完成时接收并处理结果。例如:
#include <QCoreApplication>

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

    QThreadPool pool;

    // 设置线程池的最大线程数
    pool.setMaxThreadCount(3);

    // 创建可运行对象实例并连接信号与槽
    MyRunnable *runnable = new MyRunnable(1);
    QObject::connect(runnable, &MyRunnable::taskFinished, [](int result) {
        qDebug() << "接收到任务结果:" << result;
    });

    // 添加到线程池启动线程执行任务
    pool.start(runnable);

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

    qDebug() << "所有任务已完成,程序即将退出";

    return a.exec();
}
- 在上述`main`函数中,我们创建了`MyRunnable`对象`runnable`后,通过`QObject::connect`函数连接了`runnable`的`taskFinished`信号到一个匿名的槽函数,该槽函数会在接收到任务完成信号时打印出接收到的结果。

2. 使用共享数据结构(如全局变量或类成员变量)

  • 步骤一:定义共享数据结构
    • 可以定义一个全局变量或者在某个类中定义成员变量来作为共享数据结构,用于存储线程执行的结果。例如,定义一个全局的std::vector来存储多个任务的结果(假设每个任务都返回一个整数结果):
#include <vector>

std::vector<int> globalResults;
  • 步骤二:在可运行对象类中更新共享数据结构
    • MyRunnable类的run方法中,将任务执行结果存储到共享数据结构中。以下是修改后的MyRunnable类代码:
#include <QObject>
#include <QThreadPool>
#include <QRunnable>
#include <QDebug>

class MyRunnable : public QRunnable, public QObject
{
    Q_OBJECT
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

    void run() override
    {
        // 模拟进行一个简单的数学计算任务
        int result = m_taskId * 2;

        // 将结果存储到全局变量中
        globalResults.push_back(result);

        qDebug() << "线程 " << QThread::currentThreadId() << " 完成任务 " << m_taskId;
    }

private:
    int m_taskId;
};
- 在上述代码中,`run`方法在计算出任务结果后,将其添加到`globalResults`全局变量中。
  • 步骤三:在主线程中访问共享数据结构获取结果
    • 在主线程中,在所有任务通过QThreadPool执行完成后(例如通过调用pool.waitForDone),可以直接访问共享数据结构来获取线程执行的结果。例如:
#include <QCoreApplication>

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

    QThreadPool pool;

    // 设置线程池的最大线程数
    pool.setMaxThreadCount(3);

    // 创建多个可运行对象实例并添加到线程池启动线程执行任务
    for (int i = 1; i <= 5; ++i)
    {
        MyRunnable *runnable = new MyRunnable(i);
        pool.start(runnable);
    }

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

    // 访问全局变量获取结果
    for (int result : globalResults)
    {
        qDebug() << "获取到的任务结果:" << result;
    }

    qDebug() << "所有任务已完成,程序即将退出";

    return a.exec();
}
- 在上述`main`函数中,在所有任务完成后,通过遍历`globalResults`全局变量,打印出了每个任务的结果。

需要注意的是,当使用共享数据结构时,要特别注意线程安全问题,因为多个线程可能会同时访问和修改这些数据结构。在上述示例中,std::vectorpush_back操作在多线程环境下可能会导致数据不一致等问题。为了确保线程安全,可以使用一些线程安全的容器类(如QVector等)或者在访问共享数据结构时采取适当的同步措施(如使用互斥锁等)。

以上就是在使用QThreadPool和QRunnable时获取线程执行结果的两种常见方法,你可以根据具体的项目需求和场景选择合适的方式。

===============================================================

除了信号与槽机制外,以下几种方法也可用于获取使用QThreadPool和QRunnable启动线程的执行结果:

1. 利用条件变量(Condition Variable)和互斥锁(Mutex)

  • 设置共享数据结构
    • 首先定义一个共享的数据结构来存储线程执行的结果。例如,创建一个结构体来保存任务相关信息及结果:
#include <mutex>
#include <condition_variable>

struct TaskResult {
    int taskId;
    int result;
    bool isFinished;
};

std::vector<TaskResult> taskResults;
std::mutex mutexResults;
std::condition_variable cv;
- 这里定义了一个`TaskResult`结构体,包含任务编号`taskId`、任务结果`result`以及一个表示任务是否完成的标志`isFinished`。同时定义了一个`std::vector`来存储多个任务的结果,以及互斥锁`mutexResults`和条件变量`cv`用于保证线程安全和实现同步。
  • 在可运行对象类中更新结果
    • 在继承自QRunnable的可运行对象类(如MyRunnable)的run方法中,执行完任务后更新共享数据结构中的对应任务结果,并设置任务完成标志。以下是示例代码:
class MyRunnable : public QRunnable {
public:
    MyRunnable(int taskId) : m_taskId(taskId) {}

    void run() override {
        // 模拟执行任务,这里简单进行一个数学计算
        int result = m_taskId * 2;

        // 获取互斥锁
        std::unique_lock<std::mutex> lock(mutexResults);

        // 更新任务结果到共享数据结构
        taskResults.push_back({m_taskId, result, true});

        // 通知等待在条件变量上的线程(这里主要是主线程)
        cv.notify_one();

        // 释放互斥锁
        lock.unlock();

        qDebug() << "线程 " << QThread::currentThreadId() << " 完成任务 " << m_taskId;
    }

private:
    int m_taskId;
};
- 在上述代码中,通过互斥锁保护对共享数据结构`taskResults`的访问,执行完任务后将结果存入结构体并添加到向量中,然后通过条件变量通知可能正在等待结果的其他线程(通常是主线程)。
  • 在主线程中获取结果
    • 在主线程中,通过循环等待条件变量的通知,当收到通知后检查共享数据结构中任务是否完成,若完成则获取并处理相应的结果。示例代码如下:
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    pool.setMaxThreadCount(3);

    // 创建多个可运行对象实例并添加到线程池启动线程执行任务
    for (int i = 1; i <= 5; ++i) {
        MyRunnable *runnable = new MyRunnable(i);
        pool.start(runnable);
    }

    // 等待所有任务完成并获取结果
    std::unique_lock<std::mutex> lock(mutexResults);
    while (taskResults.size() < 5) {
        cv.wait(lock);
    }
    lock.unlock();

    // 处理获取到的结果
    for (const auto& result : taskResults) {
        qDebug() << "获取到任务 " << result.taskId << " 的结果:" << result.result;
    }

    qDebug() << "所有任务已完成,程序即将退出";

    return a.exec();
}
- 在上述`main`函数中,主线程通过`cv.wait`等待条件变量的通知,一旦收到通知且满足所有任务结果都已获取的条件(通过检查`taskResults`的大小),就可以解锁互斥锁并处理获取到的结果。

2. 使用 futures 和 promises(C++11及以后版本支持)

  • 设置共享数据结构(可选)
    • 虽然futures和promises本身可以处理结果传递,但也可以结合一个简单的共享数据结构来更好地组织结果。例如,同样可以定义一个std::vector来存储任务结果(这里假设任务结果为整数):
#include <future>
#include <vector>

std::vector<int> taskResults;
  • 在可运行对象类中设置promise
    • 在继承自QRunnable的可运行对象类(如MyRunnable)中,需要引入std::promise来设置任务结果。示例代码如下:
class MyRunnable : public QRunnable {
public:
    MyRunnable(int taskId, std::promise<int>&& p) : m_taskId(taskId), m_promise(std::move(p)) {}

    void run() override {
        // 模拟执行任务,这里简单进行一个数学计算
        int result = m_taskId * 2;

        // 设置promise的值,即任务结果
        m_promise.set_value(result);

        qDebug() << "线程 " << QThread::currentThreadId() << " 完成任务 " << m_taskId;
    }

private:
    int m_taskId;
    std::promise<int> m_promise;
};
- 在上述代码中,构造函数接受一个`std::promise`对象,在`run`方法执行完任务后,通过`set_value`方法将任务结果设置到promise中。
  • 在主线程中获取结果
    • 在主线节中,通过创建std::future对象与std::promise关联,然后等待并获取任务结果。示例代码如下:
int main(int argc, char *argv[]) {
    QCoreApplication a(argc, argv);

    QThreadPool pool;
    pool.setMaxThreadCount(3);

    // 创建多个可运行对象实例并添加到线程池启动线程执行任务
    std::vector<std::future<int>> futures;
    for (int i = 1; i <= 5; ++i) {
        std::promise<int> promise;
        MyRunnable *runnable = new MyRunnable(i, std::move(promise));
        pool.start(runnable);
        futures.push_back(promise.get_future());
    }

    // 获取并处理结果
    for (auto& future : futures) {
        int result = future.get();
        taskResults.push_back(result);
        qDebug() << "获取到任务结果:" << result;
    }

    qDebug() << "所有任务已完成,程序即将退出";

    return a.exec();
}
- 在上述`main`节中,首先创建`std::promise`和`std::future`对象并关联,将`MyRunnable`对象添加到线程池启动线程执行任务后,通过`future.get`等待并获取每个任务的结果,然后将结果存入`taskResults`向量中并进行打印。

这些方法都可以在不使用信号与槽机制的情况下,有效地获取使用QThreadPool和QRunnable启动线程的执行结果,你可以根据具体项目需求和编程环境选择合适的方法。

posted @ 2024-11-10 00:18  MarsCactus  阅读(171)  评论(0编辑  收藏  举报