Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(1)

Mojo简介

Mojo 是一个运行时库的集合,提供与平台无关的通用 IPC 原语抽象、消息 IDL 格式以及具有针对多种目标语言的代码生成的绑定库,以便于跨任意进程间和进程内边界传递消息。
Mojo 分为清晰分离的层,子组件的基本层次结构如下:

请添加图片描述

分析Mojo之前,我们的思考

笔者在阅读源码前,喜欢会去思考,“如果让我来设计一个类似的功能的模块,我会怎么设计?”。然后对比文档去思考为什么会出现思路的差异,这种方式可以让我快速掌握一个开源库的设计精髓。
这次也是一样,我们想想,如果是我们自己设计Mojo,这会是什么样的架构和过程。

  1. 首先,Mojo是跨平台的,那么必然有一层platform的平台差异屏蔽层
  2. 其次,跨进程通信在不同平台上的最佳实现方案可能不一样,例如有的平台是管道,有的平台是共享内存,具体如何选择取决于不同平台的性能差异,因此,我必须将跨平台通信细节进行抽象,提取出一些概念,用于描述通信的应用层细节
  3. 之后,两个进程之间的通信之前,需要先建立连接,因此必然也需要定义一组规则和概念,来描述连接的应用层细节
  4. 为了增加通信的灵活性,我们可以定义一组观察者或者过滤器的规则,可以实现对数据流的监测和量化,也能实现更灵活的扩展。因此我们需要定义一组规则和概念来实现这个目标
  5. 为了让跨平台通信框架更加易于使用,我们需要提供一套序列化和反序列化的框架,这样可以让通信以自定义结构体的形式进行,而非数据流。
  6. 如果存在大量的通信消息,那么我们需要解决不同进程共享头文件,使用自定义结构体的形式;另外,Mojo除了跨平台,还需要夸语言,那么,我们必然不能使用某种语言的结构体定义形式(例如C的结构体或者JavaScript友好的Json),而是需要定义一种新的规则,通过工具自动生成不同语言友好的结构体源码

上面的5和6本质上可以看作是同一个问题,并且我首先想到的是protobuffer库可以实现这两个问题的解法。

带着我们自己的设计思路,再去看看Mojo的设计方案。

的Mojo方案:

源码目录体积一览:

在这里插入图片描述
源码重要文件一览(排除test源文件,按大小排序,top的文件如下图):
在这里插入图片描述
一般来说,体积大的源文件表示的功能都比较核心,因此这些文件里面对应的功能和概念,很大概率是Mojo框架的核心,值得每一项进行标注理解。

Mojo的底层Channel

在Mojo中的源码中,可以很明显的发现关键字Channel是最底层跨进程通信的关键概念,并且能找到平台相关的抽象和实现,直接搜索文件名关键字就一览无余了:
在这里插入图片描述

channel.h

看了一眼mojo\core\channel.h可以发现,抽象基类是mojo::core::Channel, 并且定义了许多核心概念,包括Message(A message to be written to a channel.)、Delegate( // Delegate methods are called from the I/O task runner with which the Channel
was created (see Channel::Create). 关键回调OnChannelMessage)等。
不同平台通过继承抽象基类mojo::core::Channel实现平台相关的读写,以mojo\core\channel_win.cc的ChannelWin为例,使用Win32API的ReadFile、WriteFile从base::win::ScopedHandle句柄中读写数据,这个句柄是构造函数的参数ConnectionParams(TakeEndpoint().TakePlatformHandle().TakeHandle())传入的获取的。

channel相关的重点类解析

  • PlatformChannel
    是一个封装了两个交织在一起的端点的类或结构,这些端点属于特定平台的基本通信原语,比如在Windows上是管道,在Unix系统上是域套接字,在macOS上是Mach端口对。其中一个端点被指定为“本地”端点,由创建它的进程保留;另一个端点被指定为“远程”端点,应当传递给外部的进程。

  • PlatformChannel 可以用来在两个进程之间启动Mojo IPC(一种进程间通信机制)。通常情况下另一个进程是当前进程的子进程,PlatformChannel
    提供辅助方法来将端点以这种方式传递给子进程;但这种设置在所有平台上并不是强制性的。
    如果需要一个允许客户端通过名称来连接的通道(比如一个命名管道或者套接字服务器,这种类型仅在Windows和POSIX系统上被支持),那么可以参考
    NamedPlatformChannel。

  • PlatformChannelServer 这个类负责持有一个 PlatformChannelServerEndpoint 实例,并监听一个单一的来自客户端的连接请求。这个类不是线程安全的,必须在运行I/O消息泵(Message Pump)的线程上使用。

  • PlatformChannelServer 和PlatformChannel 对比: 简而言之,PlatformChannel 负责创建和管理进程间通信的通道,而 PlatformChannelServer
    则是在服务端监听和接受这些通道上的连接请求。PlatformChannel 可以看作是连接的“管道”,而 PlatformChannelServer 是“水龙头”,控制着连接的开启。

  • PlatformHandle平台句柄类,它带有一些额外的类型信息,用来表明它是一个通道端点(channel endpoint)。也就是说,它是一种句柄,可以被用来作为 MOJO_INVITATION_TRANSPORT_TYPE_CHANNEL
    发送或接收邀请到一个远程的 PlatformChannelEndpoint

结合调用堆栈:

Channel的创建:
在这里插入图片描述
Channel的发送消息:
在这里插入图片描述
通过调用堆栈可以发现,Channel的概念几乎是Mojo最底层的概念了,往上走有Router、InterfaceEndpointClient、Message、ChannelMojo概念等。以下是从最底层到更高层的一些核心概念及其作用的介绍:

  1. Channel:
    Channel 是 Mojo IPC 系统中最底层的抽象。它代表了一个底层的通信通道,负责在两个进程之间传输原始的字节数据。Channel 封装了操作系统级别的 IPC 机制(如套接字或共享内存),以便在不同的平台上提供一致的 API。它负责序列化和反序列化消息,保证数据的完整性,并处理底层的传输细节。

  2. Router:
    Router 位于 Channel 之上,是一个稍高层次的抽象。它负责将发出的消息路由到正确的 Channel,并从 Channel 接收消息。Router 还管理消息的生命周期,确保消息按照正确的顺序发送和接收,并可能处理流控制和重试逻辑。

  3. InterfaceEndpointClient:
    InterfaceEndpointClient 是 Mojo IPC 中的一个组件,它代表了 Mojo 接口的端点。它与 Router 配合,以便在 Mojo 接口上发送和接收消息。通常,每个 Mojo 接口都有一个或多个方法,这些方法对应于可以通过该端点发送的消息类型。InterfaceEndpointClient 会序列化这些方法调用为消息,并将它们传递给 Router 进行传输。

  4. Message:
    Message 是代表 IPC 系统中传递的一个消息实体。它通常包含要传输的数据(例如,方法调用的参数),以及可能的元数据(例如,消息类型或优先级)。在 Mojo IPC 中,Message 是发送和接收的基本单位,由 RouterChannel 处理。

  5. ChannelMojo:
    ChannelMojoChannel 的一个具体实现,它利用 Mojo 系统的底层管道(如 MessagePipe)来传输数据。ChannelMojo 提供了一个适应 Mojo 管道特性的 Channel 接口,使得上层的 RouterInterfaceEndpointClient 能够通过 Mojo 管道发送和接收消息。

这些概念共同构成了 Mojo IPC 系统的框架,其中每个层次都建立在下一个层次之上,提供了逐步更高级别的抽象和功能。开发者可以根据需要选择在哪个层次上与 IPC 系统交互,从直接使用 Channel 的字节级操作,到通过 InterfaceEndpointClient 的接口级调用。

Mojo的Node

在源码一览中,我们发现node.cc是最大的源文件,我们以此为线索展开对Node的理解和阅读,Node相关文件有:
在这里插入图片描述
Node.h中,NodeChannel是一个核心,注释只有一句:Wraps a Channel to send and receive Node control messages.

// Wraps a Channel to send and receive Node control messages.
class MOJO_SYSTEM_IMPL_EXPORT NodeChannel: public base::RefCountedDeleteOnSequence<NodeChannel>,public Channel::Delegate {public:// .... 略

由此可见,Channel 是一种底层概念,用于抽象化和平滑处理不同平台之间的差异,而 Node 概念则在 Channel 的基础上进行了进一步的封装和抽象化。NodeChannel用于定义 Mojo 中与连接、广播、中介(Broker)、消息传输以及错误处理相关的实现细节。如果用计算机网络的术语进行类比,那么 Channel 类似于网络协议栈中的 IP 层,它提供了寻址和路由的能力;而 NodeChannel则相当于应用层的协议,例如 UDP,它在更高层次上处理数据的传输和相关逻辑。

那么,可以预见,Mojo的应用层概念将围绕Node为核心展开。

从上面代码中我们发现,NodeChannel中有个重要的嵌入类:Delegate。Delegate的概念在chromium广泛存在,其实可以理解为Delegate就是一组回调,在宿主对象处理逻辑的关键节点时,通过Delegate回调转移执行绪,以实现行为的定制和扩展的能力。 通过了解Delegate回调的函数组成,可以快速了解宿主类的主要功能和关键流程,是阅读源码的重要技巧。例如NodeChannel的Delegate的类定义如下:

 class Delegate {public:virtual ~Delegate() = default;virtual void OnAcceptInvitee(const ports::NodeName& from_node,const ports::NodeName& inviter_name,const ports::NodeName& token) = 0;virtual void OnAcceptInvitation(const ports::NodeName& from_node,const ports::NodeName& token,const ports::NodeName& invitee_name) = 0;virtual void OnAddBrokerClient(const ports::NodeName& from_node,const ports::NodeName& client_name,base::ProcessHandle process_handle) = 0;virtual void OnBrokerClientAdded(const ports::NodeName& from_node,const ports::NodeName& client_name,PlatformHandle broker_channel) = 0;virtual void OnAcceptBrokerClient(const ports::NodeName& from_node,const ports::NodeName& broker_name,PlatformHandle broker_channel,const uint64_t broker_capabilities) = 0;virtual void OnEventMessage(const ports::NodeName& from_node,Channel::MessagePtr message) = 0;virtual void OnRequestPortMerge(const ports::NodeName& from_node,const ports::PortName& connector_port_name,const std::string& token) = 0;virtual void OnRequestIntroduction(const ports::NodeName& from_node,const ports::NodeName& name) = 0;virtual void OnIntroduce(const ports::NodeName& from_node,const ports::NodeName& name,PlatformHandle channel_handle,const uint64_t remote_capabilities) = 0;virtual void OnBroadcast(const ports::NodeName& from_node,Channel::MessagePtr message) = 0;
#if BUILDFLAG(IS_WIN)virtual void OnRelayEventMessage(const ports::NodeName& from_node,base::ProcessHandle from_process,const ports::NodeName& destination,Channel::MessagePtr message) = 0;virtual void OnEventMessageFromRelay(const ports::NodeName& from_node,const ports::NodeName& source_node,Channel::MessagePtr message) = 0;
#endifvirtual void OnAcceptPeer(const ports::NodeName& from_node,const ports::NodeName& token,const ports::NodeName& peer_name,const ports::PortName& port_name) = 0;virtual void OnChannelError(const ports::NodeName& node,NodeChannel* channel) = 0;};

通过这个代理类,就能很直观地理解NodeChannel的功能和作用。

mojo的Port

在阅读NodeChannel类的时候,有一个关键字出现了多次,那就是port。在Mojo中,port是一个命名空间,也是一个重要概念,port这个类的头文件注释如下:

在 Mojo IPC 系统中,“Port”本质上是一个地址的循环列表中的一个节点。为了本文档的目的,这样的列表将被称为“路由”(route)。路由是所有 Node 事件流通的基本媒介,因此是所有 Mojo 消息传递的骨干。
每个 Port 都由一个节点(参见 node.h)内的 128 位地址唯一标识。Port 本身并不真正“做”任何事情:它是一系列状态的命名集合,而拥有它的 Node 管理所有事件的产生、传输、路由和处理逻辑。有关 Port 如何被用来传输任意用户消息以及其他 Ports 的更多细节,请参见 Node。
Ports 可以处于几种状态(见下面的 State),这些状态决定了它们如何响应以它们为目标的系统事件。在最简单和最常见的情况下,Ports 最初是作为一对纠缠在一起的状态(即由两个 Ports 组成的简单循环)创建的,都处于 kReceiving 状态。我们这里将这些 Ports 标为 |A| 和 |B|,它们可以使用 Node::CreatePortPair() 创建:

    +-----+          +-----+|     |--------->|     ||  A  |          |  B  ||     |<---------|     |+-----+          +-----+

|A| 通过 |peer_node_name| 和 |peer_port_name| 引用 |B|,同时 |B| 反过来引用
|A|。请注意,一个 Node 永远不会知道是谁向给定的 Port 发送事件;它只知道必须从给定的 Port 路由事件到哪里。
为了方便文档描述,我们将路由中的一个接收端 Port 称为另一个的“共轭”(conjugate)。一个接收端 Port 的共轭在初始创建时也是它的对端,但由于代理,这种关系可能随着时间而改变。 对这个数据结构的所有访问必须通过获取 |lock_|来进行保护,这只能通过 PortLocker 实现。PortLocker 确保在单个线程上重叠的 Port 锁获取总是以全局一致的顺序进行。

通过头文件注释,感觉似懂非懂,看看Port这个类的主要成员和方法吧:

Port 类是 Mojo IPC 系统中的一个核心组件,它代表了消息传递路径上的一个节点。这个类继承自
base::RefCountedThreadSafe<Port>,允许它在多个线程中安全地共享和管理其生命周期。以下是 Port
类的主要功能和特性:

  • State 枚举:定义了 Port 可能处于的状态,包括 kUninitialized(未初始化)、kReceiving(接收中)、kBuffering(缓冲中)、kProxying(代理中)和
    kClosed(已关闭)。
  • state 成员变量:存储当前 Port 的状态。
  • peer_node_namepeer_port_name 成员变量:指定了事件应该从该 Port 路由到哪个节点和端口的地址。
  • prev_node_nameprev_port_name 成员变量:跟踪当前发送消息到这个 Port 的上一个端口,用于验证发送方节点是否有权限发送消息到这个端口,同时保持接收消息的顺序。
  • pending_merge_peer 成员变量:标记这个端口是否准备合并。
  • 一系列的序列号成员变量(next_control_sequence_num_to_sendnext_sequence_num_to_send
    等):用于跟踪控制和用户消息事件的序列号。
  • message_queue 成员变量:存储该 Port 接收到的用户消息队列。此队列只为 kBufferingkReceiving 状态的 Port 提供服务。
  • control_message_queue 成员变量:在 Port 处于 kBuffering 状态时,暂存即将发送的控制消息。
  • send_on_proxy_removal 成员变量:在某些边缘情况下,如果这个(代理中的)Port 被销毁,它可能需要记得路由一个特殊的事件。
  • user_data 成员变量:附加到 Port 的任意用户数据。在 Mojo 中,这通常用于存储通知有关 Port 状态变化的观察者接口。
  • remove_proxy_on_last_messagepeer_closed 成员变量:标志位,用于指示 Port 的一些状态,如代理何时可以移除,以及它的对端 Port 是否已关闭。
  • Port 构造函数:用于初始化 Port,设置初始的序列号。
  • AssertLockAcquired 方法:用于调试中检查是否已获取 Port 的锁。
  • IsNextEvent 方法:检查给定的事件是否应该根据序列号和发送方节点接下来处理。
  • NextEvent 方法:获取下一个要处理的缓冲事件。
  • BufferEvent 方法:将事件缓存以供后续处理。>
  • TakePendingMessages 方法:清空等待节点验证的事件队列,并返回所有用户事件。
  • 私有析构函数 ~Port:确保 Port 只能通过引用计数安全地销毁。
  • 私有成员 lock_:用于确保对 Port 数据结构的线程安全访问。
  • PortLocker 友元类:用于确保在单个线程上以全局一致的顺序获取重叠的 Port 锁。
    该类的设计允许它在 Mojo IPC 系统中作为消息的发送和接收点,管理消息的顺序和状态,并确保消息在正确的路径上流动。

结合其他源码,发现Port和Dispatcher相关逻辑结合紧密,另外,Port存储了Event的序号等数据信息,支持插入事件,并且许多数据成员用于Node.cc中实现消息处理,可见Port这个类做的事情确实很难和已有的概念类比出来,也难怪通过这个类的注释难以一下理解其作用。简而言之,Port这个类即负责一部分事件排序和派发相关的逻辑处理,也承载了一个寻址的功能。

在 Mojo IPC 系统中,Node 通常代表了一个独立的参与者,如一个进程,它是消息传递路径上的一个物理节点。Node 可以是消息的最初发送者或最终接收者。相比之下,Port 是 Node 内部的逻辑上的虚拟节点,它负责管理消息的复杂路由、转发以及过滤等操作。每个 Port 都由其所属的 Node 管理,并且可以与其他 Node 中的 Port 形成连接,从而构成消息传递的网络。
之所以这样设计,是为了让两个Node之间可以出现多个连接,每个连接就是一对“共轭”的Port。这样每个连接各自的序号(seq)就不会互相干扰。所以,序号的数据就存储在Port类里,这也就不奇怪了。正因为Port代表了连接,所以数据的过滤和代理也必须面向连接进行,因此Port也和相关的类紧密联系。

说实话,如果把Port改名为Connection,也许会更直观一些。

这里面出现了NodeName和Port Name,也顺便看看定义:

struct COMPONENT_EXPORT(MOJO_CORE_PORTS) PortName : Name {constexpr PortName() : Name(0, 0) {}constexpr PortName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
};
struct COMPONENT_EXPORT(MOJO_CORE_PORTS) NodeName : Name {constexpr NodeName() : Name(0, 0) {}constexpr NodeName(uint64_t v1, uint64_t v2) : Name(v1, v2) {}
};

接下来

接下来,我们继续阅读Mojo模块的代码。了解消息的过滤和派发、序列化和反序列化、Mojom、等功能逻辑。
Chromium源码阅读:深入理解Mojo框架的设计思想,并掌握其基本用法(2)

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

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

相关文章

2024下《系统分析师》50个高频考点汇总!背就有效

宝子们&#xff01;上半年软考已经结束一段时间了&#xff0c;准备备考下半年软考高级-系统分析师的小伙伴可以开始准备了&#xff0c;毕竟高级科目的难度可是不低的&#xff0c;相信参加过上半年系分的小伙伴深有体会。 这里给大家整理了50个高频考点&#xff0c;涵盖全书90%…

透视环世物流:一个AI降本的产业典型样本

在过去的多年时间里&#xff0c;与其说低代码完成的数字原生的普惠&#xff0c;不如说其最强的能力恰是能帮助企业用最低的门槛、最高的效率构建出与自身适配的流程和业务应用&#xff0c;在底层帮助企业构建出一个被极致拆分和分子化的软件开发中台。 而透过环世物流&#x…

数据结构笔记-2、线性表

2.1、线性表的定义和基本操作 如有侵权请联系删除。 2.1.1、线性表的定义&#xff1a; ​ 线性表是具有相同数据类型的 n (n>0) 个数据元素的有限序列&#xff0c;其中 n 为表长&#xff0c;当 n 0 时线性表是一个空表。若用 L 命名线性表&#xff0c;则其一般表示为&am…

从报名到领证:软考初级【信息系统运行管理员】报名考试全攻略

本文共计9991字&#xff0c;预计阅读33分钟。包括七个篇章&#xff1a;报名、准考证打印、备考、考试、成绩查询、证书领取及常见问题。 一、报名篇 报名条件要求&#xff1a; 1.凡遵守中华人民共和国宪法和各项法律&#xff0c;恪守职业道德&#xff0c;具有一定计算机技术…

利用AI大模型,将任何文本语料转化为知识图谱,可本地运行!

几个月前&#xff0c;基于知识的问答&#xff08;KBQA&#xff09;还是一个新奇事物。 现在&#xff0c;对于任何 AI 爱好者来说&#xff0c;带检索增强生成&#xff08;RAG&#xff09;的 KBQA 就像小菜一碟。看到自然语言处理&#xff08;NLP&#xff09;的可能性领域由于大…

CP AUTOSAR标准中文文档链接索引(更新中)

AUTOSAR标准的核心组件包括通信、诊断、安全等&#xff0c;这些组件通过模块化结构进行组织。系统被划分为多个模块&#xff0c;每个模块负责特定的功能。模块之间通过接口进行通信&#xff0c;接口定义了模块之间的交互规则。AUTOSAR标准支持模块的配置&#xff0c;可以根据不…

基于BP神经网络对鸢尾花数据集分类

目录 1. 作者介绍2. 关于理论方面的知识介绍2.1 BP神经网络原理2.2 BP神经网络结构 3. 关于实验过程的介绍&#xff0c;完整实验代码&#xff0c;测试结果3.1 鸢尾花数据集介绍3.2 代码演示3.3 结果演示 4. 问题与分析 1. 作者介绍 侯硕&#xff0c;男&#xff0c;西安工程大学…

【动态规划算法题记录】70. 爬楼梯——递归/动态规划

题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢&#xff1f; 题目分析 递归法&#xff08;超出时间限制&#xff09; 递归参数与返回值 void reversal(int i, int k) 每次我们处理第i个台阶到第k个…

白酒:茅台镇白酒的品牌故事与传奇色彩

茅台镇&#xff0c;这个位于中国贵州省遵义市仁怀市的小镇&#xff0c;因为出产的白酒品质卓着&#xff0c;被誉为“世界名酒之乡”。在这片神奇的土地上&#xff0c;云仓酒庄以其豪迈白酒&#xff0c;书写了一段段品牌故事与传奇色彩。 云仓酒庄豪迈&#xff0c;酿造于茅台镇的…

Vue23-过滤器

一、效果图 二、好用的时间戳三方工具 该三方工具比较大 推荐使用 dayjs的用法&#xff1a; 三、过滤器的使用 3-1、计算属性实现 3-2、methods函数实现 3-3、过滤器filters属性实现 过滤器的本质就是函数&#xff01;&#xff01;&#xff01; 1、过滤器-未传参 默认将管道…

TIM—通用定时器高级定时器

通用/高级定时器的功能 在基本定时器功能的基础上新增功能&#xff1a; 通用定时器有4个独立通道&#xff0c;且每个通道都可以用于下面功能。 &#xff08;1&#xff09;输入捕获&#xff1a;测量输入信号的周期和占空比等。 &#xff08;2&#xff09;输出比较&#xff1a;产…

自动化办公03 用xlrd和xlwt库操作excel.xls文件(老版本)

目录 一、读操作 二、写操作 三、设置单元格格式 0.综合案例 1.设置行高和列宽 2.设置字体样式 3.设置边框样式 4.设置对齐方式 5.设置背景颜色 6.合并单元格 四、 xlutils修改Excel⽂件内容 1.安装 2.使用 一、读操作 import xlrd# 1. 打开excel文件 wb xlrd.op…

Flarum 安装和使用教程

随着开源社区的日益繁荣&#xff0c;人们对社区品质的要求也越来越高。传统的 BBS 论坛模式已经难以满足现代用户对美观、便捷、互动性的需求。搭建一个现代化的高品质社区&#xff0c;成为许多网站管理者的迫切需求和共同挑战。 今天就给大家安利一款现代化的、优雅的开源论坛…

立创·天空星开发板-GD32F407VE-Timer

本文以 立创天空星开发板-GD32F407VET6-青春版 作为学习的板子&#xff0c;记录学习笔记。 立创天空星开发板-GD32F407VE-Timer 定时器基本定时器示例 定时器 定时器是嵌入式系统中常用的一种外设&#xff0c;它可以产生一定的时间间隔、延时、定时等功能&#xff0c;广泛应用于…

网络安全等级保护基本要求 第1部分:安全通用要求

基本要求 第三级 安全物理环境 物理位置选择 a) 机房场地应选择在具有防震、防风和防雨等能力的建筑内&#xff1b; b) 机房场地应避免设在建筑物的顶层或地下室&#xff0c;否则应加强防水和防潮措施 物理访问控制 a) 机房出入口应配置电子门禁系统&#xff0c;控制、鉴…

Java项目:100 springboot共享汽车管理系统

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 本共享汽车管理系统有管理员和用户。 管理员功能有个人中心&#xff0c;用户管理&#xff0c;投放地区管理&#xff0c;汽车信息管理&#xff0c;汽车…

用 KV 缓存量化解锁长文本生成

很高兴和大家分享 Hugging Face 的一项新功能: KV 缓存量化 &#xff0c;它能够把你的语言模型的速度提升到一个新水平。 太长不看版: KV 缓存量化可在最小化对生成质量的影响的条件下&#xff0c;减少 LLM 在长文本生成场景下的内存使用量&#xff0c;从而在内存效率和生成速度…

基于STM32和人工智能的智能交通管理系统

目录 引言环境准备智能交通管理系统基础代码实现&#xff1a;实现智能交通管理系统 4.1 数据采集模块4.2 数据处理与分析4.3 控制系统4.4 用户界面与数据可视化应用场景&#xff1a;智能交通管理与优化问题解决方案与优化收尾与总结 1. 引言 随着城市化进程的加快&#xff0…

算法第六天:力扣第977题有序数组的平方

一、977.有序数组的平方的链接与题目描述 977. 有序数组的平方的链接如下所示&#xff1a;https://leetcode.cn/problems/squares-of-a-sorted-array/description/https://leetcode.cn/problems/squares-of-a-sorted-array/description/ 给你一个按 非递减顺序 排序的整数数组…

【C++】STL中stack和queue(适配器版)的模拟实现

前言&#xff1a;在此之前我们讲到了stack和queue还有deque的常见的使用方法&#xff0c;并且也在数据结构的时候用C语言去实现过栈和队列&#xff0c;今天我们将进一步的用C去模拟实现stack和queue &#x1f496; 博主CSDN主页:卫卫卫的个人主页 &#x1f49e; &#x1f449; …