Qt 系统相关 - 多线程

目录

1. Qt 多线程概述

2. QThread 常用 API

3. 使用线程

4. 线程安全

4.1 互斥锁

4.2 条件变量

4.3 信号量


1. Qt 多线程概述

在 Qt 中,多线程的处理一般是通过 QThread类 来实现。

QThread 代表一个在应用程序中可以独立控制的线程,也可以和进程中的其他线程共享数据。

QThread 对象管理程序中的一个控制线程。

2. QThread 常用 API

run()线程的入口函数..
start()通过调用 run() 开始执行线程。操作系统将根据优先级参数调度线程。如果线程已经在运行,这个函数什么也不做。
currentThread()返回⼀个指向管理当前执行线程的 QThread的指针。
isRunning()如果线程正在运行则返回true;否则返回false。
sleep() / msleep() / usleep()使线程休眠,单位为秒 / 毫秒 / 微秒
wait()

阻塞线程,直到满足以下任何一个条件: 与此 QThread 对象关联的线程已经完成执行(即当它从run()返回时)。如果线程已经完成,这个函数将返回 true。如果线程尚未启动,它也返回 true。

已经过了几毫秒。如果时间是 ULONG_MAX(默认值),那么等待永远不会超时(线程必须从run()返回)。如果等待超时,此函数将返回 false。 这提供了与 POSIX pthread_join() 函数类似的功能。

terminate()终止线程的执行。线程可以立即终止,也可以不立即终止,这取决于操作系统的调度策略。在terminate() 之后使用 QThread::wait() 来确保。
finished()当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。

3. 使用线程

创建线程的步骤:

  1. 自定义一个类,继承于 QThread,并且只有一个线程处理函数(和主线程不是同一个线程),这个线程处理函数主要就是重写父类中的 run() 函数。
  2. 线程处理函数里面写入需要执行的复杂数据处理;
  3. 启动线程不能直接调用 run() 函数,需要使用对象来调用 start() 函数实现线程启动;
  4. 线程处理函数执行结束后可以定义一个信号来告诉主线程;
  5. 最后关闭线程。

示例:

1、新建项目,设计 UI 文件,如下图示:

2、创建 QThread 的子类:

  • 先选中项目名称 QThread,点击鼠标右键,选择 add new ... ,弹出如下对话框:

3、选择:Choose .... ,弹出如下界面: 

4、此时项目中会新添加以下两个文件: 

5、thread.h 和 widget.h 

6、在构造函数中先 “启动线程 ”

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

    // 连接信号槽,通过槽函数更新界面
    connect(&thread, &Thread::notify, this, &Widget::handle);

    // 启动线程
    thread.start();
}

7、进入到线程的入口函数,执行 for循环,每 sleep 1秒就触发一个这样的信号

void Thread::run()
{
    // 在这个 run 中,不可以直接去修改界面的内容
    // 虽然不可以修改界面,但是可以针对时间来进行计时
    // 当每到了1秒钟的时候,通过信号槽,来通知主线程,负责更新界面内容
    for (int i = 0; i < 10; i++){
        // sleep 本身是 QThread 的成员函数,可以直接调用
        sleep(1);
        // 发送一个信号,通知主线程
        emit notify();
    }
}

8、线程触发之后,主线程的槽函数就会被执行到,从而更新界面

void Widget::handle()
{
    // 此处修改界面内容
    int value = ui->lcdNumber->intValue();
    value--;
    ui->lcdNumber->display(value);
}

9、执行程序,观察效果 


说明:

1、线程函数内部不允许操作 UI 图形界面,⼀般用数据处理;

2、connect() 函数第五个参数表示的为连接的方式,且只有在多线程的时候才有意义。

connect() 函数第五个参数为 Qt::ConnectionType,用于指定信号和槽的连接类型。同时影响信号的传递方式和槽函数的执行顺序。Qt::ConnectionType 提供了以下五种方式:

Qt::AutoConnection在 Qt 中,会根据信号和槽函数所在的线程自动选择连接类型。如果信号和槽函数在同⼀线程中,那么使用 Qt:DirectConnection 类型;如果它们位于不同的线程中,那么使用Qt::QueuedConnection 类型。
Qt::DirectConnection当信号发出时,槽函数会立即在同⼀线程中执行。这种连接类型适用于信号和槽函数在同⼀线程中的情况,可以实现直接的函数调用,但需要注意线程安全性。
Qt::QueuedConnection当信号发出时,槽函数会被插入到接收对象所属的线程的事件队列中,等待下⼀次事件循环时执行。这种连接类型适用于信号和槽函数在不同线程中的情况,可以确保线程安全。
Qt::BlockingQueuedConnec tion与 Qt:QueuedConnection 类似,但是发送信号的线程会被阻塞,直到槽函数执行完毕,这种连接类型适用于需要等待槽函数执行完毕再继续的场景,但需要注意可能引起线程死锁的风险。
Qt::UniqueConnection这是⼀个标志,可以使用位或与上述任何⼀种连接类型组合使用。

4. 线程安全

实现线程互斥和同步常用的类有:

  • 互斥锁:QMutex、QMutexLocker
  • 条件变量:QWaitCondition
  • 信号量:QSemaphore
  • 读写锁:QReadLocker、QWriteLocker、QReadWriteLock

4.1 互斥锁

        互斥锁是⼀种保护和防止多个线程同时访问同一对象实例的方法,在 Qt 中,互斥锁主要是通过QMutex类来处理。


🌴QMutex

  • 特点:QMutex 是 Qt 框架提供的互斥锁类,用于保护共享资源的访问,实现线程间的互斥操作。
  • 用途:在多线程环境下,通过互斥锁来控制对共享数据的访问,确保线程安全。
QMutex mutex;

mutex.lock(); //上锁

//访问共享资源
//...

mutex.unlock(); //解锁

🌵QMutexLocker

  • 特点:QMutexLocker 是 QMutex 的辅助类,使用 RAII(Resource Acquisition Is Initialization)方式对互斥锁进行上锁和解锁操作。
  • 用途:简化对互斥锁的上锁和解锁操作,避免忘记解锁导致的死锁等问题。
QMutex mutex;
{
    QMutexLocker locker(&mutex); //在作用域内自动上锁

    //访问共享资源
    //...

} //在作用域结束时自动解锁

🌻QReadWriteLocker、QReadLocker、QWriteLocker

特点:

  • QReadWriteLock 是读写锁类,用于控制读和写的并发访问。
  • QReadLocker 用于读操作上锁,允许多个线程同时读取共享资源。
  • QWriteLocker 用于写操作上锁,只允许一个线程写入共享资源。

用途:

  • 在某些情况下,多个线程可以同时读取共享数据,但只有一个线程能够进行写操作。读写锁提供了更高效的并发访问方式。
QReadWriteLock rwLock;
//在读操作中使⽤读锁
{
    QReadLocker locker(&rwLock); //在作用域内自动上读锁

    //读取共享资源
    //...

} //在作用域结束时自动解读锁

//在写操作中使⽤写锁
{
    QWriteLocker locker(&rwLock); //在作用域内自动上写锁

    //修改共享资源
    //...

} //在作用域结束时自动解写锁

4.2 条件变量

        在多线程编程中,假设除了等待操作系统正在执行的线程之外,某个线程还必须等待某些条件满足才能执行,这时就会出现问题。这种情况下,线程会很自然地使用锁的机制来阻塞其他线程,因为这只是线程的轮流使用,并且该线程等待某些特定条件,人们会认为需要等待条件的线程,在释放互斥锁或读写锁之后进入了睡眠状态,这样其他线程就可以继续运行。当条件满足时,等待条件的线程将被另一个线程唤醒。

在 Qt 中,专门提供了 QWaitCondition类 来解决像上述这样的问题。

特点:

  • QWaitCondition 是 Qt 框架提供的条件变量类,用于线程之间的消息通信和同步。

⽤途:

  • 在某个条件满足时等待或唤醒线程,用于线程的同步和协调。
QMutex mutex;
QWaitCondition condition;

//在等待线程中
mutex.lock();

//检查条件是否满⾜,若不满⾜则等待
while (!conditionFullfilled())
{
    condition.wait(&mutex); //等待条件满⾜并释放锁
}

//条件满⾜后继续执⾏
//...

mutex.unlock();

//在改变条件的线程中
mutex.lock();

//改变条件
changeCondition();
condition.wakeAll(); //唤醒等待的线程

mutex.unlock();

4.3 信号量

        有时在多线程编程中,需要确保多个线程可以相应的访问一个数量有限的相同资源。例如,运行程序的设备可能是非常有限的内存,因此我们更希望需要大量内存的线程将这一事实考虑在内,并根据可用的内存数量进行相关操作,多线程编程中类似问题通常用信号量来处理。信号量类似于增强的互斥锁,不仅能完成上锁和解锁操作,而且可以跟踪可用资源的数量。

特点:

  • QSemaphore 是 Qt 框架提供的计数信号量类,用于控制同时访问共享资源的线程数量。

用途:

  • 限制并发线程数量,用于解决一些资源有限的问题。
QSemaphore semaphore(2); //同时允许两个线程访问共享资源

//在需要访问共享资源的线程中
semaphore.acquire(); //尝试获取信号量,若已满则阻塞

//访问共享资源
//...

semaphore.release(); //释放信号量

//在另⼀个线程中进⾏类似操作
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

南风与鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值