前言:
QT在什么时候会用到多线程:
所有的IO操作都要放到线程里面
1、IO操作QIODevice文件IO网络IO(套接字eg:CAN Linux下也是套接字)串口等外设,因为他们不确定什么时候可以读完。
2、耗时的算法eg:文件压缩 信号处理
注意:
1、线程里面尽量少用while(1)+sleep();
尽量使用事件驱动,少用耗时扫描
并发控制简介:
进行时间片轮流:比如让xx执行0.000001秒只要来回转可以看见N多软件一起在“转”;这样让用户看起来都在执行;
实时抢占:严格按照优先级走,优先级高的能抢占优先级低的任务(线程);
一、QThread介绍
QThread类提供了一个与平台无关的线程管理的方法。一个QThread对象管理一个线程。QThread的执行
从run()函数的执行开始,在QT自带的QThread类中,run()函数通 过调用exec()函数来启动事件循环机制,,并且在线程内部处理Qt的事件。在Qt中建立 线程的主要目的就是为了用线程来处理那些耗时的后台操作,从而让主界面能及时响应用户的请求操作。
QThread的使用方法有如下两种:
QObject::moveToThread()
继承QThread类
二、演示线程
2.1 如何创建线程
默认情况,我们新建的类(除少部分外),不支持信号与槽
在class中添加一个Q_OBJECT
左边的类不支持信号与槽;需要手动添加 Q_OBJECT
继承QThread类的线程框架:
头文件:
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread();
void setThreadName(QString name);
protected:
virtual void run();
private:
QString ThreadName;
};
myThread.cpp:
#include "myThread.h"
MyThread::MyThread()
{
qDebug() << ThreadName << "MyThread::MyThread" << QThread::currentThreadId();
}
void MyThread::setThreadName(QString name)
{
ThreadName=name;
qDebug()<<ThreadName<<" My Thread::setThreadName:"<<QThread::currentThreadId();
}
void MyThread::run()
{
while(1)
{
qDebug()<<ThreadName<<" My Thread::run():"<<QThread::currentThreadId();
sleep(1);
}
}
main.cpp:
#include <QCoreApplication>
#include"myThread.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
//开辟了四个线程
MyThread thread1;
thread1.setThreadName("thread1");
thread1.start();
MyThread thread2;
thread2.setThreadName("thread2");
thread2.start();
MyThread thread3;
thread3.setThreadName("thread3");
thread3.start();
MyThread thread4;
thread4.setThreadName("thread4");
thread4.start();
return a.exec();
}
2.2 演示线程ID
QThread::currentThreadId();
结论:只有run里面才属于线程本身(自线程),其他的都是父线程的。
信号与槽的接口:
2.3如何让线程保活
run情况下:
while(1)sleep
exec;
2.4 演示线程退出
如何优雅的退出线程:
以md5CheckSumToolsThread为例:
主线程关闭时,子线程生命周期还没有结束的话,就会报如下的错误 —— 经典报错对 话框:
线程退出 exit() terminate()
线程等等 wait
Widget::~Widget()
{
md5Thread.exit(); //线程退出
qDebug() << QTime::currentTime();
md5Thread.wait(10 * 1000 ); //10秒超时 (阻塞的时间最大是 10s)
qDebug() << QTime::currentTime(); //确定wait的时间
delete ui;
}
2.5 演示信号与槽中的第五个参数
connect一直都是4个参数,在这里又怎么出第五个参数了? 一般情况,第五个参数只 有在多线程中使用 通过第五个参数演示默认连接方式 多线程讲这个才有意义,不然都是空中楼阁不好理解 。
connect原型:
QObject::connect(const QObject sender, const char signal, const QObject receiver,const char method, Qt::ConnectionType type = Qt::AutoConnection)
方式 |
值 |
描述 |
Qt::AutoConnection |
0 |
默认参数;自动连接, 自动选择是Direct或者Queued |
Qt::DirectConnection |
1 |
槽函数立即执行,槽函数所在的发送者线程中执行 |
– |
- |
– |
Qt::QueuedConnection |
2 |
槽函数排队(队列)执行,槽函数在接收者所在的线程中 |
Qt::BlockingQueuedConnection |
3 |
阻塞式排队连接 |
– |
- |
– |
Qt::UniqueConnection |
4 |
一对一的自动连接 |
这只是个人理解,可能有些地方不正确,大家遇到此类需求可以参考官方文档
上图为多线程中信号与槽中的经典错误。
2.6 线程锁QMutex(一种线程的安全方式)
QMutex类提供了一种保护一个变量或者一段代码的方法,这样可以每次只让一个线程 访问它。这个类提供了一个lock()函数用于锁住互斥量,如果互斥量是解锁状态,那么 当前线程立即占用并锁定它;否则,当前线程会阻塞,直到这个互斥量的线程对它解 锁为止。QMutex类还提供了一个tryLock()函数,如果该互斥量已经锁定,它就会立即 返回。
Qt帮助文档演示QMutex
创建两个线程:
mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H
#include<QThread>
#include<QMutex>
#include<qDebug>
class MyThread : public QThread
{
Q_OBJECT
public:
MyThread();
void setThreadName(QString name);
protected:
virtual void run();
private:
QString ThreadName;
};
main.cpp
#include <QCoreApplication>
#include"mythread.h"
int globalIndex;
QMutex globelMutex;
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyThread thread1;
thread1.setThreadName("thread1");
thread1.start();
MyThread thread2;
thread2.setThreadName("thread2");
thread2.start();
return a.exec();
}
mythread.cpp
#include "mythread.h"
extern int globalIndex;
extern Mutex globelMutex;
MyThread::MyThread()
{
}
void MyThread::setThreadName(QString name)
{
ThreadName=name;
}
void MyThread::run()
{
while(1)
{
//globelMutex.lock();
globalIndex++;
qDebug()<<ThreadName<<globalIndex<<&gloleMutex;
// globelMutex.unlock();
sleep(1);
}
}
当线程没有加锁的时候:
我们希望的是每个线程给index+1,把持好节奏访问同一段公共资源,需要Mutex
准备锁:
在什么情况下会用到锁?
第一种情况:是设备的IO设备 socket写一个东西(多线程保证按照顺序来写)
第二种情况:全部变量,多个线程访问一个全局变量。
QMutex globelMutex;
修改run
void MyThread::run()
{
while(1)
{
globelMutex.lock();
globalIndex++;
qDebug()<<ThreadName<<globalIndex<<&gloleMutex;
globelMutex.unlock();
sleep(1);
}
}
锁这{}锁必须是同一把锁:所以这里用了globelMutex