Qt-线程和线程池

前言

Qt实现在线程中执行任务有4种方法,分别是:
1. 创建一个派生于 QThread 类的子类,重写run函数,在run函数中执行任务
2. 创建一个派生于 QObject 的子类,调用 QObject::moveToThread() 方法将子类对象移动到子线程对象中。
3. 使用线程池QThreadPool
4. 使用QtConcurrent执行并行任务

这4种方法的完整代码示例在绑定的资源中,大家可以免费下载。



一 重写run函数的方法执行任务

局限性:只能在 run 函数中处理任务

使用步骤
1. 创建一个派生于 QThread 类的子类。

2. 在子类中重写 run 函数,并在 run 函数中编写子线程执行的任务代码。
    注意:不能在类的外部调用 run() 方法启动子线程,只能通过调用 start() 方法启动子线程,启动子线程后,子线程会自动执行 run 任务。

3. 在主线程中创建子线程对象,new 一个就行。

4. 在主线程中关联 QThread::finished  信号,用于在槽函数中清理子线程资源,释放内存。

5. 在主线程中关联子线程中的自定义信号,用于主线程和子线程之间的通信。

6. 调用 start() 方法启动子线程,执行 run 中的任务。

7. 子线程执行完毕释放内存,清理资源。

代码示例

1. 子线程类头文件:

	protected:
    // 重写 QThread 类的虚函数,在该函数中处理子线程任务
    void run();

signals:
    // 自定义信号, 用于和主线程通信
    void curNumber(int num);

2. 子线程类源文件:

// 重写 QThread 类的虚函数,在该函数中处理子线程任务
void SubThread::run()
{
    // QThread::currentThread():获取当前线程的 QThread 对象指针
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while(1)
    {
        // 发送自定义信号,向主线程中传递数据
        emit curNumber(num++);
        if(num == 10000000)
        {
            break;
        }

        //阻塞,让当前子线程暂停1秒
        QThread::sleep(1);
    }

    qDebug() << "run() 执行完毕, 子线程退出...";
}

3. 主线程的构造函数中:
	
	// QThread::currentThread():获取当前线程的 QThread 对象指针
    qDebug() << "主线程对象地址:  " << QThread::currentThread();

    // 创建子线程对象
    SubThread* subThread = new SubThread;

    // 将子线程的自定义信号和主线程中的 Lambda 表达式表示的槽函数关联,用于传递数据
    connect(subThread, &SubThread::curNumber, this, [=](int num)
    {
        // 在 label 控件中显示数字
        ui->label->setNum(num);
    });

    // 点击 startBtn 按钮,启动子线程
    connect(ui->pushButton, &QPushButton::clicked, this, [=]()
    {
        // 启动子线程,子线程自动执行 run 函数中的任务
        subThread->start();
    });


    //将子线程的信号和主线程的 Lambda 表达式表示的槽函数关联,用于在线程任务执行完成后清理子线程资源,释放内存
    // connect(subThread, &SubThread::finished, this, [&](){
    //     //释放子线程对象的堆内存
    //     delete subThread;
    // });

    //在子线程对象 subThread 的任务执行结束后,在 subThread 的事件循环结束后释放子线程对象 subThread 的内存
//注意:subThread 线程对象的 run 函数中需要启用事件循环(exec())。

    connect(subThread, &QThread::finished, subThread, &QObject::deleteLater);

二 移动子类对象到子线程对象

相比重写Run函数的优势:可以定义多个成员函数处理任务

使用步骤:

1. 创建一个派生于 QObject 的子类,在子类中创建自定义的公共成员函数(建议创建为槽函数,方便与子线程启动信号 QThread::started() 关联),用于执行子线程的任务(相当于 run 函数)。

2. 在主线程中实例化子线程对象(QThread) 和实例化上一步创建的子类对象。
    注意:千万不要给子类对象指定父对象

3. 调用 QObject::moveToThread() 方法将子类对象移动到子线程对象中。

4. 在主线程中将子线程对象的 QThread::started() 信号和子类对象中执行任务的槽函数关联,这样,在启动子线程时会自动执行任务

5. 在主线程中关联子线程中的自定义信号,用于主线程和子线程之间的通信。

6. 在主线程中关联 QThread::finished  信号,用于在槽函数中清理子线程和子对象资源,释放内存。

7. 调用 start() 函数启动子线程。
    注意:子线程启动后,移动到子线程中的子类对象并没有立即执行任务(自定义的公共成员槽函数)

8. 由于将子线程对象的 QThread::started() 信号和子类对象中执行任务的槽函数关联了,子线程启动后,子类对象中的任务也就自动执行了。

9. 子线程任务执行完毕释放子线程对象和子类对象内存,清理资源。

代码示例:

1. 子类对象头文件:

public slots:
    //创建自定义公共成员函数(建议创建为槽函数,方便与子线程启动信号 QThread::started() 关联),用于执行子线程任务
    void working();

signals:
    // 自定义信号, 用于和主线程通信
    void curNumber(int num);

2. 子类对象源文件:

// 创建自定义公共成员函数(建议创建为槽函数,方便与子线程启动信号 QThread::started() 关联),用于执行子线程任务
void MyWork::working() {

    // QThread::currentThread():获取当前线程的 QThread 对象指针
    qDebug() << "当前线程对象的地址: " << QThread::currentThread();

    int num = 0;
    while (1) {
        // 发送自定义信号,向主线程中传递数据
        emit curNumber(num++);
        if (num == 10000000) {
            break;
        }

        // 阻塞,让当前子线程暂停1秒
        QThread::sleep(1);
    }

    qDebug() << "run() 执行完毕, 子线程退出...";
}

3. 主线程的构造函数:

// QThread::currentThread():获取当前线程的 QThread 对象指针
    qDebug() << "主线程对象地址:  " << QThread::currentThread();

    // 创建线程对象
    QThread* sub = new QThread;

    // 创建工作的类对象
    // 注意:千万不要指定给创建的对象指定父对象
    MyWork* work = new MyWork;

    // 将工作的类对象移动到创建的子线程对象中
    work->moveToThread(sub);

    //将子线程对象的 QThread::started() 信号和子类对象中执行任务的槽函数关联,这样,在启动子线程时会自动执行任务
    connect(sub, &QThread::started, work, &MyWork::working);

    // 将子线程的自定义信号和主线程中的 Lambda 表达式表示的槽函数关联,用于传递数据
    connect(work, &MyWork::curNumber, this, [=](int num)
    {
        // 在 label 控件中显示数字
        ui->label->setNum(num);
    });

    // 点击 startBtn 按钮,执行子线程任务
    connect(ui->pushButton, &QPushButton::clicked, this, [=]()
    {
        // 启动线程
        sub->start();
    });

    //将子线程的 QThread::finished 信号和主线程的 Lambda 表达式表示的槽函数关联,用于在线程任务执行完成后清理子线程资源,释放内存
    // connect(sub, &QThread::finished, this, [&](){
    //     //释放子线程对象的堆内存
    //     delete sub;
    //     //释放工作类对象
    //     delete work;
    // });

        //在子线程对象 sub 的任务执行结束后,在子线程对象 sub 和子类对象 word 的事件循环结束后释放子线程对象 sub 和子类 work 的内存
注意:sub 的 run 函数中需要启用事件循环(exec())
    connect(sub, &QThread::finished, sub, &QObject::deleteLater);
    connect(sub, &QThread::finished, work, &QObject::deleteLater);


三 使用线程池QThreadPool

QThreadPool说明:

1. Qt 中的 QThreadPool 类管理了一组线程, 里边还维护了一个任务队列。
2. 每个 Qt 应用程序都有一个全局 QThreadPool 对象,可以通过调用 globalInstance() 来访问它。也可以单独创建一个 QThreadPool 对象使用。
3. 线程池中的线程的任务执行完毕,该线程不会立即结束,而是由线程池自动管理该线程是挂起、继续执行任务还是释放等。

 常用成员函数: 

1. 获取 Qt 应用程序的全局线程池对象指针
static QThreadPool * QThreadPool::globalInstance();


2. 线程池中的线程个数

2.1 获取线程池中的最大活动线程个数
int maxThreadCount() const;

2.2 设置线程池中的最大活动线程个数
//说明:无论提交到该线程池的任务数量有多少,同时执行的任务将不会超过设置的最大活动线程数。
void setMaxThreadCount(int maxThreadCount);

2.3 获取线程池中正在工作的线程个数
int QThreadPool::activeThreadCount() const;


3. 向线程池中添加任务(QRunnable 类型)

3.1 如果线程池中没有空闲的线程了, 任务会放到任务队列中, 等待线程处理
//参数1:任务对象指针
//参数2:指定任务的执行优先级。默认值为0,表示正常优先级。数值越大,优先级越高。
void QThreadPool::start(QRunnable * runnable, int priority = 0);

3.2 如果线程池中没有空闲的线程了, 直接返回, 任务添加失败, 任务不会添加到任务队列中
bool QThreadPool::tryStart(QRunnable * runnable);


4. 删除线程池中的任务

4.1 尝试将某一个任务从线程池的任务队列中删除, 如果任务已经开始执行就无法删除了
bool QThreadPool::tryTake(QRunnable *runnable);

4.2 将线程池中的任务队列中所有未开始处理的任务删除, 如果任务已经开始执行就无法删除了
void QThreadPool::clear();

使用步骤: 

1. 创建派生于 QRunnable 的任务子类。

 1.1 重写 run 函数,在 run 函数中编写任务的执行代码。
 1.2 在任务子类的构造函数中设置任务对象在执行完毕后自动销毁。

2. 如果想在任务中使用 Qt 的信号槽机制进行数据传递,那任务子类除了继承 QRunnable 类外,还需要继承 QObject 类。

3. 获取 Qt 应用程序的全局线程池对象指针。

4. 设置线程池中的最大活动线程个数。

5. 创建任务对象,并将其添加到线程池中。


四 使用QtConcurrent执行并行任务 

说明:这个模块极大的简化了Qt线程的使用,我们只需要调用它的API,在应用程序的全局线程池对象中安排执行某个函数而无需关心线程的创建管理和释放

代码示例:

举例1:在线程池中的线程中执行的无返回值无参数函数
void Fun1()
{
    qDebug() << "使用 QtConcurrent::run 执行无返回值无参数函数测试...";
}

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);


    // 使用 lambda 表达式在 QtConcurrent::run() 中调用要执行的 Fun1() 函数
    QFuture<void> future1 = QtConcurrent::run([](){
        Fun1();
    });

    // 等待线程池中的 Fun1() 函数执行完毕
    future1.waitForFinished();

}

举例2:在线程池中的线程中执行的有返回值有参数函数
int Fun2(int number) {
    return number * number;
}

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);


    // 使用 lambda 表达式包装有返回值的函数(传递参数5)
    QFuture<int> future2 = QtConcurrent::run([](int num){
        return Fun2(num);
    }, 5);

    // 等待线程池中的 doSomethingUseful() 函数执行完毕
    future2.waitForFinished();

    // 获取函数的返回值
    int result = future2.result();

    qDebug() << "The square of 5 is:" << result;

}
### 回答1: Qt线程池Qt提供的一种管理线程的机制,它可以帮助我们更有效地利用系统资源,并方便地进行多线程编程。 Qt线程池中的线程是共享的,这意味着多个任务可以在同一个线程中执行。线程池内部会维护一组可用的线程,当有任务需要执行时,会选择一个空闲的线程来执行任务。通过共享线程的方式,线程池可以减少线程创建和销毁的开销,提高线程的复用性。 在使用Qt线程池时,我们可以将一些待执行的任务(比如耗时的计算、网络请求等)封装成QRunnable对象,并将其提交给线程池线程池会自动将这些任务分配给空闲线程执行,而不需要我们手动创建和管理线程线程池的共享机制可以提高程序的性能和响应速度。通过共享线程线程池可以更好地管理线程的数量,避免过多的线程创建和销毁过程,从而减少了系统资源的消耗。而且,线程池可以实现任务的并行执行,提高任务处理的效率。 不过需要注意的是,在使用Qt线程池的过程中,我们需要在处理任务时保证线程安全。由于线程池中的线程是共享的,多个任务可能会同时在同一个线程中执行。因此,如果任务之间有共享的数据,必须使用互斥锁等机制来保护共享数据的访问,防止出现数据竞争和异常行为。 总而言之,Qt线程池提供了一种方便且高效的多线程编程机制,通过线程共享的方式减少了线程创建和销毁的开销,提高了程序的性能和响应速度。同时,我们需要注意线程安全性,在处理共享数据时使用适当的同步机制。 ### 回答2: 在Qt中,线程池是一种用于管理和调度线程的机制,它能够实现线程的复用,提高多线程编程的效率。线程池可以用于并行执行多个任务,每个任务由一个线程来处理。 Qt线程池使用QThreadPool类来实现,它提供了一组方法来管理线程池的大小、任务的提交和取消等操作。线程池中的线程是由Qt框架自动创建和管理的,可以通过设置线程池大小来控制线程的数量。线程池中的线程是共享的,即多个任务可以被同一个线程处理。 线程池的共享性能够节省资源,避免为每个任务都单独创建和销毁线程,从而减少了线程切换的开销。在每个任务执行完成后,线程会自动归还给线程池,以便被下一个任务使用。这样一来,线程池中的线程可以被多个任务共享,从而提高了系统的并发性能。 线程池的共享特性还可以通过Qt中的信号与槽机制实现线程间的通信。例如,一个任务在执行过程中需要与其他线程进行数据交换,可以通过发送信号和槽的方式实现线程间的消息传递。 总之,Qt线程池可以实现线程的复用和共享,提高多线程编程的效率和性能。它是一种非常实用的多线程技术,适用于需要并行执行多个任务的场景。 ### 回答3: Qt是一个跨平台的C++开发库,提供了线程池线程共享的功能。 线程池是一种线程管理机制,它维护一个线程的集合,以便在需要的时候可以复用这些线程,而不需要显式地创建和销毁线程Qt提供了QThreadPool类,它可以用于创建和管理线程池。通过使用线程池,可以更方便地处理并发任务,提高应用程序的性能和响应能力。 线程共享是指多个线程可以同时访问和修改共享的数据。在多线程编程中,线程之间共享的数据可能存在竞争条件和数据不一致的问题。为了避免这些问题,Qt提供了一些线程同步和互斥的机制。比如使用QMutex和QMutexLocker可以控制对临界区的访问,一次只允许一个线程进入;使用QReadWriteLock可以实现读写线程之间的互斥;使用QWaitCondition可以实现线程之间的条件等待和通知。 在Qt中,线程池线程共享是可以同时使用的。线程池可以创建多个线程,并管理它们的执行,而线程之间可以共享数据,并使用适当的线程同步机制来保证数据的一致性。通过合理地使用线程池线程共享,可以提高应用程序的并发性能,提升用户体验。同时,Qt提供了丰富的线程管理和同步机制,使得多线程编程更加简单和可靠。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值