目录
线程概念及官方文档
一、线程的创建:继承方式
二、线程的创建:QObject 对象(moveToThread)
2.1 创建任务类
2.2 添加线程启动(定时器启动)
2.3 添加线程启动(start信号启动)
三、线程类的基本接口和使用
3.1启动 和终止线程
3.2 线程延迟
3.3 线程同步及通信方式
3.4 常用函数例举
四、线程释放
五、总结
线程概念及官方文档
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。在Unix System V及SunOS中也被称为轻量进程(lightweight processes),但轻量进程更多指内核线程(kernel thread),而把用户线程(user thread)称为线程。这段话摘之网络。
从下面的官方文档可以看出,如果一个线程类要支持信号和槽,那么,该类要直接或者间接的继承于QObject类,并且在类中要声明宏:Q_OBJECT
好了,不说概念了,不喜欢说概念,直接开始说干货。
一、线程的创建:继承方式
线程的创建方式:自定义一个类继承于QThread,并且 重写该类的run函数,run函数中,就是 子线程要执行任务。在创建线程后,要进行启动线程 操作,下面是例子
class WorkerThread : public QThread
{
void run() override {
while(1){
qDebug()<<"hello";
QThread::sleep(1);
}
}
};
运用的话简单,举个例子,就能看到一秒中输出一次 hello
WorkerThread *workerThread = new WorkerThread(this);
workerThread->start();//子线程要调用start启动,否则,不会执行
注意事项:文章来源:https://www.toymoban.com/news/detail-538533.html
1. 默认情况下,void run() 只执行一次,想要执行多次,要手动使用循环语句
2. 子线程一定要调用start启动,否则,不会执行
3.要想实现和其他线程通信,可使用信号的方式进行通信,后面我上传 缓冲区那篇文章会用到。
实现的步骤基本就是
1.创建一个子类继承于QThread,并且重写run函数
2.创建子线程对象
3.启动子线程 ---- start()
二、线程的创建:QObject 对象(moveToThread)
这种方式我重点讲,前面的方式对于我来说,没有这种方式好用,在开发中,我经常用这种方式。
步骤分为三步,第一步创建用于运行任务的类,第二步创建线程,并在类中添加线程,第三步启动
下面讲一下详细步骤。
2.1 创建任务类
创建一个类继承于QObject,并且声明Q_OBJECT,这是规定的。定义一个槽函数,用于线程的执行。下面用代码演示一下
class threadhid : public QObject
{
Q_OBJECT
public:
explicit threadhid(QObject *parent = nullptr);
~threadhid();
void stopRun(void); //用于退出
public:
bool m_Run ; //退出线程的标志位
public slots:
void Receive(); //用于线程循环
};
函数实现:
这是最简单的模板,具体的功能可以自己添加上去。简单说明一下参数,m_Run标志位是为了更好的退出这个槽函数,再释放线程。stopRun()函数是可以通过类接口进行释放线程,更加安全,不会随意退出,更好管理。
threadhid::threadhid(QObject *parent ): QObject(parent)
{
}
threadhid::~threadhid()
{
}
void threadhid::stopRun(void)
{
m_bRun = false;
}
void threadhid::Receive()
{
m_Run = true;
while(true) {
if(!m_Run)
{
qDebug()<<"退出";
break;
}
QThread::msleep(200);
qDebug()<<"hello 大宝犯疆土...";
}
}
2.2 添加线程启动(定时器启动)
上面是创建任务,现在是给与这个类一个线程,达到分离的效果。
第一步创建类线程
QThread * thread_Hid = new QThread;
第二步创建任务类
threadhid* USB_Handle = new threadhid() ; //创建任务类
第三步添加到线程中
USB_Handle->moveToThread(&thread_Hid);
第四步 线程释放
connect(&thread_Hid, &QThread::finished, USB_Handle, &QObject::deleteLater);
注:这段代码是用于连接一个线程对象 thread_Hid 的 finished() 信号和一个 QObject 对象 USB_Thread 的 deleteLater() 槽函数的。 在多线程编程中,线程的生命周期可能会比较复杂,如果没有正确地管理线程的生命周期,就可能会导致内存泄漏或者程序崩溃等问题。因此,Qt 提供了一些机制来帮助管理线程的生命周期,其中之一就是通过在线程结束时自动删除线程对象来确保线程对象的正确释放。 在上述代码中,当 thread_Hid 线程结束时,就会发出 finished() 信号,这个信号被连接到 USB_Thread 对象的 deleteLater() 槽函数上,这样当线程结束时,就会自动调用 USB_Thread 对象的 deleteLater() 槽函数,从而保证线程对象的正确释放。 需要注意的是,当使用 QObject::deleteLater() 槽函数时,要确保所要删除的对象是在其所属线程的事件循环中被创建的,否则 deleteLater() 函数可能不会生效。
第五步 线程启动
thread_Hid.start();
第六步 创建单次定时器启动线程任务
timerHid = new QTimer(this);
connect(timerHid, &QTimer::timeout, USB_Handle, &threadhid::Receive);
timerHid->setSingleShot(true);
timerHid->start(1000);
为了方便大家复制和我以后某一天想起,直接贴完整代码
threadhid* USB_Handle = new threadhid() ;
QThread * thread_Hid = new QThread; //线程
USB_Handle->moveToThread(&thread_Hid);添加到线程中
//线程释放
connect(&thread_Hid, &QThread::finished, USB_Handle, &QObject::deleteLater);
thread_Hid.start();//线程启动
//定时器单次运行
timerHid = new QTimer(this);
connect(timerHid, &QTimer::timeout, USB_Handle, &threadhid::Receive);
timerHid->setSingleShot(true);
timerHid->start(1000);
2.3 添加线程启动(start信号启动)
为什么说这个呢,就是想简单的记录一下多种启动方式,上面的那种是我自己用的,我觉得可以让时间更加可靠,而下面这种是一启动就运行的。2.3和2.2效果是一样,不用重复写。
connect(thread_Hid, &QThread::started, USB_Handle, &threadhid::Receive); // 连接线程槽
thread_Hid->start(); //启动
三、线程类的基本接口和使用
3.1启动 和终止线程
3.1.1退出线程
void quit() //退出一个线程循环,可以起作 停止线程
3.1.2 启动线程
start(QThread::Priority priority = InheritPriority)//启动线程
3.1.3 退出线程
terminate()//终止线程
区别:
terminate() 函数是一个强制终止程序的函数,它可以立即终止应用程序并释放相关资源,但这种方式可能会导致一些未定义的行为,例如可能会导致未处理的异常和内存泄漏等问题。因此,建议只在出现紧急情况时使用 terminate() 函数。
quit() 函数是一个优雅地终止程序的函数,它会告诉事件循环停止运行,并等待所有线程结束后再退出应用程序。它会释放所有的资源,包括 GUI 界面等。因此,建议在正常情况下使用 quit() 函数来终止应用程序。
3.2 线程延迟
3.3 线程同步及通信方式
同步机制的目的是为了保护数据或者代码段,在多线程中,每次只允许一个线程来进行访问。线程同步方式:互斥锁 读写锁 信号量等
QMutex、QMutexLocker, QReadWriteLock, QSemaphore, and QWaitCondition.具体用法就不说了,复制一下百度,这次重点介绍线程。
通信方式:
1.信号和槽
2.QMetaObject::invokeMethod():可以在不同线程之间异步地调用一个函数,实现线程间的通信。
3.QThread::event()和QCoreApplication::postEvent():可以用于在线程之间发送事件和处理事件。
3.4 常用函数例举
-
start() 函数:用于启动线程,它会调用线程的 run() 函数,并将线程标记为“运行中”。
-
run() 函数:是线程的主函数,可以在该函数中执行需要在线程中运行的代码。
-
wait() 函数:用于等待线程结束,它会使当前线程阻塞,直到目标线程结束为止。
-
quit() 函数:用于终止线程,它会发送一个退出信号给线程,并等待线程结束后才返回。
-
isRunning() 函数:用于检查线程是否正在运行中。
-
setPriority() 函数:用于设置线程的优先级,Qt 支持以下几种优先级:IdlePriority、LowestPriority、LowPriority、NormalPriority、HighPriority、HighestPriority、TimeCriticalPriority。
-
sleep() 函数:用于使当前线程睡眠一段时间,单位是毫秒。
-
msleep() 函数:用于使当前线程睡眠一段时间,单位是微秒。
-
yieldCurrentThread() 函数:用于将 CPU 时间让给其他线程。
-
currentThreadId() 函数:用于获取当前线程的 ID。
-
event() 函数:用于处理事件,QThread 中的事件有两种:QThread::CustomEventType 和 QThread::Quit。
-
exit() 函数:用于退出线程,并释放线程所占用的资源。
-
finished() 信号:当线程结束时会发出该信号,可以通过该信号来实现线程的清理工作。
需要注意的是,在多线程编程中,要避免使用一些不安全的操作,如访问全局变量、使用未初始化的指针、使用不可重入的函数等,否则可能会导致程序出现不可预知的错误。在多线程编程中需要特别注意线程安全问题。
四、线程释放
线程的释放也是跟重要的,作为一个合格的工程师不能只管申请不管释放,有始有终对吧。
第一步释放类内资源,并退出死循环
第二步退出事件循环
第三步等待线程退出
第四步释放内存
USB_Handle->stopRun(); //先退出死循环 m_Run = false;
thread_Hid.quit(); //退出事件循环
thread_Hid.wait(); //等待线程退出
// QThread::terminate()//也可以,但是上面的方式更加温柔
delete thread_Hid;
delete USB_Handle ;
大功告成。
五、总结
-
QThread
可以通过继承QThread
类并重写run
函数来实现自定义线程类。 -
QThread
还提供了信号槽机制,使得线程之间的通信更加方便。 -
QThread
通过exec
函数实现事件循环,当线程没有事件时,exec
函数会阻塞线程。
注意事项:
- 在
QThread
中创建的对象默认情况下都会与QThread
线程关联,应该使用moveToThread
函数将其移动到新的线程中。 - 不应该直接调用
QThread
的run
函数,应该通过start
函数来启动线程。 -
QThread
对象应该在主线程中创建,并且只能在主线程中删除。 - 为了避免资源泄露,应该在线程执行完毕后将其删除。
Qt线程的总结暂时就这么多,我写的博客偏向于实际应用型,概念不多强调,只为更加方便的使用,减少废话。如果本篇给与你开发或多或少的帮助,希望可以给个点赞激励一下哈。文章来源地址https://www.toymoban.com/news/detail-538533.html
到了这里,关于QT创建线程的方法:一步步教你创建和启动线程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!