QT 学习笔记(十七)

news2025/1/20 4:41:04

文章目录

  • 一、多线程简介
    • 1. 基础知识
    • 2. 多线程的优缺点及注意事项
  • 二、多线程详解
    • 1. 背景案例
    • 2. 通过多线程对背景案例进行优化
    • 3. 方法一:多线程的创建使用(QT 4.7 以前)
      • 3.1 方法一的创建步骤
      • 3.2 方法一的具体实现及实现代码
    • 4. 方法二:多线程的创建使用(QT 4.7 及以后)
      • 4.1 方法二的创建步骤
      • 4.2 方法二的具体实现
      • 4.3 方法二的实现代码

由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见QT 学习笔记(一)
提示:具体项目准备工作和细节讲解见QT 学习笔记(二)

一、多线程简介

1. 基础知识

  • (1) 进程是操作系统结构的基础;是一个正在执行的程序;计算机中正在运行的程序实例;可以分配给处理器并由处理器执行的一个实体;由单一顺序的执行显示,一个当前状态和一组相关的系统资源所描述的活动单元。
  • (2) 线程是程序中一个单一的顺序控制流程。是程序执行流的最小单元。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
  • (3) 多线程是在单个程序中同时运行多个线程完成不同的工作。

2. 多线程的优缺点及注意事项

  • 通常情况下,应用程序都是在一个线程中执行操作。但是,当调用一个耗时操作(例如,大批量 I/O 或大量矩阵变换等 CPU 密集操作)时,用户界面常常会冻结。而使用多线程可以解决这一问题。
  • 多线程程序有以下几个优点:
  • (1) 提高应用程序响应速度。这对于图形界面开发的程序尤为重要,当一个操作耗时很长时,整个系统都会等待这个操作,程序就不能响应键盘、鼠标、菜单等操作,而使用多线程技术可将耗时长的操作置于一个新的线程,避免以上问题。
  • (2) 使多 CPU 系统更加有效。当前线程数不大于 CPU 数目时,操作系统可以调度不同的线程运行于不同的 CPU 上。
  • (3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为独立或半独立的运行部分,这样有利于代码的理解和维护。
  • (4) 和进程相比,线程是一种非常花销小,切换快的多任务操作方式。运行于一个进程中的多个线程,它们彼此之间使用相同的地址空间,共享大部分数据,启动一个线程所花费的空间远远小于启动一个进程所花费的空间,而且,线程间彼此切换所需的时间也远远小于进程间切换所需要的时间。
  • (5) 线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进程下的线程之间共享数据空间,所以一个线程的数据可以直接为其它线程所用,这不仅快捷,而且方便。
  • 多线程程序有以下几个缺点:
  • (1) 多线程程序的行为无法预期,当多次执行程序时,每一次的结果都可能不同。
  • (2) 多线程的执行顺序无法保证,它与操作系统的调度策略和线程优先级等因素有关。
  • (3) 多线程的切换可能发生在任何时刻、任何地点。
  • (4) 多线程对代码的敏感度高,对代码的细微修改都可能产生意想不到的结果。
  • 多线程使用过程中注意事项:
  • (1) 线程不能操作 ui 对象(从 QWidget 直接或间接派生的窗口对象)
  • (2) 需要移动到子线程中处理的模块类,创建的对象的时候不能指定父对象。

二、多线程详解

  • 生成一个新的项目,具体步骤过程见提示。
  • 在生成新项目的过程中,我们选择基类为 QWidget ,这是因为 QWidget 当中比较干净,不存在别的东西,而 QMainWindow 虽然也可以实现,但其中存在工具栏,菜单栏,核心控件等东西,比较复杂。

1. 背景案例

  • 在 QT 中使用 QThread 来管理线程。下面来看一个简单的例子:
  • 首先,我们在 ui 界面布置出所需要的窗口界面,包含一个 Display Widgets 当中的 LCD Number,并将其放大,这里需要注意的是,LCD Number 是有范围限定的,是 5 位,超过 5 位的数字就无法显示,一个按钮,用以启动 LCD Number。具体界面布局如下图所示。

在这里插入图片描述

  • 在整个过程当中,需要使用到定时器 QTimer。因此,先进行头文件的编写和变量的声明。在实现的过程当中不选择使用 Lambda 表达式,使用传统的槽函数(定时器的槽函数没有参数和返回值)进行功能的实现。
  • 定时器 QTimer 功能完成后,在程序界面对按钮进行转到槽函数操作,并通过按钮完成定时器 QTimer 的启动。
  • 当我们完成代码的编写后,点击启动按钮。此时,ui 界面会无法操作,LCD Number 没有任何变化,具体实现现象如下图所示。

在这里插入图片描述

  • 使用 QThread 中的 sleep 函数,让程序等待 5s,我们现在目前只有一个主线程,所以在点击按钮之后会造成定时器虽然设置了,但是 LCD Number 的显示数字是不会改变的,因为 sleep 了 5s 所以说需要等 5s 之后才会开始变化。
  • 如果 sleep 换成一个数据处理的函数时候,在数据处理函数执行的这段时间,其余的程序无法运行,会造成窗口卡住,无响应等问题,影响他人的正常使用。

2. 通过多线程对背景案例进行优化

  • 我们的主界面有一个用于显示时间的 LCD Number 数字面板还有一个用于启动任务的按钮。
  • 程序的目的是用户点击按钮,开始一个非常耗时的运算(程序中我们以程序界面睡眠 5s 来替代这个非常耗时的工作,在真实的程序中,这可能是一个网络访问,可能是需要复制一个很大的文件或者其它任务)。
  • 同时 LCD Number 开始显示逝去的毫秒数。毫秒数通过一个定时器 QTimer 进行更新。计算完成后,计时器停止。
  • 背景案例当中的是一个很简单的应用,也看不出有任何问题。但是当我们开始运行程序时,问题就来了:点击按钮之后,程序界面直接停止响应,直到结束后才开始重新更新。
  • 通过这个问题,我们决定这里使用多线程进行解决。这是因为 QT 中所有界面都是在 ui 线程中(也被称为主线程,就是执行了 QApplication::exec() 的线程),在这个线程中执行耗时的操作(,就会阻塞 ui 线程,从而让界面停止响应。
  • 所以,为了避免这一问题,我们要使用 QThread 开启一个新的线程。
  • 具体多线程的创建使用有如下两种方法。

3. 方法一:多线程的创建使用(QT 4.7 以前)

  • 方法一这里直接在背景案例的基础上进行修改。

3.1 方法一的创建步骤

在这里插入图片描述

  • (1) 自定义一个类,继承于 QThread,并且只有一个线程处理函数(和主线程不再同一个线程),这个线程处理函数就是重写父类中的 run 函数。
  • (2) 线程处理函数里面写入需要执行的复杂数据处理。
  • (3) 启动线程不能直接调用 run 函数,需要使用对象来调用 start 函数实现线程启动。
  • (4) 线程处理函数执行结束后可以定义一个信号来告诉主线程。
  • (5) 最后关闭线程。

3.2 方法一的具体实现及实现代码

  • 这里我们对背景案例进行优化,由多线程的创建步骤可知,需要添加一个 C++ 文件和类。

在这里插入图片描述

  • 基类选择 QObject,这里注意千万不要选成 QWidget,因为我们的线程并不是控件。

在这里插入图片描述

  • 在新建完成后,由于我们是要新建一个线程,也就是 QThread。在此,我们对刚刚生成的 QObject 进行修改,将其改为 QThread
  • 由于线程号是有限的,因此当我们使用完线程号后要及时关闭线程。
  • 在完成上述准备工作和代码编写后,具体实现现象和实现代码如下所示
  • (1) 实现现象
  • 按下按钮 start 后,开始计时,至于为什么是在 45 的时候停止的,是由于中间过程启动时间导致的。

在这里插入图片描述

  • 在完成计时后,输出 it is over。

在这里插入图片描述

  • (2) 主窗口头文件 widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTimer> //定时器头文件
#include "mythread.h" //线程头文件

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

    void dealtimeout(); //定时器槽函数
    void dealDone(); //线程结束槽函数
    void stopthread(); //停止线程槽函数

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    QTimer *mytimer; //声明变量
    mythread *thread; //线程对象
};

#endif // WIDGET_H

  • (3) 主窗口源文件 widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QThread>
#include <QDebug>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    mytimer = new QTimer(this);

    //只要定时器启动,自动触发timeout信号
    connect(mytimer,&QTimer::timeout,this,&Widget::dealtimeout);

    //分配空间,指定父对象
    thread = new mythread(this);

    connect(thread,&mythread::isDone,this,&Widget::dealDone);
    
    //当按窗口右上角关闭按钮X时,窗口触发destroyed()信号
    connect(this,&mythread::destroyed,this,&Widget::stopthread);
}

void Widget::stopthread()
{
    //停止线程
    thread->quit();
    
    //等待线程完成当前工作
    thread->wait();
}

void Widget::dealDone()
{
    qDebug() << "it is over";
    mytimer->stop(); //关闭定时器
}

void Widget::dealtimeout()
{
    static int i = 0;
    i++;

    //设定LCD的值
    ui->lcdNumber->display(i);

}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    if(mytimer->isActive() == false)
    {
        //如果定时器没有工作
        mytimer->start(100);
    }

    //启动线程,处理数据
    thread->start();
}

  • (4) 子线程头文件 mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QThread>

//自定义一个类重写线程处理函数
class mythread : public QThread
{
    Q_OBJECT
public:
    explicit mythread(QObject *parent = nullptr);

protected:
    //QThread的虚函数
    //线程处理函数
    //不能直接调用,通过start间接调用
    void run();

signals:
    //自定义一个线程处理函数指向完成后的一个信号
    void isDone();

public slots:
};

#endif // MYTHREAD_H

  • (5) 子线程源文件 mythread.cpp
#include "mythread.h"

mythread::mythread(QObject *parent) : QThread(parent)
{

}

void mythread::run()
{
    //非常复杂的数据处理
    //需要耗时5s
    QThread::sleep(5);

    emit isDone();
}

4. 方法二:多线程的创建使用(QT 4.7 及以后)

  • 生成一个新的项目,具体步骤过程见提示。
  • 在生成新项目的过程中,我们选择基类为 QWidget ,这是因为 QWidget 当中比较干净,不存在别的东西,而 QMainWindow 虽然也可以实现,但其中存在工具栏,菜单栏,核心控件等东西,比较复杂。
  • 此方法比方法一更为复杂一些。

4.1 方法二的创建步骤

在这里插入图片描述

  • (1) 自定义一个类,只需要继承 QObject 即可,并且线程处理函数名字随便取,但是也只有一个线程处理函数。
  • (2) 创建一个自定义线程类的对象,不能指定父对象。
  • (3) 创建一个 QThread 类的对象,可以指定父对象。
  • (4) 将自定义线程对象加入到 QThread 类的对象,使用。
  • (5) 启动线程的时候要注意:启动 QThread 类的对象线程,调用 start 函数只是启动了线程,但是没有开启线程处理函数,线程处理函数的开启需要用到信号槽机制。
  • (6) 关闭线程。

4.2 方法二的具体实现

  • 其主要特点就是利用 QT 的事件驱动特性,将需要在次线程中处理的业务放在独立的模块(类)中,由主线程创建完该对象后,将其移交给指定的线程,且可以将多个类似的对象移交给同一个线程。
  • 首先,我们在 ui 界面布置出所需要的窗口界面,包含一个 Display Widgets 当中的 LCD Number,并将其放大,两个按钮,用以启动和停止 LCD Number。具体界面布局如下图所示。

在这里插入图片描述

  • 这里我们对背景案例进行优化,由多线程的创建步骤可知,需要添加一个 C++ 文件和类。

在这里插入图片描述

  • 基类选择 QObject,这里注意千万不要选成 QWidget,因为我们的线程并不是控件。

在这里插入图片描述

  • 在新建完成后,不同于方法一,它本身就是继承父类,因此,不需要将 QObject 修改为 QThread
  • 由于线程号是有限的,因此当我们使用完线程号后要及时关闭线程。
  • 如果我们要是有信号和槽,必须有如下图所示的宏。

在这里插入图片描述

  • 我们每隔 1s 发送一个信号,通过设置一个 while(1) 死循环,在其中调用 QThreadsleep 函数,每睡眠 1s 之后调用信号,如此往复。
  • 然后,在 ui 界面通过按钮 start 进行转到槽操作,启动定时器,由于我们每隔 1s 钟会发送一个信号,因此我们对这些信号进行处理,表明定时器已经正常启动。
  • 这里需要注意的是不能直接调用线程处理函数,直接调用线程处理函数会导致,线程处理函数和主线程在同一个线程。
  • start() 函数只是启动了线程,但是没有开启线程处理函数,线程处理函数的开启需要用到信号槽机制。
  • 完成启动定时器按钮的编写后,运行程序,点击按钮 start,每隔 1s 定时器计数加一,同时发送一个子线程号,具体实现现象如下图所示。

在这里插入图片描述

  • 然后,在 ui 界面通过按钮 close 进行转到槽操作,停止定时器,并回收线程资源(线程号是有限的)。
  • 正常的关闭线程是使用 quit() 函数,但该函数比较温柔,会让线程先完成当前工作再停止,由于我们这里是 while(1) 的死循环,因此,这种关闭线程的方法是不可取的。
  • 完成关闭定时器按钮的编写后,运行程序,当我们点击按钮 close 后,定时器会立刻停止工作,同时子线程号也不再发送,具体实现现象如下图所示。

在这里插入图片描述

  • 此时,当我们关闭 ui 界面时,会出现 QThread: Destroyed while thread is still running,表明我们的线程仍在继续工作,因此,我们通过信号和槽函数对这种现象进行修改。
  • 知识点补充:关于 QObject 类的 connect函数第五个参数(只在多线程当中才有意义),连接类型有自动,直接和队列三种。
  • (1) 自动连接(AutoConnection),默认的连接方式。
  • 如果信号与槽,也就是发送者与接受者在同一线程,等同于直接连接;
  • 如果发送者与接受者处在不同线程,等同于队列连接。
  • (2) 直接连接(DirectConnection)
  • 当信号发射时,槽函数立即直接调用。
  • 无论槽函数所属对象在哪个线程,槽函数总在发送者所在线程执行。
  • (3) 队列连接(QueuedConnection)
    当控制权回到接受者所在线程的事件循环时,槽函数被调用。槽函数在接受者所在线程执行。
  • 知识点补充:总结如下。
  • 队列连接:槽函数在接受者所在线程执行。
  • 直接连接:槽函数在发送者所在线程执行。
  • 自动连接:二者不在同一线程时,等同于队列连接

4.3 方法二的实现代码

  • (1) 主窗口头文件 widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QThread>
#include "mythread.h"

namespace Ui {
class Widget;
}

class Widget : public QWidget
{
    Q_OBJECT

public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

    void dealsignal(); //处理信号槽函数

signals:
    void startthread(); //启动子线程的信号
    void dealclose();

private slots:
    void on_pushButtonstart_clicked();

    void on_pushButton_2_clicked();

private:
    Ui::Widget *ui;    
    mythread *myt;    
    QThread *thread;
};

#endif // WIDGET_H

  • (2) 主窗口源文件 widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

Widget::Widget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::Widget)
{
    ui->setupUi(this);

    //动态分配空间,不能指定父对象
    myt = new mythread;

    //创建子线程,指定父对象
    thread = new QThread(this);

    //把自定义的线程加入到子线程中
    myt->moveToThread(thread);

    connect(myt,&mythread::mysignal,this,&Widget::dealsignal);

    qDebug() << "主线程号:" << QThread::currentThread();

    connect(this,&Widget::startthread,myt,&mythread::mytimeout);

    connect(this,&Widget::destroyed,this,&Widget::dealclose);
    
    //线程处理函数内部,不允许操作图形界面
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButtonstart_clicked()
{
    if(thread->isRunning() == true)
    {
        return;
    }
    
    //启动线程,但是没有启动线程处理函数
    thread->start();
    myt->setflag(false);

    //不能直接调用线程处理函数
    //直接调用线程处理函数会导致,线程处理函数和主线程在同一个线程
    //只能通过 signal - slot 方式调用
    emit startthread();
}

void Widget::dealsignal()
{
    static int i = 0;
    i++;
    ui->lcdNumber->display(i);
}


void Widget::on_pushButton_2_clicked()
{
    if(thread->isRunning() == false)
    {
        return;
    }
    
    myt->setflag(true);
    thread->quit();
    thread->wait();
}

void Widget::dealclose()
{
    myt->setflag(true);
    thread->quit();
    thread->wait();   
}

  • (3) 子线程头文件 mythread.h
#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>

class mythread : public QObject
{
    Q_OBJECT
public:
    explicit mythread(QObject *parent = nullptr);

    //线程处理函数
    void mytimeout();

    void setflag(bool flag = true); //isstop函数的外部接口

signals:
    void mysignal();

public slots:

private:
    bool isstop;
};

#endif // MYTHREAD_H

  • (4) 子线程源文件 mythread.cpp
#include "mythread.h"
#include <QThread>
#include <QDebug>

mythread::mythread(QObject *parent) : QObject(parent)
{
    isstop = false;
}

void mythread::mytimeout()
{
    while(isstop == false)
    {
        QThread::sleep(1);
        emit mysignal();

        qDebug() << "子线程号:" << QThread::currentThread();

        if(true == isstop)
        {
            break;
        }
    }
}

void mythread::setflag(bool flag)
{
    isstop = flag;
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Android Hook之Frida安装使用

目录Frida安装安装frida-serverfrida-server配置和启动Frida Hook实例1&#xff1a;实例2&#xff1a;Frida 常用命令Frida 是一个动态检测框架&#xff0c;允许开发人员在 Windows、macOS、Linux、iOS 和 Android 上的原生应用程序中注入 JavaScript 或 Python 脚本。该框架可…

Spring复习(二)

FactoryBean FactoryBean是一个接口&#xff0c;需要创建一个类实现该接口 package com.mao.pojo;import org.springframework.beans.factory.FactoryBean;public class StudentFactoryBean implements FactoryBean {//getObject方法将对象交给Spring容器来管理Overridepubli…

Go web开发

使用 Gin-Plus 框架快速进行 web 开发 一、前言 安装 Get go get github.com/archine/gin-plus/v2v2.0.0Mod # go.mod文件加入下面的一条 github.com/archine/gin-plus/v2 v2.0.0# 命令行在该项目目录下执行 go mod tidy二、项目使用 1、启动入口 定义 main 文件&#xf…

来自一位双非本科大二学生的?自我救赎:堕落——蜕变

回望2022&#xff1a;获得&#xff1f;&#xff1a;失去&#xff1b;开心&#xff1f;&#xff1a;难过&#xff1b;伤心&#xff1f;&#xff1a;释怀 先提一句&#xff0c;总结2022&#xff0c;有后悔、不甘&#xff1b;有开心、感动&#xff1b;2022结束&#xff0c;更多的是…

springboot 2.6.6读不到nacos上的配置文件

搭建了一个微服务项目 spring boot 版本&#xff1a;2.6.6 spring cloud 版本&#xff1a;2021.0.1 spring cloud alibaba 版本&#xff1a;2021.0.1.0 遇到的问题无论是在application.yml还是在bootstrap.properties属性文件里配置服务中心相关的的信息都不能读取nacos上的…

LeetCode[215]数组中的第K个最大元素

难度&#xff1a;中等题目&#xff1a;给定整数数组 nums和整数 k&#xff0c;请返回数组中第 k个最大的元素。请注意&#xff0c;你需要找的是数组排序后的第 k个最大的元素&#xff0c;而不是第 k个不同的元素。你必须设计并实现时间复杂度为 O(n)的算法解决此问题。示例 1:输…

2.5(完结)C语言重点解剖内存管理函数要点速记

1.指针如果有具体的指向&#xff0c;包括野指针&#xff0c;对应的合法性&#xff0c;我们是无法验证的。 2.所有的指针如果没有被直接使用&#xff0c;必须设置为NULL。 3.在函数内部&#xff0c;要验证指针的合法性&#xff0c;本质上是验证指针!NULL。 4.自定义函数传入指…

Odoo 16 企业版手册 - 库存管理之到岸成本

到岸成本 到岸成本可以定义为买方在购买时应支付的产品总价。这包括运输、关税、手续费、服务费、运费等。为了获得产品的销售价格&#xff0c;这些额外费用被添加到产品的实际成本中。根据公司政策&#xff0c;不同公司的到岸成本计算方法将有所不同。在Odoo 库存模块中&#…

JAVA中的for循环使用方法

一. 循环结构1. 概念在学习Java里的循环之前&#xff0c;我们先来了解一下到底什么是循环&#xff0c;以及循环的作用。我们先来看下面这张图&#xff1a;大家想一下&#xff0c;我们在400米的跑道上参加万米长跑&#xff0c;正常情况下要跑25圈&#xff0c;这25圈每一圈的跑步…

FPGA:逻辑运算及逻辑门

文章目录逻辑变量与逻辑函数逻辑运算基本逻辑运算及对应的逻辑门&#xff11;.与运算与逻辑举例状态表与真值表与逻辑符号与逻辑表达式与门电路&#xff12;. 或运算或逻辑举例电路状态表状态表与真值表或逻辑符号或逻辑表达式或门电路3. 非运算非逻辑举例电路状态表状态表与真…

Lichee_RV学习系列---认识Lichee Rv Dock、环境搭建和编译第一个程序

Lichee Rv Dock学习系列文章目录 文章目录Lichee Rv Dock学习系列文章目录前言一、认识Lichee RV1、D1-H 芯片2、Lichee RV开发板3、系统镜像二、Lichee RV 固件烧录1、要求基本硬件2、基本资料下载3、固件烧录在这里插入图片描述三、连接上开发板1、ADB方式连接a&#xff1a;A…

【ART-Pi学习FreeRTOS】使用STM32CubeIDE搭建开发环境

【ART-Pi】使用STM32CubeIDE搭建开发环境STM32CubeIDESTM32CubeIDE新建工程进入芯片选型界面HSE和LSE时钟源设置时钟系统&#xff08;时钟树&#xff09;配置Generate CodeSTM32CubeIDE STM32CubeIDE安装比较简单&#xff0c;这里略过&#xff0c;可以百度搜索相关教程 STM32…

Excel 中选择行的快捷方式有哪些

在处理不同类型的数据时,用户需要多次选择任意行或多行。在这种情况下,任何人都必须知道选择行的快捷方式。了解一些快捷键,如在Excel中选择行,使任何人都能熟练使用MS Excel,而且这对任何人以更智能的方式工作都很有帮助。当一个人开始使用快捷方式执行他的/她的工作时,…

Spring与SpringMVC的区别和联系是啥?

Spring Spring是一个开源容器框架&#xff0c;可以接管web层&#xff0c;业务层&#xff0c;dao层&#xff0c;持久层的组件&#xff0c;并且可以配置各种bean,和维护bean与bean之间的关系。其核心就是控制反转(IoC),和面向切面(AOP),简单的说就是一个分层的轻量级开源框架。 …

餐饮巨头被攻击的“致命”48小时

编者按数字化浪潮蓬勃兴起&#xff0c;企业面临的安全挑战亦日益严峻。腾讯安全近期将复盘2022年典型的攻击事件&#xff0c;帮助企业深入了解攻击手法和应对措施&#xff0c;完善自身安全防御体系。本篇是第八期&#xff0c;讲述了某餐饮巨头遭遇黑客多轮次攻击、全国点餐系统…

Linux ALSA 之六:ALSA ASoc 架构

ALSA ASoc 架构一、ASOC 由来二、从 HW 角度三、从 SW 角度四、重要数据结构关联图1、基于 Linux 3.0 数据结构图2、基于 Linux 4.0 数据结构图一、ASOC 由来 ASoC–ALSA System on Chip&#xff0c;是建立在标准 ALSA 驱动层上&#xff0c;为了更好地支持嵌入式处理器和移动设…

软件著作权申请注意事项

​软著申请需要哪些材料 ①企业申请&#xff1a;企业营业执照副本复印件&#xff08;加盖公章&#xff09;、身份证复印件&#xff08;正反面&#xff09;。 个人申请&#xff1a;个人身份证复印件。 ②软件设计/使用说明书文档一份 文档可根据要求提供&#xff0c;可以是软件…

c#使用配置文件

在我们开发软件的时候&#xff0c;有时候有很多的配置文件&#xff0c;可以把配置的参数保存到本地&#xff0c;那么肯定要对文件进行读和写的操作&#xff0c;使用SharpConfig可以很简单的实现这个功能。 下面是GitHub的介绍。 https://codeload.github.com/cemdervis/Sharp…

常见的锁策略

文章目录一、常见的锁策略乐观锁 vs 悲观锁轻量级锁 vs 重量级锁自旋锁 vs 挂起等待锁互斥锁 vs 读写锁公平锁 vs 非公平锁可重入锁 vs 不可重入锁二、CAS原子类实现自旋锁ABA问题一、常见的锁策略 我们这里所介绍到的锁策略&#xff0c;不仅仅是java中的&#xff0c;任何涉及…

异常检测-缺陷检测-论文精读PaDiM

Abstract 我们提出了一个新的 Patch 分布建模框架&#xff0c;在单类学习的设置下&#xff0c;PaDiM 同时检测和定位图像中的异常。PaDiM 利用一个预先训练好的卷积神经网络 (CNN) 进行 patch 嵌入&#xff0c;利用多元高斯分布得到正常类的概率表示。它还利用了 CNN 的不同语…