子类化QThread来实现多线程,moveToThread函数的作用
子类化QThread来实现多线程, QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里。正确启动线程的方法是调用QThread::start()来启动。
一、步骤
- 子类化 QThread;
- 重写run,将耗时的事件放到此函数执行;
- 根据是否需要事件循环,若需要就在run函数中调用 QThread::exec() ,开启线程的事件循环。事件循环的作用可以查看往期《QThread源码浅析》文章中《QThread::run()源码》小节进行阅读;
- 为子类定义信号和槽,由于槽函数并不会在新开的线程运行,所以需要在构造函数中调用 moveToThread(this)。 注意:虽然调用moveToThread(this)可以改变对象的线程依附性关系,但是QThread的大多数成员方法是线程的控制接口,QThread类的设计本意是将线程的控制接口供给旧线程(创建QThread对象的线程)使用。所以不要使用moveToThread()将该接口移动到新创建的线程中,调用moveToThread(this)被视为不好的实现。
接下来会通过 使用线程来实现计时器,并实时在UI上显示 的实例来说明不使用事件循环和使用事件循环的情况。(此实例使用QTimer会更方便,此处为了说明QThread的使用,故使用线程来实现)
二、不使用事件循环实例
InheritQThread.hpp
class InheritQThread:public QThread
{Q_OBJECT
public:InheritQThread(QObject *parent = Q_NULLPTR):QThread(parent){}void StopThread(){QMutexLocker lock(&m_lock);m_flag = false;}protected://线程执行函数void run(){qDebug()<<"child thread = "<<QThread::currentThreadId();int i=0;m_flag = true;while(1){++i;emit ValueChanged(i); //发送信号不需要事件循环机制QThread::sleep(1);{QMutexLocker lock(&m_lock);if( !m_flag )break;}}}signals:void ValueChanged(int i);public:bool m_flag;QMutex m_lock;
};
mainwindow.hpp
class MainWindow : public QMainWindow
{Q_OBJECT
public:explicit MainWindow(QWidget *parent = nullptr) :QMainWindow(parent),ui(new Ui::MainWindow){ui->setupUi(this);qDebug()<<"GUI thread = "<<QThread::currentThreadId();WorkerTh = new InheritQThread(this);connect(WorkerTh, &InheritQThread::ValueChanged, this, &MainWindow::setValue);}~MainWindow(){delete ui;}public slots:void setValue(int i){ui->lcdNumber->display(i);}private slots:void on_startBt_clicked(){WorkerTh->start();}void on_stopBt_clicked(){WorkerTh->StopThread();}void on_checkBt_clicked(){if(WorkerTh->isRunning()){ui->label->setText("Running");}else{ui->label->setText("Finished");}}private:Ui::MainWindow *ui;InheritQThread *WorkerTh;
};
在使用多线程的时候,如果出现共享资源使用,需要注意资源抢夺的问题,例如上述InheritQThread类中m_flag变量就是一个多线程同时使用的资源,上面例子使用 QMutexLocker+QMutex 的方式对临界资源进行安全保护使用,其实际是使用了 RAII技术:(Resource Acquisition Is Initialization),也称为“资源获取就是初始化”,是C++语言的一种管理资源、避免泄漏的惯用法。C++标准保证任何情况下,已构造的对象最终会销毁,即它的析构函数最终会被调用。简单的说,RAII 的做法是使用一个对象,在其构造时获取资源,在对象生命期控制对资源的访问使之始终保持有效,最后在对象析构的时候释放资源。具体 QMutexLocker+QMutex 互斥锁的原理以及使用方法,在这里就不展开说了,这个知识点网上有很多非常好的文章。
效果:
(1)在不点【start】按键的时候,点击【check thread state】按钮检查线程状态,该线程是未开启的。

(2)按下【start】后效果如下,并查看终端消息打印信息:


只有调用了QThread::start()后,子线程才是真正的启动,并且只有在run()函数才处于子线程内。
(3)我们再试一下点击【stop】按钮,然后检查线程的状态:

点击【stop】按钮使 m_flag = false, 此时run函数也就可以跳出死循环,并且停止了线程的运作,之后我们就不能再次使用该线程了,也许有的人说,我再一次start不就好了吗?再一次start已经不是你刚才使用的线程了,这是start的是一个全新的线程。到此子类化 QThread ,不使用事件循环的线程使用就实现了,就这么简单。
三、使用事件循环实例
run函数中的 while 或者 for 循环执行完之后,如果还想让线程保持运作,后期继续使用,那应该怎么做?
可以启动子线程的事件循环,并且使用信号槽的方式继续使用子线程。注意:一定要使用信号槽的方式,否则函数依旧是在创建QThread对象的线程执行。
- 在run函数中添加QThread::exec()来启动事件循环。(注意: 在没退出事件循环时,QThread::exec()后面的语句都无法被执行,退出后程序会继续执行其后面的语句);
- 为QThread子类定义信号和槽;
- 在QThread子类构造函数中调用 moveToThread(this)(注意: 可以实现构造函数在子线程内执行,但此方法不推荐,更好的方法会在后期的文章进行介绍)。
接着上述的实例,在InheritQThread类构造函数中添加并且调用moveToThread(this);在run函数中添加exec();并定义槽函数:
/**************在InheritQThread构造函数添加moveToThread(this)**********/
InheritQThread(QObject *parent = Q_NULLPTR):QThread(parent){moveToThread(this); }/**************在InheritQThread::run函数添加exec()***************/
void run(){qDebug()<<"child thread = "<<QThread::currentThreadId();int i=0;m_flag = true;while(1){++i;emit ValueChanged(i);QThread::sleep(1);{QMutexLocker lock(&m_lock);if( !m_flag )break;}}exec(); //开启事件循环}/************在InheritQThread类中添加QdebugSlot()槽函数***************/
public slots:void QdebugSlot(){qDebug()<<"QdebugSlot function is in thread:"<<QThread::currentThreadId();}
在MainWindow类中添加QdebugSignal信号;在构造函数中将QdebugSignal信号与InheritQThread::QdebugSlot槽函数进行绑;添加一个发送QdebugSignal信号的按钮:
/**********在MainWindow构造函数中绑定信号槽******************/
explicit MainWindow(QWidget *parent = nullptr) :QMainWindow(parent),ui(new Ui::MainWindow){qDebug()<<"GUI thread = "<<QThread::currentThreadId();ui->setupUi(this);WorkerTh = new InheritQThread(this);connect(WorkerTh, &InheritQThread::ValueChanged, this, &MainWindow::setValue);connect(this, &MainWindow::QdebugSignal, WorkerTh, &InheritQThread::QdebugSlot); //绑定信号槽
}/********MainWindow类中添加信号QdebugSignal槽以及按钮事件槽函数**********/
signals:void QdebugSignal(); //添加QdebugSignal信号
private slots://按钮的事件槽函数void on_SendQdebugSignalBt_clicked(){emit QdebugSignal();}
实现事件循环的程序已修改完成,来看下效果:
(1)在运行的时候为什么会出现以下警告?
QObject::moveToThread: Cannot move objects with a parent
我们看到MainWindow类中是这样定义InheritQThread类对象的:WorkerTh = new InheritQThread(this)。如果需要使用moveToThread()来改变对象的依附性,其创建时不能够带有父类。将语句改为:WorkerTh = new InheritQThread()即可。
(2)修改完成后,点击【start】启动线程,然后点击【stop】按钮跳出run函数中的while循环,最后点击【check thread state】按钮来检查线程的状态,会是什么样的情况呢?

由上图可以看到,线程依旧处于运行状态,这是因为run函数中调用了exec(),此时线程正处于事件循环中。
(3)接下来再点击【Send QdebugSignal】按钮来发送QdebugSignal信号。

由终端的打印信息得知,InheritQThread::QdebugSlot槽函数是在子线程中执行的。
四、子类化QThread线程的信号与槽

从上图可知,事件循环是一个无止尽循环,事件循环结束之前,exec()函数后的语句无法得到执行。只有槽函数所在线程开启了事件循环,它才能在对应信号发射后被调用。无论事件循环是否开启,信号发送后会直接进入槽函数所依附的线程的事件队列,然而,只有开启了事件循环,对应的槽函数才会在线程中得到调用。下面通过几种情况来验证下:
(1)代码和《三、使用事件循环》小节的代码一样,然后进行如下的操作:点击【start】按钮->再点击【Send QdebugSignal】按钮,这个时候槽函数会不会被执行呢?


这种情况无论点多少次发送QdebugSignal信号,InheritQThread::QdebugSlot槽函数都不会执行。因为当前线程还处于while循环当中,如果需要实现槽函数在当前线程中执行,那么当前线程就应该处于事件循环的状态,也就是正在执行exec()函数。所以如果需要InheritQThread::QdebugSlot槽函数执行,就需要点击【stop】按钮退出while循环,让线程进入事件循环。
(2)在《三、使用事件循环》小节的代码基础上,把InheritQThread::run函数删除,然后进行如下的操作:点击【start】启动线程->点击【stop】按钮跳出run函数中的while循环进入事件循环->点击【Send QdebugSignal】按钮来发送QdebugSignal信号,会有什么结果呢?
结果会和上面第一种情况一样,虽然信号已经在子线程的事件队列上,但是由于子线程没有事件循环,所以槽函数永远都不会被执行。
(3)在上面《三、使用事件循环》小节的代码基础上,将InheritQThread构造函数中的 moveToThread(this) 去除掉。进行如下操作:点击【start】启动线程->点击【stop】按钮跳出run函数中的while循环进入事件循环->点击【Send QdebugSignal】按钮来发送QdebugSignal信号,会有什么结果呢?

由上图可以看出InheritQThread::QdebugSlot槽函数居然是在GUI主线程中执行了。因为InheritQThread对象我们是在主线程中new出来的,如果不使用moveToThread(this)来改变对象的依附性关系,那么InheritQThread对象就是属于GUI主线程,根据connect信号槽的执行规则,最终槽函数会在对象所依赖的线程中执行。信号与槽绑定的connect函数的细节会在后期的《跨线程的信号槽》文章进行单独介绍。
五、如何正确退出线程并释放资源
InheritQThread类的代码不变动,和上述的代码一样:
#ifndef INHERITQTHREAD_H
#define INHERITQTHREAD_H
#include <QThread>
#include <QMutex>
#include <QMutexLocker>
#include <QDebug>class InheritQThread:public QThread
{Q_OBJECTpublic:InheritQThread(QObject *parent = Q_NULLPTR):QThread(parent){moveToThread(this);}void StopThread(){QMutexLocker lock(&m_lock);m_flag = false;}protected://线程执行函数void run(){qDebug()<<"child thread = "<<QThread::currentThreadId();int i=0;m_flag = true;while(1){++i;emit ValueChanged(i);QThread::sleep(1);{QMutexLocker lock(&m_lock);if( !m_flag )break;}}exec();}signals:void ValueChanged(int i);public slots:void QdebugSlot(){qDebug()<<"QdebugSlot function is in thread:"<<QThread::currentThreadId();}public:bool m_flag;QMutex m_lock;
};#endif // INHERITQTHREAD_H
MainWindow类添加ExitBt、TerminateBt两个按钮,用于调用WorkerTh->exit(0)、WorkerTh->terminate()退出线程函数。由往期《QThread源码浅析》文章中《QThread::quit()、QThread::exit()、QThread::terminate()源码》小节得知调用quit和exit是一样的,所以本处只添加了ExitBt按钮:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include "ui_mainwindow.h"
#include "InheritQThread.h"
#include <QThread>
#include <QDebug>namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr) :QMainWindow(parent),ui(new Ui::MainWindow){qDebug()<<"GUI thread = "<<QThread::currentThreadId();ui->setupUi(this);WorkerTh = new InheritQThread();connect(WorkerTh, &InheritQThread::ValueChanged, this, &MainWindow::setValue);connect(this, &MainWindow::QdebugSignal, WorkerTh, &InheritQThread::QdebugSlot);}~MainWindow(){delete ui;}signals:void QdebugSignal();public slots:void setValue(int i){ui->lcdNumber->display(i);}private slots:void on_startBt_clicked(){WorkerTh->start();}void on_stopBt_clicked(){WorkerTh->StopThread();}void on_checkBt_clicked(){if(WorkerTh->isRunning()){ui->label->setText("Running");}else{ui->label->setText("Finished");}}void on_SendQdebugSignalBt_clicked(){emit QdebugSignal();}void on_ExitBt_clicked(){WorkerTh->exit(0);}void on_TerminateBt_clicked(){WorkerTh->terminate();}private:Ui::MainWindow *ui;InheritQThread *WorkerTh;
};#endif // MAINWINDOW_H
运行上述的例程,点击【start】启动线程按钮,然后直接点击【exit(0)】或者【terminate()】,这样会直接退出线程吗?
点击【exit(0)】按钮(猛点)

点击【terminate()】按钮(就点一点)

由上述情况我们可以看到上面例程的线程启动之后,无论怎么点击【exit(0)】按钮,线程都不会退出,点击【terminate()】按钮的时候就会立刻退出当前线程。由往期《QThread源码浅析》文章中《QThread::quit()、QThread::exit()、QThread::terminate()源码》小节可以得知,若使用QThread::quit()、QThread::exit()来退出线程,该线程就必须要在事件循环的状态(也就是正在执行exec()),线程才会退出。而QThread::terminate()不管线程处于哪种状态都会强制退出线程,但这个函数存在非常多不安定因素,不推荐使用。我们下面来看看如何正确退出线程。
(1)如何正确退出线程?
- 如果线程内没有事件循环,那么只需要用一个标志变量来跳出run函数的while循环,这就可以正常退出线程了。
- 如果线程内有事件循环,那么就需要调用QThread::quit()或者QThread::exit()来结束事件循环。像刚刚举的例程,不仅有while循环,循环后面又有exec(),那么这种情况就需要先让线程跳出while循环,然后再调用QThread::quit()或者QThread::exit()来结束事件循环。如下:

注意:尽量不要使用QThread::terminate()来结束线程,这个函数存在非常多不安定因素。
(2)如何正确释放线程资源?
退出线程不代表线程的资源就释放了,退出线程只是把线程停止了而已,那么QThread类或者QThread派生类的资源应该如何释放呢?直接 delete QThread类或者派生类的指针吗?当然不能这样做,千万别手动delete线程指针,手动delete会发生不可预料的意外。理论上所有QObject都不应该手动delete,如果没有多线程,手动delete可能不会发生问题,但是多线程情况下delete非常容易出问题,那是因为有可能你要删除的这个对象在Qt的事件循环里还排队,但你却已经在外面删除了它,这样程序会发生崩溃。 线程资源释放分为两种情况,一种是在创建QThread派生类时,添加了父对象,例如在MainWindow类中WorkerTh = new InheritQThread(this)让主窗体作为InheritQThread对象的父类;另一种是不设置任何父类,例如在MainWindow类中WorkerTh = new InheritQThread()。
- 1、创建QThread派生类,有设置父类的情况:
这种情况,QThread派生类的资源都让父类接管了,当父对象被销毁时,QThread派生类对象也会被父类delete掉,我们无需显示delete销毁资源。但是子线程还没结束完,主线程就destroy掉了(WorkerTh的父类是主线程窗口,主线程窗口如果没等子线程结束就destroy的话,会顺手把WorkerTh也delete这时就会奔溃了)。 注意:这种情况不能使用moveToThread(this)改变对象的依附性。 因此我们应该把上面MainWindow类的析构函数改为如下:
~MainWindow(){WorkerTh->StopThread();//先让线程退出while循环WorkerTh->exit();//退出线程事件循环WorkerTh->wait();//挂起当前线程,等待WorkerTh子线程结束delete ui;
}
- 2、创建QThread派生类,没有设置父类的情况:
也就是没有任何父类接管资源了,又不能直接delete QThread派生类对象的指针,但是QObject类中有 void QObject::deleteLater () [slot] 这个槽,这个槽非常有用,后面会经常用到它用于安全的线程资源销毁。我们通过查看往期《QThread源码浅析》文章中《QThreadPrivate::start()源码》小节可知线程结束之后会发出 QThread::finished() 的信号,我们将这个信号和 deleteLater 槽绑定,线程结束后调用deleteLater来销毁分配的内存。
在MainWindow类构造函数中,添加以下代码:
connect(WorkerTh, &QThread::finished, WorkerTh, &QObject::deleteLater)
~MainWindow()析构函数可以把 wait()函数去掉了,因为该线程的资源已经不是让主窗口来接管了。当我们启动线程之后,然后退出主窗口或者直接点击【stop】+【exit()】按钮的时候,会出现以下的警告:
QThread::wait: Thread tried to wait on itself
QThread: Destroyed while thread is still running
为了让子线程能够响应信号并在子线程执行槽函数,我们在InheritQThread类构造函数中添加了 moveToThread(this) ,此方法是官方极其不推荐使用的方法。那么现在我们就遇到了由于这个方法引发的问题,我们把moveToThread(this)删除,程序就可以正常结束和释放资源了。那如果要让子线程能够响应信号并在子线程执行槽函数,这应该怎么做?在下一期会介绍一个官方推荐的《子类化QObject+moveToThread》的方法。
六、小结
- QThread只有run函数是在新线程里;
- 如果必须需要实现在线程内执行槽的情景,那就需要在QThread的派生类构造函数中调用moveToThread(this),并且在run函数内执行QThread::exec()开启事件循环;(极其不推荐使用moveToThread(this),下一期会介绍一种安全可靠的方法)
- 若需要使用事件循环,需要在run函数中调用QThread::exec();
- 尽量不要使用terminate()来结束线程,可以使用bool标志位退出或者在线程处于事件循环时调用QThread::quit、QThread::exit来退出线程;
- 善用QObject::deleteLater来进行内存管理;
- 在QThread执行start函数之后,run函数还未运行完毕,再次start,不会发生任何结果;
- 子类化QThread多线程的方法适用于后台执行长时间的耗时操作、单任务执行的、无需在线程内执行槽的情景。
相关文章:
子类化QThread来实现多线程,moveToThread函数的作用
子类化QThread来实现多线程, QThread只有run函数是在新线程里的,其他所有函数都在QThread生成的线程里。正确启动线程的方法是调用QThread::start()来启动。 一、步骤 子类化 QThread;重写run,将耗时的事件放到此函数执行&#…...
经典面试题(力扣,接雨水)
接雨水 方法一思路测试代码复杂度测试结果 方法二思路测试代码复杂度测试结果 给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。 示例1: 输入:height [0,1,0,2,1,0,1,3,2,1,2,1]…...
2023年深圳杯数学建模C题无人机协同避障航迹规划
2023年深圳杯数学建模 C题 无人机协同避障航迹规划 原题再现: 平面上A、B两个无人机站分别位于半径为500 m的障碍圆两边直径的延长线上,A站距离圆心1 km,B站距离圆心3.5 km。两架无人机分别从A、B两站同时出发,以恒定速率10 m/s…...
PostgreSQL--实现数据库备份恢复详细教学
前言 这是我在这个网站整理的笔记,关注我,接下来还会持续更新。 作者:RodmaChen PostgreSQL--实现数据库备份恢复详细教学 一. 数据库备份二. 数据库恢复三. 存留问题 数据库备份恢复功能是每个产品所需的,以下是简单的脚本案例&a…...
JDK工具之jstack说明
JDK工具之jstack说明 前言什么是jstack?如何使用jstack?获取Java进程的PID分析jstack输出 常用的jstack命令选项jstack的应用场景结论 前言 作为Java开发人员,在开发和维护复杂的Java应用程序时,我们经常会遇到各种各样的问题&am…...
34 | 牛顿迭代法
文章目录 牛顿迭代法一、原理二、Python实现三、练习题四、总结牛顿迭代法 一、原理 牛顿迭代法(Newton’s Method)是一种用于寻找方程的实根的数值方法。其基本思想是通过一系列逼近来求解方程的根。对于方程 f ( x ) = 0 f(x) = 0 f(x...
ChatGPT如何帮助学生学习
一些教育工作者担心学生可能使用ChatGPT作弊。因为这个AI工具能写报告和计算机代码,画出复杂图表……甚至已经有许多学校把ChatGPT屏蔽。 研究发现,学生作弊的主要原因是想考得好。是否作弊与作业和考试的打分方式有关,所以这与技术的便…...
easyexcel导出excel-50行代码搞定大量数据导出
文章目录 一、写在前面二、使用步骤定义导出的数据实体导出 一、写在前面 场景: 当数据量导出过大时如果一次从数据库取出所有数据会导致内存飙升导致系统奔溃,所以我们采取循环读取和循环写入。 准备: mave导入:easyexcel:3.0.5 二、使用…...
OpenAI宣布安卓版ChatGPT正式上线;一站式 LLM底层技术原理入门指南
🦉 AI新闻 🚀 OpenAI宣布安卓版ChatGPT正式上线 摘要:OpenAI今日宣布,安卓版ChatGPT已正式上线,目前美国、印度、孟加拉国和巴西四国的安卓用户已可在谷歌Play商店下载,并计划在下周拓展到更多地区。Chat…...
Rust vs Go:常用语法对比(二)
21. Swap values 交换变量a和b的值 a, b b, a package mainimport "fmt"func main() { a : 3 b : 10 a, b b, a fmt.Println(a) fmt.Println(b)} 103 fn main() { let a 3; let b 10; let (a, b) (b, a); println!("a: {a}, b: {b}", aa,…...
对于Vue3的一些思考
看完 Vue Hooks: 让Vue开发更简单与高效 - 掘金 一些小心得 vue3: 组合式API(是利用架构,强制达到拆分目的) 达到解耦的目的。对于vue3来说 每个模块的每个逻辑都是 一个一个独立的方法。通过 方法方法整体业务 代码风格&#…...
Bean的生命周期 - spring
前言 本篇介绍了Bean的生命周期,认识PostConstruct注释,PreDestroy注释,如有错误,请在评论区指正,让我们一起交流,共同进步! 文章目录 前言1. spring生命周期2. Bean的生命周期3. 为什么先设置…...
入门Linux基本指令(2)
这篇文章主要提供一些对文件操作的Linux基本指令,希望对大家有所帮助,三连支持! 目录 cp指令(复制) mv指令(剪切) nano指令 cat指令(打印文件内容) > 输出重定向 >> 追加重定向 < 输入重定向 more指令 less指令(推荐) …...
【C++】【自用】选择题 刷题总结
文章目录 【类和对象】1. 构造、拷贝构造的调用2. 静态成员变量3. 初始化列表4. 成员函数:运算符重载5. 友元函数、友元类55. 特殊类设计 【细节题】1. 构造 析构 new \ deletet、new[] \ delete[] 【类和对象】 1. 构造、拷贝构造的调用 #include using namespace…...
SkyWalking链路追踪-Collector(收集器)
Collector(收集器) SkyWalking的Collector(收集器)是SkyWalking链路追踪的核心组件之一。它负责接收来自各个Agent的追踪数据,并将其存储到数据存储器(如数据库)中。具体来说,Colle…...
typescript自动编译文件实时更新
npm install -g typescripttsc --init 生成tsconfig.json配置文件 tsc -w 在监听模式下运行,当文件发生改变的时候自动编译...
qt6.5 download for kali/ubuntu ,windows (以及配置选项选择)
download and sign in qt官网 sign in onlion Install 1 2 3 4 5...
【JS 原型链】
JavaScript 原型链是一个重要的概念,它是 JavaScript 语言实现面向对象编程的核心。在 JavaScript 中,每个对象都有一个与之关联的原型,并且该对象继承了原型中的属性和方法。这些原型组成了一个原型链,可以通过该链追溯到顶层的 …...
harmonyOS 开发之UI开发(ArkTS声明式开发范式)概述
UI开发(ArkTS声明式开发范式)概述 基于ArkTS的声明式开发范式的方舟开发框架是一套开发极简、高性能、支持跨设备的UI开发框架,提供了构建OpenHarmony应用UI所必需的能力,主要包括: ArkTS ArkTS是UI开发语言ÿ…...
【人工智能】神经网络、M-P_神经元模型、激活函数、神经网络结构、学习网络参数、代价定义、总代价
M-P_神经元模型、激活函数、神经网络结构、学习网络参数、代价定义 文章目录 M-P_神经元模型、激活函数、神经网络结构、学习网络参数、代价定义M-P 神经元模型激活函数(Activation function)神经网络结构举例训练神经网络学习网络参数代价定义均方误差交叉熵(Cross Entropy)…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
MMaDA: Multimodal Large Diffusion Language Models
CODE : https://github.com/Gen-Verse/MMaDA Abstract 我们介绍了一种新型的多模态扩散基础模型MMaDA,它被设计用于在文本推理、多模态理解和文本到图像生成等不同领域实现卓越的性能。该方法的特点是三个关键创新:(i) MMaDA采用统一的扩散架构…...
HBuilderX安装(uni-app和小程序开发)
下载HBuilderX 访问官方网站:https://www.dcloud.io/hbuilderx.html 根据您的操作系统选择合适版本: Windows版(推荐下载标准版) Windows系统安装步骤 运行安装程序: 双击下载的.exe安装文件 如果出现安全提示&…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
【生成模型】视频生成论文调研
工作清单 上游应用方向:控制、速度、时长、高动态、多主体驱动 类型工作基础模型WAN / WAN-VACE / HunyuanVideo控制条件轨迹控制ATI~镜头控制ReCamMaster~多主体驱动Phantom~音频驱动Let Them Talk: Audio-Driven Multi-Person Conversational Video Generation速…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
