文章目录
- QThread的API
- 使用示例
- 客户端多线程应用场景
- 互斥锁
- QMutex
- QMutexLocker
- QReadWriteLocker、QReadLocker、QWriteLocker
- 条件变量和信号量
QThread的API
Qt中的多线程和Linux中的线程,本质上是一个东西
Linux线程概念
Linux多线程——线程控制
Linux多线程——互斥锁
Linux多线程——生产消费者模型
QThread:
- 要创建线程,需要创建这个类的实例
- 创建线程时,需指明线程入口函数
- 创建
QThread
的子类,重写了其中的run
方法,起到指定入口函数的方式(多态)
Tips:
这种方式在C++中并不常见,相比之下
std::thread
直接指定回调方式更常见因为C++比较追求性能,多态机制可能导致运行时的额外开销(查询函数表,找到对应执行函数再执行)
但是对应客户端开发,对性能的要求,并没有那么的高
API | 说明 |
---|---|
run() | 线程入口函数 |
start() | 通过运行run()开始执行线程 (该操作是真正调用系统API创建线程) |
currentThread() | 获取当前线程的指针 |
isRunning() | 如果线程正在运行返回true,否则返回false |
sleep()、msleep()、usleep() | 线程休眠,单位秒/毫秒/微妙 |
wait() | 线程阻塞,功能和pthread_join 类似 |
terminate() | 终止线程执行。 线程可以立即终止,也可以不终止,取决于操作系统的调用 |
finished() | 线程结束发出的信号,可通过该信号实现线程的清理工作 |
使用示例
基于定时器的倒计时程序
创建QThread
子类:
thread.h
:
#ifndef THREAD_H
#define THREAD_H#include <QWidget>
#include<QThread>
class Thread : public QThread
{Q_OBJECT
public:Thread();//重写父类run方法void run();
signals://只需写函数声明, 定义Qt自动生成void notify();
};#endif // THREAD_H
widget.h
:
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include"thread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void handle();private:Ui::Widget *ui;Thread thread;
};
#endif // WIDGET_H
thread.cpp
:
#include "thread.h"Thread::Thread()
{}void Thread::run()
{//针对时间进行计时,每过一秒,通过信号槽通知主线程更新界面for(int i = 0; i < 10; i++){sleep(1);//发生信息emit notify();}
}
widget.cpp
:
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//连接信号槽connect(&thread, &Thread::notify, this, &Widget::handle);//启动线程thread.start();
}Widget::~Widget()
{delete ui;
}void Widget::handle()
{int value = ui->lcdNumber->intValue();value--;ui->lcdNumber->display(value);
}
如果多个线程同时对界面进行修改,就会导致界面出错。
Qt直接一刀切,针对控件的任何修改,都在主线程中执行。
运行示意图:
客户端多线程应用场景
在服务器开发的角度,多线程主要是充分利用多核CPU的计算资源,达到更高的效率。
而对于客户端,对效率要求并不是特别高,如果追求效率,把CPU计算资源吃完,会导致系统卡顿,这用户体验是很差的。
在客户端中,多线程主要是用于一些耗时的等待IO的操作,避免主线程卡死。
比如说客户端向服务端上传/下载较大的文件
这种密集的IO操作会使程序被系统阻塞挂起,一旦进程被挂起了,此时用户的操作就无法响应了。
因此使用单独的线程来处理这种密集的IO操作,就算挂起,也是挂起的这个线程,并不会影响主线程。
互斥锁
QMutex
谈到线程,必定绕不开线程安全问题,最通用的手段就是加锁,QMutex
类就是Qt封装的互斥锁。
上面这种情况就是线程安全问题,采取加锁,让线程串行执行
锁也是公共区的,只有一把锁
QMutexLocker
C++11引入了std::lock_guard
,智能锁RAII机制,这样能避免抛出异常或者忘记释放锁导致的问题。
Qt参考过来了,叫做QMutexLocker
。
Tips:
Qt的锁和C++的锁,本质上都是封装系统提供的锁
虽然可以用C++的锁锁住Qt的线程,但是不建议。
QReadWriteLocker、QReadLocker、QWriteLocker
QReadWriteLocker
读写锁,用于控制读和写的并发访问QReadLocker
用于读操作上锁,允许多个线程共享资源QWriteLocker
用于写操作上锁,一次允许一个线程写数据
条件变量和信号量
Qt当中的条件变量和信号量,与Linux当中的概念一模一样,只不过是接口不一样而已。
多个线程的调度是无序的,为了一定程度干预执行顺序,引入条件变量。
QWaitCondition
:
wait
等待wake
唤醒wakeAll
唤醒全部
QSemaphore
:
acquire
获取信号量release
释放信号量