子类化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_OBJECT

public:
    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_OBJECT

public:
    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多线程的方法适用于后台执行长时间的耗时操作、单任务执行的、无需在线程内执行槽的情景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/46772.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【UE5 多人联机教程】03-创建游戏

效果 步骤 打开“UMG_MainMenu”&#xff0c;增加创建房间按钮的点击事件 添加如下节点 其中&#xff0c;“FUNL Fast Create Widget”是插件自带的函数节点&#xff0c;内容如下&#xff1a; “创建会话”节点指游戏成功创建一个会话后&#xff0c;游戏的其他实例即可发现&am…

NineData支持最受欢迎数据库PostgreSQL

根据在 Stack Overflow 发布的 2023 开发者调研报告中显示&#xff0c;PostgreSQL 以 45% vs 41% 的受欢迎比率战胜 MySQL&#xff0c;成为新的最受欢迎的数据库。NineData 也在近期支持了 PostgreSQL&#xff0c;用户可以在 NineData 平台上进行创建数据库/Schema、管理用户与…

gensim conherence model C_V 值与其他指标负相关BUG

在我用gensim3.8.3 conherence model分析京东评论主题模型时&#xff0c; C_V 与npmi、u_mass出现了强烈的皮尔逊负相关&#xff1a; 这些地方也反映了类似问题&#xff1a; https://github.com/dice-group/Palmetto/issues/12 https://github.com/dice-group/Palmetto/issue…

手把手教你写代码——基于控制台的学生信息管理系统(单表)

栏目介绍 本栏目专为入门java学习者设计的一些简单的入门项目&#xff0c;另有视频一步一步从零到完整代码的开发过程&#xff0c;让你从头到尾明白整个系统的实现过程&#xff0c;当你完整看完视频之后&#xff0c;完全可以自己不看教程的情况下写出一套属于你自己的代码&…

X - Transformer

回顾 Transformer 的发展 Transformer 最初是作为机器翻译的序列到序列模型提出的&#xff0c;而后来的研究表明&#xff0c;基于 Transformer 的预训练模型&#xff08;PTM&#xff09; 在各项任务中都有最优的表现。因此&#xff0c;Transformer 已成为 NLP 领域的首选架构&…

k8s部署新版elasticsearch+kibana并配置快照备份

版本:es 7.17.6 kibana 7.17.6 k8s:1.19.16 一、介绍 Elasticsearch和Kibana是一对强大的开源工具&#xff0c;通常一起使用以构建实时数据分析和可视化解决方案。 Elasticsearch: Elasticsearch是一个分布式、高性能的实时搜索和分析引擎。它构建在开源搜索引擎库Lucene之上…

【2023 年第二届钉钉杯大学生大数据挑战赛】 初赛 B:美国纽约公共自行车使用量预测分析 问题三时间序列预测Python代码分析

2023 年第二届钉钉杯大学生大数据挑战赛 初赛 B&#xff1a;美国纽约公共自行车使用量预测分析 问题三时间序列预测Python代码分析 相关链接 【2023 年第二届钉钉杯大学生大数据挑战赛】 初赛 B&#xff1a;美国纽约公共自行车使用量预测分析 问题一Python代码分析 【2023 年…

【数据结构】AVL树/红黑树

目录 1.AVL树&#xff08;高度平衡二叉搜索树&#xff09; 10.1.基本概念 10.2.实现 10.2.1.AVL树节点的定义 10.2.2.AVL树的插入 10.2.3.AVL树的旋转 1.新节点插入较高左子树的左侧---左左&#xff1a;右单旋 2.新节点插入较高右子树的右侧---右右&#xff1a;左单旋 3.新节点…

第三章 HL7 架构和可用工具 - 使用 HL7 架构结构页面

文章目录 第三章 HL7 架构和可用工具 - 使用 HL7 架构结构页面使用 HL7 架构结构页面查看文档类型列表查看消息结构查看段结构 第三章 HL7 架构和可用工具 - 使用 HL7 架构结构页面 使用 HL7 架构结构页面 通过 HL7 架构页面&#xff0c;可以导入和查看 HL7 版本 2 架构规范。…

Tensorflow benchmark 实操指南

环境搭建篇见环境搭建-CentOS7下Nvidia Docker容器基于TensorFlow1.15测试GPU_东方狱兔的博客-CSDN博客 1. 下载Benchmarks源码 从 TensorFlow 的 Github 仓库上下载 TensorFlow Benchmarks&#xff0c;可以通过以下命令来下载 https://github.com/tensorflow/benchmarks 我…

Jenkins 安装构建

一、CentOS 安装 1. 使用该存储库 sudo wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo sudo rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key 2. 安装 Java yum install fontconfig java-11-openjdk配…

【iOS】iOS持久化

文章目录 一. 数据持久化的目的二. iOS中数据持久化方案三. 数据持有化方式的分类1. 内存缓存2. 磁盘缓存SDWebImage缓存 四. 沙盒机制的介绍五. 沙盒目录结构1. 获取应用程序的沙盒路径2. 访问沙盒目录常用C函数介绍3. 沙盒目录介绍 六. 持久化数据存储方式1. XML属性列表2. P…

Appium+python自动化(二十)- Monkey(猴子)日志(超详解)

简介 日志是非常重要的&#xff0c;用于记录系统、软件操作事件的记录文件或文件集合&#xff0c;可分为事件日志和消息日志。具有处理历史数据、诊断问题的追踪以及理解系统、软件的活动等重要作用&#xff0c;在开发或者测试软系统过程中出现了问题&#xff0c;我们首先想到的…

CSDN原理等级的提升方法

文章目录 原力增长是一种积分型的社交平台&#xff0c;它有多个可以增长用户积分的渠道&#xff0c;也可以升级发付费资源&#xff0c;以下是几个主要的渠道&#xff1a;1.原创文章&#xff1a;2.回答问题&#xff1a;3.发动态&#xff1a;4.浏览文章&#xff1a;5.翻译文章&am…

Rust vs Go:常用语法对比(十)

题图来自 Rust vs. Golang: Which One is Better?[1] 182. Quine program Output the source of the program. 输出程序的源代码 package mainimport "fmt"func main() { fmt.Printf("%s%c%s%c\n", s, 0x60, s, 0x60)}var s package mainimport "fm…

qt打包发布项目

目录 QT打包发布项目正常操作流程&#xff08;不包含第三方库&#xff09;包含第三方库 QT打包发布项目 qt 版本&#xff1a;5.14.2 qtcreator 版本 4.11.1 正常操作流程&#xff08;不包含第三方库&#xff09; 首先在左下角的把项目模式从常用的Debug模式改成Release模式&…

未来行星探索希望:新型多脚机器人-团队版

机器人正在探索一个模拟的外星环境 即使一个机器人失败了&#xff0c;其余的团队成员也可以抵消它的损失。 背景 虽然探测器取得了令人难以置信的发现&#xff0c;但它们的轮子可能会拖慢它们的速度&#xff0c;而不稳定的地形可能会导致损坏。虽然没有东西可以取代“毅力号”…

linux 在excel里面找内容

linux 在excel里面找内容 背景&#xff1a;在大数据行业中&#xff0c;很多数据源是excel文件&#xff0c;但是常常会出现查找excel内容找到对应的文件&#xff0c;所以制作了简单的shell脚本方便查询对应的excel文件 查看下面精简的内容即可&#xff0c;开箱即用 shell转换 …

基于Java+SpringBoot+Vue+uniapp微信小程序实现仓储管理系统

博主介绍&#xff1a;✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

windos 服务器设置指定ip访问指定端口,其他ip不能访问

需求&#xff1a;设置指定ip访问指定端口&#xff0c;其他ip不能访问 一&#xff0c;禁止所有ip访问 需要打开IP安全策略 或者winR 输入secpol.msc 1.先创建一个ip安全策略 2.点击添加&#xff0c;不使用添加向导&#xff0c;建一个安全策略 继续点添加 二&#xff0c;放开需…