【Java】多线程前置知识 初识Thread

devtools/2024/11/13 9:09:04/

多线程前置知识 & 初识Thread

  • 冯诺依曼体系结构
    • 初步认识
    • 存储设备
    • CPU
    • 指令
  • 操作系统
  • 进程/任务
    • 进程是什么
    • 进程的管理
    • 进程的调度
    • 虚拟内存地址
    • 进程间的通信
  • 线程
    • 线程的出现
    • 线程是什么
    • 线程可能出现的问题
    • 线程与进程的联系和区别
  • 协程
  • 初识Thread类
    • Thread类是什么
    • Thread初步使用

冯诺依曼体系结构

初步认识

冯诺依曼体系结构是当今计算机的基本结构, 当今的大多数计算机都是能够基本符合这个结构的, 它的大体图示如下

在这里插入图片描述

它主要分为了输入设备, 存储器, 输出设备以及 CPU 这几大块, 其中我们接下来主要就需要去了解一下存储器和 CPU 相关的知识.

而输入和输出的设备, 我们就可以简单理解为像鼠标, 键盘这样的把我们操作的信息提供给计算机的, 就属于是输入设备. 而像屏幕, 音响这样的把信息传输出来的, 就是输出设备.

接下来我们就来简单了解一下另外两个部分

存储设备

现在来说, 存储设备一般有两种, 一个叫做内存, 一个叫做外存. 例如我们平常买一个手机, 可能看到说它的大小是 1T, 512G 这样的, 它描述的就是硬盘的大小, 此时就属于是一种外存. 而那些什么运行内存 8G, 4G 这样的, 就属于是内存.

那么这两个东西有什么区别呢? 其实我们它们的区别也非常简单, 通过两句话就可以概括完

  1. 内存, 访问速度相对较快, 空间相对较小, 成本较高, 断电后数据丢失
  2. 外存, 访问速度相对较慢, 空间相对较大, 成本较低, 断电后数据保存

平常, 我们运行的一些程序, 例如 QQ, 微信, 浏览器什么的, 它就是运行在内存上面的, 此时如果我们去把电脑的电给拔了, 那么此时去重新开机, 会发现这些程序都没了, 需要重新开启.

而像那些文件, 比如 word, pdf 这样的, 假如我们把它存储到了一个文件夹后, 那么无论我怎么重启电脑, 它依旧都躺在那个文件夹里面, 这些内存就是存储在外存上面的.


外存的访问速度, 相对于内存来说是更慢的, 比如下面是一个参考的各级硬件的执行速度比较

在这里插入图片描述

上面的Read from Memory就是指从内存中读取数据, 而下面Read from disk则是指从硬盘中读取数据. 可以看到, 它们之间的差距, 差不多是 100 倍的差距. 也就是说, 我读取一次硬盘, 就差不多相当于读 100 次内存, 这个差距还是相当恐怖的.

总而言之, 我们目前就只需要知道下面几件事情就行:

  1. 我们的软件是运行在内存上面的, 我们的文件之类的都是存储在硬盘上面的
  2. 硬盘的读取速度, 远远慢于内存
  3. 断电后, 硬盘的数据不会丢失, 内存中的数据会丢失

CPU

CPU, 又被称作是中央处理器. 可以说是当今计算机中最重要的组件, CPU 在一个计算机中的地位就和大脑在人身体中的地位类似, 没有 CPU 电脑就没有办法运行. 那么 CPU 到底是执行了什么工作呢? 它为什么如此重要?

实际上, 我们的操作系统, 包括这些软件, 都是由一条一条的指令组成的, 而这些指令, 如果没有执行者, 那么就是一条条静态的数据罢了, 没有任何的作用.

而 CPU 就是这些指令的执行者, 它负责去疯狂的执行各个软件的指令, 从而让我们的软件能够逐个的运行起来. 那此时可能有人就要问了: 那我电脑上这么多软件, 全都是靠 CPU 一个人来顶, 它顶的住吗?

实际上, 当今的 CPU 的运行速度, 已经到达了一种非常恐怖的程度, 例如我们可以在任务管理器中, 看到我们 CPU 的一些属性

在这里插入图片描述

可以看到, 它这里有一个速度, 以及右边还有一个基准速度, 这个实际上就是去描述 CPU 的执行速度的, 那么它是什么意思呢?

这里我们不进行复杂的介绍, 我们就简单的理解为这个是 CPU 一秒钟执行的指令数即可. 如果严格来说其实并不是这样的, 但是差别不会很大, 如果想要了解这部分知识, 那么可以去了解一下 CPU 的时钟频率和时钟周期.

那么如果我们以简化的方式理解, 那么这个 CPU 这里就是一秒钟可以执行 30 亿条指令, 这个数字是非常恐怖的. 对于去执行那么一些简单的程序来说, 可以说是绰绰有余了.


既然提到了 CPU, 那么就不得不提到另外一个话题, 就是多核 CPU 的诞生. 在过去, 这些 CPU 都是只有单个核心的(核心就可以理解为是一个能够完成一些运算功能的集合体, 里面由很多运算器组成, 完成指令执行, 简易运算操作). 在最开始的时候, 都是通过提高 CPU 内部电路的集成程度, 去提高 CPU 的运行速度. 随着集成程度的提高, 那么此时自然那些运算单元就也需要去变得更小, 但是小到一定程度之后, 那么此时进一步去缩小难度就会比较高了.

那为了能够进一步的提升 CPU 的运行速度, 此时就不再从核心本身下手, 而是从核心的数目下手. 就好比有一个活, 一个人干到极限了, 没办法再让它继续干了, 此时我就多叫几个人.

随后 CPU 的核心数目就逐渐的从单核慢慢变多, 例如什么 4 核, 8 核, 甚至一些高端服务器 CPU 还有 64 核, 128核这样的.

指令

上面提到 CPU 时, 我们提到了一个指令的概念, 那么指令到底是什么东西呢?

指令本质上其实也是一串二进制数字, 但是它能够去告诉 CPU 去执行什么操作, 以及 CPU 执行操作所需要的一些资源, 例如我想要让 CPU 去执行一个加法指令, 那么此时我就会通过指令的前面一部分告诉 CPU, 这是一个加法操作, 然后其他部分就去给 CPU 提供操作数. 这个提供的过程, 既可以是直接提供操作数, 也可以提供地址或者寄存器, 然后让 CPU 去自己读取.

此时可能有人就要问了: 寄存器是什么呢?

实际上, 寄存器是相较于内存来说更加小的一个存储器, 相应的, 它的访问速度也比内存还要更快. 它一般就用于存储一些中间数据这样的, 例如执行一个连续的加法10 + 20 + 30 + 40, 那么 CPU 就可以先去执行10 + 20, 然后把结果30存到寄存器里面, 随后读取下一个数后, 再把它从寄存器里面取出来继续相加.

操作系统

初识操作系统

目前市面上还是有非常多操作系统的, 例如 Windows, Linux, macOS 等等. 它们虽然使用场景不同, 叫法不同, 但是他们的功能都是比较一致的, 就是用于去管理硬件和软件的设备.

对上要去给软件提供稳定的运行环境, 对下要去管理各式各样的硬件设备.

在这里插入图片描述

对于软件来说, 主要就是实现程序之间的隔离. 例如在我们的电脑上, 如果说 QQ 突然崩溃了, 那么此时他不会影响到另外一边正常运行的微信. 不能说我的某一个程序爆炸了后, 就和连锁一样导致我的其他所有程序全部爆炸了, 那这样用户体验自然是很差的.

而对下管理硬件设备, 实际上也是需要通过一系列手段去管理的. 因为市面上有各式各样的硬件, 我们就以鼠标为例, 有一些鼠标, 它可能除了可以左键右键滚轮, 甚至还可以有什么侧键, 调整DPI这样的操作, 那么此时操作系统怎么认识这些额外的操作呢?

我们需要知道的是, 操作系统本质上也是一个管理软件, 它也不可能认识世界上的所有硬件设备, 因此即使一些常见功能它会支持, 但总有遇到不认识的设备的时候. 那么此时就需要靠生产硬件的厂商, 去提供一个名为驱动程序的软件, 告诉操作系统这是什么设备, 可以有什么功能, 从而让操作系统认识它的特定功能, 从而完成对这个硬件设备的控制.

内核态和用户态

我们上面提到了操作系统会提供给软件一个稳定的运行环境, 但是实际上这个运行的环境也是有一些区分的. 其中最核心的一个区别就是内核态和用户态的区别.

内核态顾名思义, 就是操作系统的核心部分, 一些操作系统的核心功能, 就是通过这里去提供的. 例如一些去操作底层硬件设备的功能, 此时操作系统就可以通过提供一些可以操作内核的 API, 使得程序员可以通过 API 去进行一些例如操作硬盘, 网卡这样的功能.

而用户态, 则是用于去运行一些基本应用的, 一般来说, 内核态都需要去给这些运行在用户态的软件提供一系列支持.

进程/任务

进程是什么

进程要解释起来比较抽象, 不过我们可以直接通过一个方式直观了解一下进程. 实际上如果你对任务管理器比较熟悉, 那么其实你应该每天都能看到进程

在这里插入图片描述

进程, 如果不严格的说, 可以说是一个运行起来的程序.

但是实际上我们从上面的这个图片也可以看出, 这个谷歌浏览器一个程序就对应了非常多的进程. 因此严格的来说, 进程和运行中的程序并不是一个一一对应的关系, 而可能是一个多对一的关系.

同时, 这里我们还需要提到程序的这个概念, 程序并不是我们上面看到的这个运行中的程序, 而是那种躺在文件资源管理器里面的那种可执行文件, 如下所示
在这里插入图片描述

程序是一个静态的概念, 与进程不同, 进程是一个动态的概念.


同时, 我们能够在任务管理器中看到, 每一个进程都是消耗了一定的资源的, 例如 CPU, 硬盘, 内存之类的

在这里插入图片描述

实际上, 一个进程如果想要运行, 那么就需要操作系统去分配一定的系统资源去给他运行, 就类似于一个员工, 你得给他发点工资, 他才会给你干活一样.

而这些资源的分配, 都是以每一个进程为单位的. 因此也可以说, 进程是系统资源分配的基本单位.

进程的管理

如果在任务管理器中上下观察一下, 会发现我们的操作系统中, 有一大堆的进程在运行. 那么在我们的操作系统中, 这么多进程在运行, 那么操作系统是如何进行管理的呢?

首先, 操作系统内部有一个结构体来去描述进程的属性, 这个结构体也叫做进程控制块(PCB, Process Control Block), 这个进程控制块就是用来描述进程的属性的, 一个进程可能使用一个 PCB 也可能使用多个 PCB

接下来就是组织进程, 我们的操作系统会使用一种类似于双向链表的数据结构来组织 PCB. 如果新增进程, 那么就会在这个链表中插入一个进程, 如果关闭进程, 那么就会进行对应的删除操作.


那么PCB是如何描述进程的呢? 我们这里就先了解 PCB 中的一部分内容

  1. pid:

pid是进程的身份表示, 每一时刻每一个进程都会有自己独特的pid. 例如我们在任务管理器中就可以看到进程的pid

在这里插入图片描述

在这里插入图片描述

  1. 内存指针:

通过任务管理器我们可以看到, 每一个进程运行的时候, 都需要一定的内存去运行, 而这个内存指针就是用来描述进程占据的内存区域的.

在这里插入图片描述

为了描述一个进程的内存占用, 会用一组内存指针去描述存储专用数据的区域, 存储指令的区域, 存储临时数据的区域等. 简单的说, 内存指针就是用于去描述进程占用的内存资源的.

  1. 文件描述符表:

一个进程如果要涉及文件操作, 实际上就是要去操作硬盘. 这个文件描述符表就是用于描述进程关联了哪些文件, 要操作哪些文件. 简单的说, 文件描述符表就是用来描述进程占用的硬盘资源.

那么此时可能有要问了, 之前我们讲了那么多 CPU 的东西, 那 CPU 的资源占用, 又是如何去描述的呢?

要提到进程占用的CPU资源要如何体现的, 那么这就不得不提到一个新的话题, 进程的调度.

进程的调度

早期的操作系统是一个单任务的操作系统, 单任务操作系统指的就是只允许同一个时刻只能有一个任务运行, 每当我们要换一个应用程序的时候, 上一个应用程序就要退出. 但是很明显现在的操作系统已经不是这样的了, 那么现在的操作系统是如何支持同时运行多个任务的呢?

首先我们要知道, CPU 是有核心的, 并且 CPU 的每一个核心, 都只能在同一个时间段内执行一个进程的指令. 就比如我现在有一个 QQ 和一个微信, 如果 QQ 占用了 CPU 的一个核, 那么微信就没有办法在这个核上面继续跑了

在这里插入图片描述

那么此时可能有人会说: 我明白了, 是因为现在CPU有多核了所以能同时执行多个任务, 只能说对了一部分但是不完全对. 因为我们现在的家用计算机, 都是差不多 10 个核心左右的. 而我们通过任务管理器可以看到, 我们的电脑上绝对是不止这么一点进程的.

在这里插入图片描述

那么就说明, 似乎一个 CPU 核心, 也是应该可以去同时运行多个进程的. 其实在早期的CPU还是单核的时候, 也有操作系统支持了多进程同时执行的效果, 那么是如何实现的呢?

实际上靠的就是进程的调度来实现的, 我们的各个程序有多条指令, 它们会以非常快的速度在CPU上轮番执行指令, 这种进程将 CPU 的核心分时复用的做法就能被称为是进程的调度. 例如我们上面举的 QQ 和微信的例子, 他们就会一直在 CPU 的一个核心上面轮转.

在这里插入图片描述

同时由于 CPU 的调度速度极快, 一秒能够执行上亿条指令, 因此我们人体是感觉不到这种调度的, 在我们的感觉上就类似于同时执行一样, 那么这种微观上非同时但宏观上看起来是同时发生的情况就被称为并发. 同时, 由于现代的 CPU 中一般会有多个核心, 因此有时候也会出现真正的同时执行的情况, 这种微观上也是同时发生的情况就被称为并行.

不过一般来说, 我们从书写程序的角度去看, 两者并没有什么很大的区别, 因此我们一般来说会将两者共同称作是并发.


那么要实现进程的调度, 自然也需要一些东西去描述相关的属性, 不能说想怎么调度就怎么调度, 因此 PCB 中也有一些相关的属性, 下面我们简单了解一下和进程调度有关的几个 PCB 属性

  1. 进程的状态:
  • 就绪状态: 运行中或者随时准备可以马上执行
  • 阻塞状态: 某个条件不具备, 导致这个进程暂时无法参与CPU的调度执行

这里的状态并不是所有的状态, 我们这里只是简单了解一下

  1. 进程的优先级:

进程的优先级会去影响 CPU 优先调度哪个进程, 例如操作系统自带的一些进程, 优先级肯定更高. 能够更好的调配系统资源

  1. 进程的上下文:

假如说有一个进程, 它从 CPU 上下来了, 那么此时它运行到哪了, 就需要去记录下来. 后续继续调度到这个进程的时候, 那么就可以从这个记录的位置继续执行, 而不是从头开始.

就类似于一些单机游戏的存档读档, 下线前保存一下, 下次上来玩直接从存好的位置继续即可, 而不是从头开始重新玩.

  1. 进程的记账信息:

记录每一个进程 CPU 资源的占用情况, 然后操作系统会根据这个信息适当调整进程的调度情况. 这个主要是为了防止进程饿死, 简单地说就是防止由于优先级导致的一个进程一直吃不到 CPU 的调度, 一直在阻塞.

虚拟内存地址

试想, 假如我们的程序能够直接去操作内存, 那么假如发生了内存越界, 此时就可能会出现问题. 如图所示

在这里插入图片描述

此时进程 1, 可能就会因为其他进程把自己的数据修改了, 随后直接报错崩溃. 这很明显是不正常的, 因此操作系统中就引入了虚拟内存地址的机制.

操作系统在分配内存给进程的时候, 都只会分配虚拟的内存. 同时将这个虚拟的地址与对应的物理地址建立一定的映射关系, 随后进程在操作的时候就会经过操作系统检查, 看看有没有在虚拟内存中越界. 确认无误后再映射到物理内存中, 从而防止某进程越界访问内存导致其他进程被影响
在这里插入图片描述

进程间的通信

什么是进程间的通信, 实际上就是指两个进程之间要产生交互. 但是由于要给进程提供一个稳定的运行环境, 操作系统又对进程进行了隔离. 那么此时如何实现进程间的通信呢?

此时我们就可以给进程去准备一些公共空间, 然后让它们在这个公共空间里面去进行交互, 就好比张三和李四想要一起玩, 但是他们的父母管的都比较严厉, 不准在家里面玩, 此时它们就可以自己约的出去外面玩.

常见的交互方式就是去通过文件或者是网络的方式去实现进程的通信, 其实实现进程间的通信并不只有这两个手段, 不过我们这里就不细致介绍了.

线程

线程的出现

引入多个进程的初心, 就是为了实现并发编程. 虽然多进程实现并发编程效果也非常理想, 但是世界上没有十全十美的东西, 进程实现并发编程也有它的缺点, 缺点就是效率不高

进程的各种操作, 例如创建, 销毁和调度, 执行效率都是比较慢的, 为什么?上面也说过进程是系统资源分配的基本单位, 进程的各个操作可能都会涉及到申请系统资源, 申请资源时就离不开分配内存, 而分配内存又是一个耗时较长的操作

那么为了解决上面这个进程申请系统资源的开销过大的问题, 就引入了一个新的概念, 线程

线程是什么

线程, 也被称作轻量级进程, 线程的所有操作都比进程的各个操作都要更快更轻量, 但是线程需要依靠进程才可以存在. 一个进程可以有一个线程, 也可以有多个线程. 一个进程在最开始的时候至少会有一个线程, 线程负责执行代码工作. 并且我们也可以根据需求创建出更多的线程, 从而实现并发编程的效果.

我们前面所谈到的调度, 都是说的调度进程, 但是实际上由于我们前面讨论的情况都是对应的一个进程只有一个线程, 因此才说是调度进程. 但是实际上调度的应该是进程的里的那个线程, 并且有一些进程是可以有多个线程的, 此时我们也是可以独立的对各个线程进行调度的.

为了能够实现线程的调度, 线程也是有 PCB 来描述的. 这也就是为什么一个进程可能有多个 PCB, 因为它有多个线程需要多个 PCB 去描述线程. 但是与进程不同的是, 线程的 pid, 内存指针, 文件描述符表都是共用的.

这也就是为什么线程的各个操作都会更加的轻量的原因, 因为相比于进程来说, 创建一个线程直接复用之前创建进程申请的系统资源即可, 省去了申请资源的开销.

那么最终我们就可以得出一个结论: 线程是调度执行的基本单位

线程可能出现的问题

操作系统为了防止进程之间产生影响, 对进程进行了隔离. 但是我们的线程本身就是存在于同一个进程里面的, 那么此时是否可能会引发一些问题呢? 当然可能, 接下来我们就来通过一个例子来了解一下, 线程可能会出现的问题.

假如现在一张桌子上面有 100 个苹果, 需要张三同学去把这 100 个苹果给吃了. 那么很明显, 此时如果它一个人去吃, 还是比较难的, 那么此时他就可以去将这 100 个苹果分到两个桌子上面, 然后再叫个人去吃另外一个桌子的苹果

在这里插入图片描述

此时的这个方式就有一点类似于多进程的方式, 很明显这样子做, 虽然加快了速度, 但是也消耗了一块额外空间. 因此张三改变策略, 直接让这个人到自己的桌子上面来一起吃

在这里插入图片描述

此时就相当于是多线程的方式了, 很明显此时即使没有消耗额外的空间, 效率也提高了. 张三一看, 这个方法行, 因此叫了更多的人来一起帮忙吃

在这里插入图片描述

但是此时人数一多, 反而又变的有一点难受了, 只有围在桌子边上的人才能直接吃到, 而在外围的人则需要去挤到桌子边上去才能吃到苹果. 此时实际上就相当于是, 线程过多, 调度的开销反而更大, 效率没有提高反而降低了.

同时, 人数一多也会引发一些其他问题, 假如有一个苹果同时被两个人给拿了. 那么此时这个苹果给谁呢?

在这里插入图片描述

此时这种两个线程在同一个资源上面产生了冲突的情况, 也被称作是线程安全问题. 这个是线程中要研究的重点问题之一.

同时, 人多起来后, 假如说有一个人一直吃不到苹果, 那么此时它就有点暴躁了, 可能就想要直接掀桌了.

在这里插入图片描述

而这个情况, 就可以看作是线程的异常, 如果线程的异常没有得到妥善的处理, 那么此时整个进程都有可能直接崩溃. 什么是妥善的处理, 例如上面在那个人暴怒前, 有人就发现了异常, 直接跟他说: 大哥消消气, 来这个位置给你, 你上去吃吧. 那么此时就不会产生问题了.

在这里插入图片描述

如果是体现在代码上面, 那就是我们去把这个异常给捕获掉, 然后根据对应的异常情况, 进行对应的处理.

线程与进程的联系和区别

上面我们提到了进程和线程两个概念, 我们这里就简单总结一下这两个概念之间的联系以及区别

  1. 进程包含了线程, 一个进程里面可以只有一个线程, 也可以有多个线程
  2. 创建进程的开销大, 创建线程的开销小
  3. 进程可以独立存在, 线程必须依赖进程存在
  4. 每个进程独享一个 PCB, 而多个线程可能会共用 PCB 中的一些属性
  5. 进程是资源分配的基本单位, 线程是调度执行的基本单位

协程

线程虽然轻量, 但是终究还是要经过 CPU 的调度, 因此还有一种更加轻量的设定, 就是协程. 它也被称作是轻量级线程, 它的调度不用经过内核态, 而是可以直接在用户态去进行调度, 因此相对于线程来说, 它又是更加轻量的一种选择

不过由于 Java 中并没有官方指定的协程, 因此这里我们就不过多介绍了, 想要进一步了解可以了解一下 Java 中的虚拟线程这个概念

初识Thread类

Thread类是什么

线程, 归根究底是操作系统中的一个概念, 那么如果我们想要操作线程, 那么就需要通过操作系统提供的一些 API 去进行操作. 而对应不同操作系统, 提供的 API 也是不相同的, 因此 Java 为了满足它的跨平台特性, 就针对操作系统提供的 API 进行了封装, 把它们变为了同一套东西.

而 Thread 类, 就是 Java 基于操作系统去封装的用于操作线程的 API. 我们只需要通过这个类, 我们就可以去控制操作系统中的线程了

Thread初步使用

我们依旧是介绍一个东西的老套路, 先写一个 Hello World 程序来试试水. 我们暂时没有必要去理解其中每一步的具体含义, 我们这里主要目的是写出第一个程序.

首先我们创建一个类, 去继承 Thread 类

java">class MyThread extends Thread {}

然后去重写里面的run()方法, 打印一个 Hello World. 这个run()方法的核心功能就是去告诉线程, 它的工作是什么. 这里它的工作就是去打印一个 Hello World

java">class MyThread extends Thread {@Overridepublic void run() {System.out.println("Hello World");}
}

最后, 我们就在main()方法中去创建这个 MyThread 类的对象, 然后调用里面的start()方法即可. start()方法用于去启动线程

java">public class Main {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();}
}

最后运行查看打印结果

在这里插入图片描述

此时我们就完成了我们多线程的第一个程序, 此时可能有人觉得这个似乎和我们之前学习的东西没有什么区别. 确实我们现在的这个简单程序, 是看不出具体的效果的, 实际上它的执行流程应该是如图所示的

在这里插入图片描述

这里我们启动 main 方法后, 首先会自动启动一个 主线程, 然后主线程再去调用这个start()方法启动 MyThread 线程, 随后 MyThread 线程再去打印这个 Hello World.

现在即使觉得有一点抽象, 难以理解也是正常的, 后续在进行深入的学习的时候, 就可以逐渐的理解了. 我们这里只是初步的了解Thread类, 并且进行第一个程序的书写, 其他更加细节的内容我们就在后续再进行介绍了.


http://www.ppmy.cn/devtools/114246.html

相关文章

数据结构——链表(短小精悍版)

使用链表结构可以克服数组链表需要预先知道数据大小的缺点 链表结构可以充分利用计算机内存空间,实现灵活的内存动态管理。 但是链表失去了数组随机读取的优点,同时链表由于增加了结点的指针域,空间开销比较大。 单向链表: 一个…

JDBC 编程

目录 JDBC 是什么 JDBC 的工作原理 JDBC 的使用 引入驱动 使用 常用接口和类 Connection Statement ResultSet 使用总结 JDBC 是什么 JDBC(Java Database Connectivity):Java数据库连接,是一种用于执行 SQL 语句的Java…

讨论人机交互研究中大语言模型的整合与伦理问题

概述 论文地址:https://arxiv.org/pdf/2403.19876.pdf 近年来,大规模语言模型发展迅速。它们给研究和教育领域带来了许多变化。这些模型也是对人机交互(HCI)研究过程的有力补充,可以分析定性和定量数据,再…

有毒有害气体检测仪的应用和性能_鼎跃安全

随着现代工业的不断发展和扩张,越来越多的企业涉及到有毒有害气体的生产、使用和处理。工业规模的扩大导致有毒有害气体的排放量增加,同时也增加了气体泄漏的风险。在发生火灾、爆炸或危险化学品泄漏等紧急事件时,救援人员需要迅速了解现场的…

【mysql】mysql中窗口函数lag()用法

在MySQL中,窗口函数LAG()可以用来访问当前行的前一行或多行的数据。这个函数通常用于分析时间序列数据,比如计算相邻行之间的差异或者获取前一个状态等。 以下是LAG()函数的基本语法: LAG(expression [, offset] [, default_value]) OVER (…

torch.embedding 报错 IndexError: index out of range in self

文章目录 1. 报错2. 原因3. 解决方法 1. 报错 torch.embedding 报错: IndexError: index out of range in self2. 原因 首先看下正常情况: import torch import torch.nn.functional as Finputs torch.tensor([[1, 2, 4, 5], [4, 3, 2, 9]]) embedd…

计算机网络-第二章【新】

目录 计算机网络文章汇总 物理层的基本概念 数据通信的基础知识 数据通信系统的模型 有关信道的几个基本概念 信道信息交互的方式: 信道的极限容量 信道能够通过的频率范围 信噪比 物理层下面的传输媒体 导引型传输媒体 双绞线 同轴电缆 光缆 非导引型…

【鸿蒙】HarmonyOS NEXT星河入门到实战6-组件化开发-样式结构重用常见组件

目录 1、Swiper轮播组件 1.1 Swiper基本用法 1.2 Swiper的常见属性 1.3 Swiper的样式自定义 1.3.1 基本语法 1.3.2 案例小米有品 2、样式&结构重用 2.1 Extend:扩展组件(样式、事件) 2.2 Styles:抽取通用属性、事件 2.3 Builder:自定义构建函数(结构、样式、事…