【Rust自学】15.6. RefCell与内部可变性:“摆脱”安全性限制
题外话,这篇文章一共4050字,是截止到目前为止最长的文章,如果你能坚持读完并理解,那真的很强!
喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
15.6.1. 什么是内部可变性
内部可变性(interior mutability)是Rust的设计模式之一,它允许程序员在只持有不可变引用的前提下对数据进行修改。
通常而言,这样的行为会被借用规则(详见 4.4. 引用与借用)所禁止,但是为了能够改变数据,内部可变性模式在代码的数据结构里使用了unsafe
代码来绕过Rust正常的可变性和借用规则。
不安全代码向编译器表明我们正在手动检查规则,而不是依赖编译器为我们检查规则。不安全代码相关的概念将在以后的文章中涉及。
15.6.2. RefCell<T>
与Rc<T>
不同,RefCell<T>
类型代表了其持有数据的唯一所有权。
为了了解RefCell<T>
与Box<T>
的区别,我们得回顾一下借用规则(详见 4.4. 引用与借用):
- 在任何给定时间,你可以拥有(但不能同时拥有)一个可变引用或任意数量的不可变引用。
- 引用总是保持有效。
PS:给定时间可以理解为给定的作用域内
RefCell<T>
与Box<T>
的区别如下:
类型 | 检查阶段 | 规则违背后果 |
---|---|---|
Box<T> | 编译阶段检查借用规则 | 编译时报错 |
RefCell<T> | 运行时检查借用规则 | 触发 panic |
借用规则在不同阶段进行检查有不同的特点:
-
编译阶段:
- 尽早暴露问题
- 没有任何运行时的开销
- 是大多数场景的最佳选择
- 是Rust的默认行为
-
运行时:
- 问题暴露延后,甚至到生产环境
- 因借用计数产生些许性能损失
- 实现某些特定的内存安全场景(比如在不可变环境中修改自身数据)
该在什么时候使用`RefCell
Rust编译器在编译阶段会检查所有的代码,其中大部分代码它都能够分析明白,如果没有问题就通过编译,如果有问题就报错。
Rust编译器是非常保守的,某些代码并不能在编译阶段就能分析明白,针对这类无法在编译阶段完成分析的代码Rust会直接拒绝掉,哪怕这些代码本质上没有任何问题。
Rust这么保守是为了保证程序的安全性。虽然拒绝掉某些本身没有问题的代码会对开发者造成不便,但是至少不会产生任何灾难性的后果。
针对这些编译器无法分析的代码,如果开发者能够保证这段代码满足借用规则,那么就可以使用RefCell<T>
。
与RefCell<T>
类似,Rc<T>
只适用于单线程场景。
15.6.3. 如何在Box<T>
、Rc<T>
和RefCell<T>
中进行选择
根据下表列出的三者的特性就可以进行选择:
特性 | Box<T> | Rc<T> | RefCell<T> |
---|---|---|---|
同一数据的所有者 | 一个 | 多个 | 一个 |
可变性、借用检查 | 可变、不可变借用(编译时检查) | 不可变借用(编译时检查) | 可变、不可变借用(运行时检查) |
额外说一句,由于RefCell<T>
在运行时才会被检查,所以即使RefCell<T>
本身是不可变的,但我们仍然可以修改里面储存的值。
15.6.4. 内部可变形:可变的借用一个不可变的值
这个小标题有一点绕,意思是对一个没有声明为mut
的类型使用&mut
引用。看个例子就明白了:
fn main() {let x = 5;let y = &mut x;
}
借用规则的一个推论是,当你有一个不可变的值时,你就不能可变地借用它。所以这么写会报错:
error[E0596]: cannot borrow `x` as mutable, as it is not declared as mutable--> src/main.rs:3:13|
3 | let y = &mut x;| ^^^^^^ cannot borrow as mutable|
help: consider changing this to be mutable|
2 | let mut x = 5;| +++For more information about this error, try `rustc --explain E0596`.
error: could not compile `borrowing` (bin "borrowing") due to 1 previous error
然而在某些特定情况下,我们会需要这样一个值——它对外部保持不可变,但它同时能在方法内部修改自身的值,除了这个值本身的方法,其余的代码都不能修改这个值,这叫做内部可变性。RefCell<T>
就是为了这种情况而存在的。
但是RefCell<T>
并没有完全地绕开借用规则,编译阶段的检查虽然能够通过,但是在运行阶段如果违反了借用规则就会造成程序恐慌。
下面看一个例子(lib.rs
):
功能:用于跟踪某个值与最大值的接近程度,并在该值达到特定级别时发出警告
pub trait Messenger {fn send(&self, msg: &str);
}pub struct LimitTracker<'a, T: Messenger> {messenger: &'a T,value: usize,max: usize,
}impl<'a, T> LimitTracker<'a, T>
whereT: Messenger,
{pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {LimitTracker {messenger,value: 0,max,}}pub fn set_value(&mut self, value: usize) {self.value = value;let percentage_of_max = self.value as f64 / self.max as f64;if percentage_of_max >= 1.0 {self.messenger.send("Error: You are over your quota!");} else if percentage_of_max >= 0.9 {self.messenger.send("Urgent warning: You've used up over 90% of your quota!");} else if percentage_of_max >= 0.75 {self.messenger.send("Warning: You've used up over 75% of your quota!");}}
}
这个例子的逻辑并不重要,看一下它的写法:
-
程序开头定义了
Messenger
trait,里面有send
方法的签名:接收不可变引用&self
和一个字符串切片类型&str
的形参msg
作为参数。 -
下面定义了一个结构体叫
LimitTracker
,它是一个泛型类型,生命周期为'a
,泛型参数为T
,要求T
的生命周期为'a
并实现Messenger
这个在程序开头定义的trait。LimitTracker
里面有三个字段:messenger
:类型为&str
字符串切片类型,生命周期为'a
value
:类型为usize
max
:类型为usize
-
往下看,通过
impl
块在LimitTracker
上写了关联函数new
,其参数是类型为泛型引用&T
的形参messenger
和类型为usize
的形参max
,返回值是LimitTracker
类型。这个函数用于创建LimitTracker
实例,这个实例:messenger
字段是形参menssenger
的值value
字段值为0max
字段值为形参max
的值
-
LimitTracker
还有一个方法叫做set_value
,其第一个参数是self
的可变引用&mut self
,第二个参数是value
,类型为usize
。
方法内部的代码逻辑很简单。把self
的value
字段值和参数value
的值相除(还要转换成f64
避免丢失精度)得到一个百分比,存储在percentage_of_max
内。根据percentage_of_max
的大小使用Messenger
trait下的send
方法发送不同的警告。
使用测试替代(test double)进行测试
这里有一个问题,如果我们要对这个set_value
方法进行测试,就需要这个方法得输出些什么东西以供断言。但是set_value
方法实际上并没有返回任何的值,所以说它不会提供任何的结果来进行断言。
我们要测试的是当某一个实现了Messenger
trait的值和一个max
值来创建LimitTracker
实例时,传入不同的value
就能够触发Messenger
发送不同的消息。
为了解决这个问题,这里要介绍test double,它的中文叫测试替代,是一个通用的变成概念,代表了测试工作中被用作其他类型的替代品。test double中有一个特定的类型,叫模拟对象(Mock Object),它会承担记录测试过程中的工作。我们就可以利用这些记录来断言这个测试工作运行是否正确。
Rust里没有类似的概念,在标准库里也没有模拟对象(Mock Object),但是我们可以自定义一个结构体来实现和Mock Object相同的功能。
接着上文的代码来写:
#[cfg(test)]
mod tests {use super::*;struct MockMessenger {sent_messages: Vec<String>,}impl MockMessenger {fn new() -> MockMessenger {MockMessenger {sent_messages: vec![],}}}impl Messenger for MockMessenger {fn send(&self, message: &str) {self.sent_messages.push(String::from(message));}}#[test]fn it_sends_an_over_75_percent_warning_message() {let mock_messenger = MockMessenger::new();let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);limit_tracker.set_value(80);assert_eq!(mock_messenger.sent_messages.len(), 1);}
}
-
在测试模块的最开头先声明了
MockMessenger
结构体,里面有1个字段sent_message
,表示发送的消息,其类型是Vec<String>
-
MockMessenger
在下文又通过impl
块创建了了new
函数用于创建MockMessenger
的实例,实例的sent_messages
字段的值是一个空的Vector
。 -
后面又为
MockMessenger
结构体实现了整个代码一开头的Messenger
trait。实现了这个trait之后MockMessenger
就可以用来创建LimitTracker
(因为LimitTracker
要求泛型类型实现Messenger
trait)。
使用send
方法时这个消息会存储在MockMessenger
下字段sent_message
这个Vector
里。 -
最后是
it_sends_an_over_75_percent_warning_message
这个测试函数,它测试的是超过75%的这部分。
首先创建了MockMessenger
的实例叫mock_messenger
,然后创建了一个LimitTracker
的实例叫limit_tracker
,接着在LimitTracker
的实例上(就是limit_tracker
)调用。
最后通过mock_messenger
下sent_message
这个Vector
里元素的数量来断言。
此时的代码逻辑有问题,但是运行会报错:
error[E0596]: cannot borrow `self.sent_messages` as mutable, as it is behind a `&` reference--> src/lib.rs:58:13|
58 | self.sent_messages.push(String::from(message));| ^^^^^^^^^^^^^^^^^^ `self` is a `&` reference, so the data it refers to cannot be borrowed as mutable|
help: consider changing this to be a mutable reference in the `impl` method and the `trait` definition|
2 ~ fn send(&mut self, msg: &str);
3 | }
...
56 | impl Messenger for MockMessenger {
57 ~ fn send(&mut self, message: &str) {|For more information about this error, try `rustc --explain E0596`.
error: could not compile `limit-tracker` (lib test) due to 1 previous error
错误在为MockMessenger
实现Messenger
trait时定义send
方法的过程:
impl Messenger for MockMessenger {fn send(&self, message: &str) {self.sent_messages.push(String::from(message));}
}
无法修改MockMessenger
来跟踪消息,因为send
方法的函数签名参数是对self
不可变引用。我们也无法使用&mut self
来代替,因为这样send
的签名将与Messenger
trait定义中的签名&self
不匹配。
针对这种需要内部可变性的情况,就可以使用RefCell<T>
,只需要把MockMessenger
的sent_messages
字段用RefCell<T>
再包装一下即可:
struct MockMessenger {sent_messages: RefCell<Vec<String>>,
}
由于RefCell<T>
不在预导入模块中,所以在使用它之前得先把它引入当前作用域
use std::cell::RefCell;
这样改了之后使用了sent_messages
字段的代码都需要使用RefCell<T>
再包装一下:
impl MockMessenger {fn new() -> MockMessenger {MockMessenger {sent_messages: RefCell::new(vec![]),}}
}
RefCell
到底是怎么用的呢?其实就是用RefCell
创建的数据,可以用borrow_mut
方法来修改,对实参调用borrow_mut
方法即可获得一个可变引用,所以为MockMessenger
实现Messenger
trait时定义send
方法就可以使用borrow_mut
:
impl Messenger for MockMessenger {fn send(&self, message: &str) {self.sent_messages.borrow_mut().push(String::from(message));}
}
这样即使send
的参数是不可变引用,在函数体里也可以通过borrow_mut
来修改其值。
最后把测试函数的断言部分改一下:
fn it_sends_an_over_75_percent_warning_message() {let mock_messenger = MockMessenger::new();let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);limit_tracker.set_value(80);assert_eq!(mock_messenger.sent_messages..borrow().len(), 1);
}
对mock_messenger
使用borrow
函数即可获取对该变量的不可变引用用于断言。
这时候运行就没有问题了,整体代码如下:
pub trait Messenger {fn send(&self, msg: &str);
}pub struct LimitTracker<'a, T: Messenger> {messenger: &'a T,value: usize,max: usize,
}impl<'a, T> LimitTracker<'a, T>
whereT: Messenger,
{pub fn new(messenger: &'a T, max: usize) -> LimitTracker<'a, T> {LimitTracker {messenger,value: 0,max,}}pub fn set_value(&mut self, value: usize) {self.value = value;let percentage_of_max = self.value as f64 / self.max as f64;if percentage_of_max >= 1.0 {self.messenger.send("Error: You are over your quota!");} else if percentage_of_max >= 0.9 {self.messenger.send("Urgent warning: You've used up over 90% of your quota!");} else if percentage_of_max >= 0.75 {self.messenger.send("Warning: You've used up over 75% of your quota!");}}
}#[cfg(test)]
mod tests {use super::*;use std::cell::RefCell;struct MockMessenger {sent_messages: RefCell<Vec<String>>,}impl MockMessenger {fn new() -> MockMessenger {MockMessenger {sent_messages: RefCell::new(vec![]),}}}impl Messenger for MockMessenger {fn send(&self, message: &str) {self.sent_messages.borrow_mut().push(String::from(message));}}#[test]fn it_sends_an_over_75_percent_warning_message() {let mock_messenger = MockMessenger::new();let mut limit_tracker = LimitTracker::new(&mock_messenger, 100);limit_tracker.set_value(80);assert_eq!(mock_messenger.sent_messages.borrow().len(), 1);}
}
15.6.5. 使用RefCell<T>
在运行时记录借用信息
实际上,上文所使用的borrow_mut
和borrow
方法相当于提供给用户的两个安全接口:
borrow
:返回智能指针Ref<T>
,它实现了Deref
traitborrow_mut
:返回智能指针RefMut<T>
,实现了Deref
trait
RefCell<T>
会记录当前存在多少活跃的Ref<T>
和RefMut<T>
:
- 每次调用
borrow
:不可变借用计数加1。
任何一个Ref<T>
的值离开作用域被释放:不可变借用计数减1 - 每次调用
borrow_mut
:可变借用计数加1
任何一个RefMut<T>
的值离开作用域被释放:可变借用计数减1
与编译时借用规则(详见 4.4. 引用与借用)一样, RefCell<T>
允许我们在任何时间点拥有许多不可变借用或一个可变借用。
如果我们尝试违反这些规则, RefCell<T>
的实现将在运行时出现恐慌(因为RefCell<T>
在运行时才会进行借用规则检查)。出现恐慌 already borrowed: BorrowMutError
就是RefCell<T>
在运行时处理违反借用规则的方式。
15.6.6. 将Rc<T>
和RefCell<T>
结合使用的例子
Rc<T>
允许某些数据被多个所有者持有,但它只提供对该数据的不可变访问。如果您有一个包含RefCell<T>
的Rc<T>
,你可以获得一个可以拥有多个所有者并且可变的值。
下面看一个将Rc<T>
和RefCell<T>
结合使用来实现多重数据所有权的可变数据:
#[derive(Debug)]
enum List {Cons(Rc<RefCell<i32>>, Rc<List>),Nil,
}use crate::List::{Cons, Nil};
use std::cell::RefCell;
use std::rc::Rc;fn main() {let value = Rc::new(RefCell::new(5));let a = Rc::new(Cons(Rc::clone(&value), Rc::new(Nil)));let b = Cons(Rc::new(RefCell::new(3)), Rc::clone(&a));let c = Cons(Rc::new(RefCell::new(4)), Rc::clone(&a));*value.borrow_mut() += 10;println!("a after = {a:?}");println!("b after = {b:?}");println!("c after = {c:?}");
}
还记得上一篇文章的Cons列表示例吗?其中我们使用了Rc<T>
允许多个列表共享另一个列表的所有权。因为Rc<T>
仅保存不可变值,一旦创建了列表中的任何值,我们就无法更改它们。通过这篇文章的内容,让我们添加RefCell<T>
以获得更改列表中的值的能力:
- 首先在生命枚举类型
List
时把Cons
关联的i32
类型用RefCell<>
包裹,由于Rust编译器无法确定RefCell<T>
大小,得用Rc<>
包裹在外,其余保持不变 - 记得引入
Rc
和RefCell
到当前作用域 - 下面通过
Rc::new()
和RefCell::new()
来创建实例,a
通过Rc::clone()
来共享value
的值,b
和c
通过Rc::clone()
来共享a
的值(前提是a
被Rc<>
包裹)。 - 最后通过
RefCell<T>
上的borrow_mut
获得value
的可变引用,其类型时&i32
,然后通过解引用符号*
变为i32
来进行加10的操作。
输出:
a after = Cons(RefCell { value: 15 }, Nil)
b after = Cons(RefCell { value: 3 }, Cons(RefCell { value: 15 }, Nil))
c after = Cons(RefCell { value: 4 }, Cons(RefCell { value: 15 }, Nil))
跟预期一样,没有问题。
15.6.7. 其他可以实现内部可变性的类型
Cell<T>
:通过复制来访问数据Mutex<T>
:用于实现跨线程情况下的内部可变性模5
相关文章:

【Rust自学】15.6. RefCell与内部可变性:“摆脱”安全性限制
题外话,这篇文章一共4050字,是截止到目前为止最长的文章,如果你能坚持读完并理解,那真的很强! 喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以…...
14.模型,纹理,着色器
模型、纹理和着色器是计算机图形学中的三个核心概念,用通俗易懂的方式来解释: 1. 模型:3D物体的骨架 通俗解释: 模型就像3D物体的骨架,定义了物体的形状和结构。 比如,一个房子的模型包括墙、屋顶、窗户等…...
【C语言分支与循环结构详解】
目录 ---------------------------------------begin--------------------------------------- 一、分支结构 1. if语句 2. switch语句 二、循环结构 1. for循环 2. while循环 3. do-while循环 三、嵌套结构 结语 -----------------------------------------end----…...

新项目上传gitlab
Git global setup git config --global user.name “FUFANGYU” git config --global user.email “fyfucnic.cn” Create a new repository git clone gitgit.dev.arp.cn:casDs/sawrd.git cd sawrd touch README.md git add README.md git commit -m “add README” git push…...

qt-QtQuick笔记之常见项目类简要介绍
qt-QtQuick笔记之常见项目类简要介绍 code review! 文章目录 qt-QtQuick笔记之常见项目类简要介绍1.QQuickItem2.QQuickRectangle3.QQuickImage4.QQuickText5.QQuickBorderImage6.QQuickTextInput7.QQuickButton8.QQuickSwitch9.QQuickListView10.QQuickGridView11.QQuickPopu…...

Continuous Batching 连续批处理
原始论文题目: Continuous Batching — ORCA: a distributed serving system for Transformer-based generative models 关键词: Continuous Batching, iteration-level scheduling, selective batching 1.迭代级调度(iteration-level scheduling) Orca系统又由几个关键…...

海外问卷调查渠道查如何设置:最佳实践+示例
随着经济全球化和一体化进程的加速,企业间的竞争日益加剧,为了获得更大的市场份额,对企业和品牌而言,了解受众群体的的需求、偏好和痛点才是走向成功的关键。而海外问卷调查才是获得受众群体痛点的关键,制作海外问卷调…...

把本地搭建的hexo博客部署到自己的服务器上
配置远程服务器的git 安装git 安装依赖工具包 yum install -y curl-devel expat-devel gettext-devel openssl-devel zlib-devel安装编译工具 yum install -y gcc perl-ExtUtils-MakeMaker package下载git,也可以去官网下载了传到服务器上 wget https://www.ke…...

初阶数据结构:链表(二)
目录 一、前言 二、带头双向循环链表 1.带头双向循环链表的结构 (1)什么是带头? (2)什么是双向呢? (3)那什么是循环呢? 2.带头双向循环链表的实现 (1)节点结构 (2…...
postgresql根据主键ID字段分批删除表数据
生产环境针对大表的处理相对比较麻烦。 方案1、直接truncate,可能会遇到系统卡主的情况,因为truncate的过程中会对表进行加锁,会导致数据不能正常的写入 方案2、创建一个同结构的表结构,rename旧表,不停业务rename表担…...
10.business english-global market
eco-friendly case study: 案例学习 At the workshop工作坊, they agreed to emphasize eco-friendliness,adapt messageing, and boost digital marketing to stand out globally. Our study shows that more people want eco-friendly products in different places.Looks …...
C 语言实现计算一年中指定日期是第几天 题】
引言 在编程的世界里,处理日期和时间相关的问题是非常常见的。比如在日历应用、任务管理系统、数据分析等场景中,经常需要计算某个日期在一年中是第几天。本文将详细介绍如何使用 C 语言来实现这一功能,通过分析代码的结构、逻辑以及可能存在…...
深入理解三高架构:高可用性、高性能、高扩展性的最佳实践
引言 在现代互联网环境下,随着用户规模和业务需求的快速增长,系统架构的设计变得尤为重要。为了确保系统能够在高负载和复杂场景下稳定运行,"三高架构"(高可用性、高性能、高扩展性)成为技术架构设计中的核…...

【反悔堆】力扣1642. 可以到达的最远建筑
给你一个整数数组 heights ,表示建筑物的高度。另有一些砖块 bricks 和梯子 ladders 。 你从建筑物 0 开始旅程,不断向后面的建筑物移动,期间可能会用到砖块或梯子。 当从建筑物 i 移动到建筑物 i1(下标 从 0 开始 )…...

关于使用Mybatis-plus的TableNameHandler动态表名处理器实现分表业务的详细介绍
引言 随着互联网应用的快速发展,数据量呈爆炸式增长。传统的单表设计在面对海量数据时显得力不从心,容易出现性能瓶颈、查询效率低下等问题。为了提高数据库的扩展性和响应速度,分表(Sharding)成为了一种常见的解决方案…...

docker 安装 redis 详解
在平常的开发工作中,我们经常会用到 redis,那么 docker 下应该如何安装 redis 呢?简单来说:第一步:拉取redis镜像;第二步:设置 redis.conf 配置文件;第三步:编写 docker-…...

56. 合并区间
【题目】:56. 合并区间 class Solution { public:vector<vector<int>> merge(vector<vector<int>>& intervals) {// 按照左端点排序sort(intervals.begin(), intervals.end(), [&](vector<int> lhs, vector<int> rhs)…...

BOM对象location与数组操作结合——查询串提取案例
BOM对象location与数组操作结合——查询串提取案例 前置知识 1. Location 对象 Location 对象是 JavaScript 提供的内置对象之一,它表示当前窗口或框架的 URL,并允许你通过它操作或获取 URL 的信息。可以通过 window.location 访问。 主要属性&#…...
Jetson Orin Nano Super之 onnxruntime 编译安装
Jetson Orin Nano Super之 onnxruntime 编译安装 1. 源由2. 步骤步骤一:安装3.26 cmake步骤二:下载代码步骤三:编译代码步骤四:找到安装包步骤五:安装whl包 3. 注意4. 参考资料 1. 源由 Build onnxruntime 1.19.2 fai…...

开发环境搭建-3:配置 nodejs 开发环境 (fnm+ node + pnpm)
在 WSL 环境中配置:WSL2 (2.3.26.0) Oracle Linux 8.7 官方镜像 node 官网:https://nodejs.org/zh-cn/download 点击【下载】,选择想要的 node 版本、操作系统、node 版本管理器、npm包管理器 根据下面代码提示依次执行对应代码即可 基本概…...
synchronized 学习
学习源: https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖,也要考虑性能问题(场景) 2.常见面试问题: sync出…...
【解密LSTM、GRU如何解决传统RNN梯度消失问题】
解密LSTM与GRU:如何让RNN变得更聪明? 在深度学习的世界里,循环神经网络(RNN)以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而,传统RNN存在的一个严重问题——梯度消失&#…...
基础测试工具使用经验
背景 vtune,perf, nsight system等基础测试工具,都是用过的,但是没有记录,都逐渐忘了。所以写这篇博客总结记录一下,只要以后发现新的用法,就记得来编辑补充一下 perf 比较基础的用法: 先改这…...
【git】把本地更改提交远程新分支feature_g
创建并切换新分支 git checkout -b feature_g 添加并提交更改 git add . git commit -m “实现图片上传功能” 推送到远程 git push -u origin feature_g...
LOOI机器人的技术实现解析:从手势识别到边缘检测
LOOI机器人作为一款创新的AI硬件产品,通过将智能手机转变为具有情感交互能力的桌面机器人,展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家,我将全面解析LOOI的技术实现架构,特别是其手势识别、物体识别和环境…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

高端性能封装正在突破性能壁垒,其芯片集成技术助力人工智能革命。
2024 年,高端封装市场规模为 80 亿美元,预计到 2030 年将超过 280 亿美元,2024-2030 年复合年增长率为 23%。 细分到各个终端市场,最大的高端性能封装市场是“电信和基础设施”,2024 年该市场创造了超过 67% 的收入。…...

【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法
使用 ROS1-Noetic 和 mavros v1.20.1, 携带经纬度海拔的话题主要有三个: /mavros/global_position/raw/fix/mavros/gpsstatus/gps1/raw/mavros/global_position/global 查看 mavros 源码,来分析他们的发布过程。发现前两个话题都对应了同一…...

Linux 内存管理调试分析:ftrace、perf、crash 的系统化使用
Linux 内存管理调试分析:ftrace、perf、crash 的系统化使用 Linux 内核内存管理是构成整个内核性能和系统稳定性的基础,但这一子系统结构复杂,常常有设置失败、性能展示不良、OOM 杀进程等问题。要分析这些问题,需要一套工具化、…...
GitHub 常见高频问题与解决方案(实用手册)
1.Push 提示权限错误(Permission denied) 问题: Bash Permission denied (publickey) fatal: Could not read from remote repository. 原因: 没有配置 SSH key 或使用了 HTTPS 而没有权限…...