操作系统--Linux内核进程间的通信方式

每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间是每个进程都共享的,所以进程之间要通信必须通过内核。


一、管道

  • 匿名管道:它没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中,shell 命令中的「|」竖线就是匿名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失。
  • 命名管道(FIFO):突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。

管道传输数据是单向的,如果想相互通信,我们需要创建两个管道才行。

管道这种通信方式效率低,不适合进程间频繁地交换数据。好处就是简单,我们很容易得知管道里的数据已经被另一个进程读取了。

1.匿名管道创建

一个进程里匿名管道的创建,返回两个描述符。

  • 管道的读取端描述符 fd[0],管道的写入端描述符 fd[1]
  • 注意,这个匿名管道是特殊的文件,只存在于内存,不存于文件系统中。
  • 所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。
  • 管道传输的数据是无格式的流且大小受限。

两个进程里(父子进程)匿名管道的创建

           

使用 fork 创建子进程,创建的子进程会复制父进程的文件描述符

两个进程各有两个「 fd[0]fd[1]」,两个进程就可以通过各自的 fd 写入和读取同一个管道文件实现跨进程通信了。

管道只能一端写入,另一端读出,所以上面这种模式容易造成混乱,因为父进程和子进程都可以同时写入,也都可以读出。那么,为了避免这种情况,通常的做法是:

  • 父进程关闭读取的 fd[0],只保留写入的 fd[1];
  • 子进程关闭写入的 fd[1],只保留读取的 fd[0];

两个进程里(非父子进程)匿名管道的创建

在 shell 里面执行 A | B命令的时候,A 进程和 B 进程都是 shell 创建出来的子进程,A 和 B 之间不存在父子关系,它俩的父进程都是 shell。

所以说,在 shell 里通过「|」匿名管道将多个命令连接在一起,实际上也就是创建了多个子进程,那么在我们编写 shell 脚本时,能使用一个管道搞定的事情,就不要多用一个管道,这样可以减少创建子进程的系统开销。

对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。

对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。



二、消息队列

  • 消息队列的通信模式可以解决“管道的通信方式效率低,因此不适合进程间频繁地交换数据”的问题。比如,A 进程要给 B 进程发送消息,A 进程把数据放在对应的消息队列后就可以正常返回了,B 进程需要的时候再去读取数据就可以了。同理,B 进程要给 A 进程发送消息也是如此。
  • 消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送方和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
  • 消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。
  • 消息队列通信不足:一是通信不及时,二是附件也有大小限制
  • 消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限。在 Linux 内核中,会有两个宏定义 MSGMAXMSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。
  • 消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。


三、共享内存

消息队列的读取和写入的过程,都会有发生用户态与内核态之间的消息拷贝过程。共享内存的方式,就很好的解决了这一问题。

现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。

共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。



四、信号量

共享内存通信方式会导致:如果多个进程同时修改同一个共享内存,很有可能就冲突了。为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。

信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据

信号量表示资源的数量,控制信号量的方式有两种原子操作:

  •  P 操作:这个操作会把信号量减去 1,相减后如果信号量 < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 >= 0,则表明还有资源可使用,进程可正常继续执行。
  • V 操作:这个操作会把信号量加上 1,相加后如果信号量 <= 0,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0,则表明当前没有阻塞中的进程;

P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。

信号初始化为 1,就代表着是互斥信号量,它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。

具体的过程如下:

  • 进程 A 在访问共享内存前,先执行了 P 操作,由于信号量的初始值为 1,故在进程 A 执行 P 操作后信号量变为 0,表示共享资源可用,于是进程 A 就可以访问共享内存。
  • 若此时,进程 B 也想访问共享内存,执行了 P 操作,结果信号量变为了 -1,这就意味着临界资源已被占用,因此进程 B 被阻塞。
  • 直到进程 A 访问完共享内存,才会执行 V 操作,使得信号量恢复为 0,接着就会唤醒阻塞中的线程 B,使得进程 B 可以访问共享内存,最后完成共享内存的访问后,执行 V 操作,使信号量恢复到初始值 1。

信号初始化为 0,就代表着是同步信号量,它可以保证进程 A 应在进程 B 之前执行。

具体过程:

  • 如果进程 B 比进程 A 先执行了,那么执行到 P 操作时,由于信号量初始值为 0,故信号量会变为 -1,表示进程 A 还没生产数据,于是进程 B 就阻塞等待;
  • 接着,当进程 A 生产完数据后,执行了 V 操作,就会使得信号量变为 0,于是就会唤醒阻塞在 P 操作的进程 B;
  • 最后,进程 B 被唤醒后,意味着进程 A 已经生产了数据,于是进程 B 就可以正常读取数据了。


五、信号

上面说的进程间通信,都是常规状态下的工作模式。对于异常情况下的工作模式,就需要用「信号」的方式来通知进程。

在 Linux 操作系统中, 为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过 kill -l 命令,查看所有的信号:

信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令):

运行在 shell 终端的进程,我们可以通过键盘输入某些组合键的时候,给进程发送信号。例如:

  • Ctrl+C 产生 SIGINT 信号,表示终止该进程;
  • Ctrl+Z 产生 SIGTSTP 信号,表示停止该进程,但还未结束;

如果进程在后台运行,可以通过 kill 命令的方式给进程发送信号,但前提需要知道运行中的进程 PID 号,例如:

  • kill -9 1050 ,表示给 PID 为 1050 的进程发送 SIGKILL 信号,用来立即结束该进程;

信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一进程,一旦有信号产生,我们就有下面这几种,用户进程对信号的处理方式。

1.执行默认操作。Linux 对每种信号都规定了默认操作,例如,上面列表中的 SIGTERM 信号,就是终止进程的意思。

2.捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。

3.忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILLSEGSTOP,它们用于在任何时候中断或结束某一进程。



六、Socket

管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信。

跨网络与不同主机上的进程之间通信,就需要 Socket 通信了。Socket 通信也可以在同主机上进程间通信。

我们来看看创建 socket 的系统调用:

int socket(int domain, int type, int protocal)

三个参数分别代表:

  • domain 参数用来指定协议族,比如 AF_INET 用于 IPV4、AF_INET6 用于 IPV6、AF_LOCAL/AF_UNIX 用于本机;
  • type 参数用来指定通信特性,比如 SOCK_STREAM 表示的是字节流,对应 TCP、SOCK_DGRAM 表示的是数据报,对应 UDP、SOCK_RAW 表示的是原始套接字;
  • protocal 参数原本是用来指定通信协议的,但现在基本废弃。因为协议已经通过前面两个参数指定完成,protocol 目前一般写成 0 即可;

根据创建 socket 类型的不同,通信的方式也就不同:

  • 实现 TCP 字节流通信: socket 类型是 AF_INET 和 SOCK_STREAM;
  • 实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM;
  • 实现本地进程间通信: 「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和 AF_LOCAL 是等价的,所以 AF_UNIX 也属于本地 socket;

1.针对 TCP 协议通信的 socket 编程模型

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将绑定在 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务器端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。

所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket

成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

2.针对 UDP 协议通信的 socket 编程模型

UDP 是没有连接的,所以不需要三次握手,也就不需要像 TCP 调用 listen 和 connect,但是 UDP 的交互仍然需要 IP 地址和端口号,因此也需要 bind。

对于 UDP 来说,不需要要维护连接,那么也就没有所谓的发送方和接收方,甚至都不存在客户端和服务端的概念,只要有一个 socket 多台机器就可以任意通信,因此每一个 UDP 的 socket 都需要 bind。

另外,每次通信时,调用 sendto 和 recvfrom,都要传入目标主机的 IP 地址和端口。

3.针对本地进程间通信的 socket 编程模型

本地 socket 被用于在同一台主机上进程间通信的场景:

  • 本地 socket 的编程接口和 IPv4 、IPv6 套接字编程接口是一致的,可以支持「字节流」和「数据报」两种协议;
  • 本地 socket 的实现效率大大高于 IPv4 和 IPv6 的字节流、数据报 socket 实现;

对于本地字节流 socket,其 socket 类型是 AF_LOCAL 和 SOCK_STREAM。

对于本地数据报 socket,其 socket 类型是 AF_LOCAL 和 SOCK_DGRAM。

本地字节流 socket 和 本地数据报 socket 在 bind 的时候,不像 TCP 和 UDP 要绑定 IP 地址和端口,而是绑定一个本地文件,这也就是它们之间的最大区别。



七、总结

由于每个进程的用户空间都是独立的,不能相互访问,这时就需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享一个内核空间。

Linux 内核提供了不少进程间通信的方式,其中最简单的方式就是管道,管道分为「匿名管道」和「命名管道」。

匿名管道顾名思义,它没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中,shell 命令中的「|」竖线就是匿名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程创建而建立,随着进程终止而消失。

命名管道突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,需要在文件系统创建一个类型为 p 的设备文件,那么毫无关系的进程就可以通过这个设备文件进行通信。另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持 lseek 之类的文件定位操作。

消息队列克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的「消息链表」,消息队列的消息体是可以用户自定义的数据类型,发送数据时,会被分成一个一个独立的消息体,当然接收数据时,也要与发送方发送的消息体的数据类型保持一致,这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程。

共享内存可以解决消息队列通信中用户态与内核态之间数据拷贝过程带来的开销,它直接分配一个共享空间,每个进程都可以直接访问,就像访问进程自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。但是便捷高效的共享内存通信,带来新的问题,多进程竞争同个共享资源会造成数据的错乱。

那么,就需要信号量来保护共享资源,以确保任何时刻只能有一个进程访问共享资源,这种方式就是互斥访问。信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是一个计数器,表示的是资源个数,其值可以通过两个原子操作来控制,分别是 P 操作和 V 操作

与信号量名字很相似的叫信号,它俩名字虽然相似,但功能一点儿都不一样。信号是异步通信机制,信号可以在应用进程和内核之间直接交互,内核也可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件的来源主要有硬件来源(如键盘 Cltr+C )和软件来源(如 kill 命令),一旦有信号发生,进程有三种方式响应信号 1. 执行默认操作、2. 捕捉信号、3. 忽略信号。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILLSIGSTOP,这是为了方便我们能在任何时候结束或停止某个进程。

前面说到的通信机制,都是工作于同一台主机,如果要与不同主机的进程间通信,那么就需要 Socket 通信了。Socket 实际上不仅用于不同的主机进程间通信,还可以用于本地主机进程间通信,可根据创建 Socket 的类型不同,分为三种常见的通信方式,一个是基于 TCP 协议的通信方式,一个是基于 UDP 协议的通信方式,一个是本地进程间通信方式。

以上,就是进程间通信的主要机制了。你可能会问了,那线程通信间的方式呢?

同个进程下的线程之间都是共享进程的资源,只要是共享变量都可以做到线程间通信,比如全局变量,所以对于线程间关注的不是通信方式,而是关注多线程竞争共享资源的问题,信号量也同样可以在线程间实现互斥与同步:

  • 互斥的方式,可保证任意时刻只有一个线程访问共享资源;
  • 同步的方式,可保证线程 A 应在线程 B 之前执行;


八、参考

小林 coding

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

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

相关文章

二、Gradle 与 Idea 整合

这里写自定义目录标题 1、Groovy简介2、Groovy 安装3、创建 Groovy 项目4、Groovy 基本语法5、在 idea 中创建普通 java 工程 1、Groovy简介 详细了解请参考&#xff1a;http://www.groovy-lang.org/documentation.html 2、Groovy 安装 下载后解压到本地 验证&#xff1a; …

springboot完成一个线上图片存放地址+实现前后端上传图片+回显

1.路径 注意路径 2.代码&#xff1a;&#xff08;那个imagePath没什么用&#xff0c;懒的删了&#xff09;&#xff0c;注意你的本地文件夹要有图片&#xff0c;才可以在线上地址中打开查看 package com.xxx.common.config;import org.springframework.beans.factory.annotat…

HTML+CSS+JS的3D进度条

<!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>HTMLCSSJS的3D进度条</title><style>…

高效的跳表

高效的跳表 一、 概念二、 实现原理三、存在的问题四、解决方法五、如何保证效率六、代码实现七、总结对比平衡搜索树对比哈希表 一、 概念 跳表&#xff0c;是一种用来查询数据的数据结构&#xff0c;它是由William Pugh发明的&#xff0c;借助有序链表&#xff0c;来实现高效…

git 小乌龟解决冲突问题

git 解决冲突 下边命令我指的是小乌龟的命令&#xff0c;不是指的git的命令行语句 git commit 提交代码到本地仓库 git pull 拉代码 git push 推代码的时候出现代码冲突问题 自动合并失败了&#xff0c;有冲突的文件&#xff0c;需要先解决冲突。修改标记为已解决&#xff0…

轻松玩转书生·浦语大模型趣味Demo(二)

大模型及 InternLM 模型介绍 什么是大模型 人工智能领域中参数数量巨大&#xff0c;拥有庞大计算能力和参数规模的模型 特点及应用 利用大量数据进行训练拥有数十亿甚至数千亿个参数模型在各种任务中展现出惊人的性能 InternLM-Chat-7B 智能对话 Demo Legant 介绍 lagnet …

Flutter 应用服务:主题、暗黑、国际化、本地化 - app_service库

Flutter应用服务 主题、暗黑、国际化、本地化 app_service库 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/det…

Debezium发布历史103

原文地址&#xff1a; https://debezium.io/blog/2021/03/18/understanding-non-key-joins-with-quarkus-extension-for-kafka-streams/ 欢迎关注留言&#xff0c;我是收集整理小能手&#xff0c;工具翻译&#xff0c;仅供参考&#xff0c;笔芯笔芯. 了解 Kafka Streams 的 Q…

2024美赛数学建模A题思路分析 - 资源可用性和性别比例

1 赛题 问题A&#xff1a;资源可用性和性别比例 虽然一些动物物种存在于通常的雄性或雌性性别之外&#xff0c;但大多数物种实质上是雄性或雌性。虽然许多物种在出生时的性别比例为1&#xff1a;1&#xff0c;但其他物种的性别比例并不均匀。这被称为适应性性别比例的变化。例…

书客、米家、柏曼大路灯哪款好?多维度实测对比推荐!

每到寒暑假&#xff0c;各个论坛上出现“大路灯怎么选”的类似话题非常频繁&#xff0c;因为现在的孩子出来上学期间需要读写之外&#xff0c;在寒暑假时也在不断的学习&#xff0c;许多家长关注到孩子学习时的光线问题&#xff0c;担心影响到孩子的视力状况&#xff0c;都纷纷…

pdf怎么标注?这3个标注方法亲测好用

pdf怎么标注&#xff1f;在日常办公中&#xff0c;PDF标注软件发挥着重要作用。当我们收到一份PDF文档&#xff0c;如合同、报告或电子书&#xff0c;需要对其进行批注、修改或解释时&#xff0c;一款好用的PDF标注软件就显得尤为重要。通过这些软件&#xff0c;我们可以轻松地…

【机器学习】贝叶斯垃圾邮件识别

实验三&#xff1a;贝叶斯垃圾邮件识别 本次作业以垃圾邮件分类任务为基础&#xff0c;要求提取文本特征并使用朴素贝叶斯算法进行垃圾邮件识别&#xff08;调用已有工具包或自行实现&#xff09;。 1 任务介绍 ​ 电子邮件是互联网的一项重要服务&#xff0c;在大家的学习、…

操作系统-02-Ubuntu 常用命令等汇总

Ubuntu Ubuntu是一个开源软件平台&#xff0c;可以在从智能手机、平板电脑和个人电脑到服务器和云端等各种设备上运行。 ubuntu Title: Desktop Ubuntu desktop->command: CtrlAltF2~F6 command->desktop: CtrlAltF7 Login Change to root user $ sudu suLogin to al…

springboot 集成 nacos (demo 版)

环境要求&#xff1a;本地安装 nacos&#xff08;windows版本&#xff09;&#xff0c;jdk 是 8&#xff0c;安装完还得在 nacos 上面配置一个 yaml 类型的配置文件&#xff0c;方便项目演示读取。 提示&#xff1a;如标题所示&#xff0c;本文只介绍如何从零到一的去集成 naco…

【C/Python】GtkApplicationWindow

一、C语言 GtkApplicationWindow 是 GTK 库中用于创建应用程序主窗口的一个控件。 首先&#xff0c;需要确保环境安装了GTK开发库。然后&#xff0c;以下是一个简单的使用 GtkApplicationWindow 创建一个 GTK 应用程序的示例&#xff1a; #include <gtk/gtk.h>static …

nvm - nodejs版本管理工具

我们可能同时在进行2个或者多个不同的项目开发&#xff0c;每个项目的需求不同&#xff0c;进而不同项目必须依赖不同版本的NodeJS运行环境&#xff0c;这种情况下&#xff0c;对于维护多个版本的node将会是一件非常麻烦的事情&#xff0c;nvm就是为解决这个问题而产生的&#…

9.2爬楼梯(LC70-E)

算法&#xff1a; 多举几个例子&#xff0c;找规律&#xff1a; 爬到第一层楼梯有一种方法&#xff0c;爬到二层楼梯有两种方法。 那么第一层楼梯再跨两步就到第三层 &#xff0c;第二层楼梯再跨一步就到第三层&#xff08;时序&#xff09;。 所以到第三层楼梯的状态可以由…

k8s存储之PV、PVC

在k8s集群中&#xff0c;资源存储会散落到各个工作节点上&#xff0c;这样对用资源调用很不方便&#xff0c;那么k8s是如何实现存储资源共享的呢&#xff0c;本文浅尝辄止的探讨一下&#xff0c;k8s是通过pv、pvc实现的。 一、PV、PVC的概念 1、持久卷(PV&#xff09; pv是Pe…

闲人闲谈PS之五十三——离散制造中的魔鬼--物料套裁

惯例闲话&#xff1a;最近和老婆大人商议买车事宜&#xff0c;闲人以为会陷入买油车还是电车的纠结&#xff0c;没想到老婆大人无比坚定&#xff0c;买电车。在买车这方面&#xff0c;老婆的想法居然比闲人超前。闲人对车定位在代步工具&#xff0c;2年前&#xff0c;对车还是印…

【MATLAB】PSO_BiLSTM神经网络回归预测算法

有意向获取代码&#xff0c;请转文末观看代码获取方式~也可转原文链接获取~ 1 基本定义 PSO_BiLSTM神经网络回归预测算法是一种结合了粒子群优化&#xff08;Particle Swarm Optimization&#xff0c;PSO&#xff09;和双向长短期记忆网络&#xff08;Bidirectional Long Shor…