rust 自动化测试、迭代器与闭包、智能指针、无畏并发
编写测试可以让我们的代码在后续迭代过程中不出现功能性缺陷问题;理解迭代器、闭包的函数式编程特性;Box
智能指针在堆上存储数据,Rc
智能指针开启多所有权模式等;理解并发,如何安全的使用线程,共享数据。
自动化测试
编写测试以方便我们在后续的迭代过程中,不会改坏代码。保证了程序的健壮性。
测试函数通常进行如下操作:
(资料图片仅供参考)
- 设置需要的数据或状态
- 运行需要测试的代码
- 断言其结果是我们期望的
在 rust 中,通过test
属性、断言宏和一些属性设置来测试代码。
$> cargo new ifun-grep --lib
创建项目时,通过--lib
表明创建一个库,会默认生成一个测试示例,在src/lib.rs
中
pub fn add(left: usize, right: usize) -> usize { left + right}#[cfg(test)]mod tests { use super::*; #[test] fn it_works() { let result = add(2, 2); assert_eq!(result, 4); }}
进入到项目中,执行cargo test
就会看到执行完测试的详细信息。包括了测试数量、通过测试数、失败测试数等等维度
首先使用mod tests
定义了一个 tests 模块,内部函数需要使用外部方法,在最顶部调用了use super::*;
。这在包的一节里已有说明。
#[cfg(test)]
标注测试模块。它可以告诉 rust 在编译时不需要包含该测试代码。
#[test]
表明是测试函数,通过 assert_eq!()
断言结果值是否相同。
可以手动改动一下断言值assert_eq!(result, 5)
,再次执行可以看到测试不通过,并给出了结果的不同之处。
由 rust 标准库提供的断言测试宏,帮助我们处理结果值。结果与预期相同时,则测试会通过;不一样时,则会调用panic!
宏,导致测试失败。
assert!()
一个必传参数,true
是测试通过;false
测试失败。assert_eq!()
两个必传参数,比对它们是否相同。assert_ne!
两个必传参数,比对它们是否不相同。
assert_eq!
和assert_ne
断言失败时,会打印出两个值,便于观察为什么失败。因为会打印输出,所以两个值必须实现PartialEq
和Debug trait
可以被比较和输出调试。
如果是我们自定义的结构体或枚举类型,则可以直接增加#[derive(PartialEq, Debug)]
注解。如果是复杂的类型,则需要派生宏trait
,这在后面的文章会讲。
#[derive(PartialEq,Debug)]struct User { name: String,}
宏除了它们必须的参数之外,也可以传递更多的参数,这些参数会被传递给format!()
打印输出。这样我们可以增加一些输出,方便解决断言失败的问题
assert_eq!(result, 5, "hello rust!");
测试程序处理错误
除了测试程序正常执行逻辑的结果,也需要测试程序发生错误时,是否按照我们的错误处理逻辑 处理了错误。
假设我们的被测试函数接受的参数不能大于100
,大于时panic
错误 信息
pub fn add(left: usize, right: usize) -> usize { if right > 100 { panic!("the value exceeds 100!"); } left + right}#[cfg(test)]mod tests { use super::*; #[test] fn it_works() { let result = add(2, 102); assert_eq!(result, 104); }}
执行测试cargo test
,就算断言结果时逻辑正确的,但是我们的函数限制了参数最大值,测试不通过。
增加测试用例来测试这种场景,通过增加#[should_panic]
来处理程序确实有这种限制,并panic!
。
#[test]#[should_panic]fn value_exceed_100() { add(5, 120);}
执行cargo test
,可以看到测试示例通过了。如果我们符合参数要求,测试示例就会是失败
但如果我们代码中有多个错误panic!()
,就会有同样的多个测试示例不通过,打印输出并没有给我们足够的信息去找到问题所在。通过should_panic
可选择参数expected
提供一个错误描述信息,
pub fn add(left: usize, right: usize) -> usize { if right > 100 { panic!("the value exceeds 100!,got {}", right) } else if right < 50 { panic!("the value does not less than 50!,got {}", right) } left + right}#[cfg(test)]mod tests { use super::*; #[test] #[should_panic(expected = "exceeds 100!")] fn value_exceed_100() { add(5, 99); } #[test] #[should_panic(expected = "less than 50!")] fn value_not_less_50() { add(59, 59); }}
也可以通过Result
编写测试,在程序失败时,返回Err
而不是panic
;
#[test]fn add_equal() -> Result<(), String> { if add(5, 105) == 111 { Ok(()) } else { Err(String::from("Error in add")) }}
此时不能使用#[should_panic()]
注解。也不能使用表达式?
控制测试运行
cargo test
在测试模式下编译代码并发运行生成的测试二进制文件。
- 可以通过设置测试线程,单次只执行一个测试示例
$> cargo test -- --test-threads=1
测试线程为 1,程序不会使用任何并行机制。
- 默认的测试在测试示例通过时,不会打印输出。通过设置在测试成功时也输出程序中的打印
$> cargo test -- --show-output
- 默认的
cargo test
会运行所有测试,通过指定名称来运行部分测试
$> cargot test add_equal
过滤运行多个测试,可以通过指定测试名称的一部分,只要匹配这个名称的测试都会被运行。
$> cargot test value
通过#[ignore]
标记忽略该测试。
#[test]#[ignore]fn add_equal() -> Result<(), String> { if add(5, 105) == 110 { Ok(()) } else { Err(String::from("Error in add")) }}
测试被忽略,但是可以通过cargot test -- --ignored
来运行被忽略的测试。
如果想运行所有的测试,可以通过cargot test -- --include-ignored
集成测试
单元测试可以在指定的模块中书写测试实例,每次测试一个模块,也可以测试私有接口。
集成测试对库来说是外部的,只能测试公有接口,可测试多个模块。通过创建tests
目录编写独立的测试文件。
tests/lib.rs
use ifun_grep;#[test]#[should_panic(expected = "exceeds")]fn value_exceed_100() { ifun_grep::add(5, 99);}
随着集成测试模块的增多,我们需要更好的组织它们,可以根据测试的功能将测试分组。将一些测试公共模块抽离出来,作为其他测试功能组的测试函数调用
比如tests/common.rs
pub fn init(){ // something init}
再执行cargo test
,会看到运行了tests/common.rs
运行了 0 个测试。这显然是我们不需要的,可以改写文件目录tests/common/mod.rs
,这会告诉 rust 不要将common
看作一个集成测试文件。
迭代器与闭包
rust 类似函数式编程语言的特性。可以将函数作为参数值或返回值、将函数赋值给变量等。
闭包
可以储存在变量里的类似函数的结构。保存在一个变量中或作为参数传递给其他函数的匿名函数。
闭包允许捕获被定义时所在作用域中的值。
#[derive(Debug)]enum Name { Admin, Test,}#[derive(Debug)]struct User {}impl User { fn get_name(&self, name: Option) -> Name { name.unwrap_or_else(|| self.random_name()) } fn random_name(&self) -> Name { Name::Admin }}fn main(){ let user = User {}; println!("{:?}", user.get_name(Some(Name::Test))); println!("{:?}", user.get_name(None));}
unwrap_or_else
方法接受一个闭包函数,当一个Some
值存在时直接返回,如果不存在则执行其传入的闭包函数计算一个值返回。
闭包不需要在参数或返回值上注明类型。闭包通常只关联小范围的上下文而非任意情景,所以编译器可以推导出参数和返回值类型。
也可以显示定义闭包的参数和返回值的类型:
fn main(){ let get_age = |age: i8| -> i8 { age }; // let get_age = |age| age; println!("{}", get_age(32));}
相对于增加参数或返回值类型使得书写更加的繁琐。而对于未标注类型的闭包,在第一次调用后就确定其参数和返回值类型,再传其他类型时就会报错。
fn main(){ let get_age = |age| age; println!("{}", get_age(String::from("admin"))); // 调用出错,已经确定了参数和返回值类型为String println!("{}", get_age(32));}
捕获引用或移动所有权
在传递给闭包参数时,需要考虑参数的传递方式:不可变借用、可变借用和获取所有权。这是根据传递的值决定的。
对于不可变借用,变量可以在任何情形下被访问。
let str = String::from("hboot");let print_str = || println!("{:?}", str);println!("{str}");print_str();println!("{str}");
而对于可变借用,则只能在借用结束后调用.声明的闭包函数也需要mut
声明
let mut str = String::from("hboot");let mut print_str = || str.push_str("-rust");// println!("{str}");print_str();println!("{str}");
通过move
关键字将变量的所有权转移闭包所在的环境中。
use std::thread;fn main(){ let mut str = String::from("hboot"); println!("{str}"); thread::spawn(move || { str.push_str("-rust"); println!("{str}") }) .join() .unwrap();}
此时,将变量str
值的所有权转移到了新线程中,主线程则不能再使用。
将被捕获的值移出闭包和 Fn trait
在闭包环境中,捕获和处理值的方式会影响闭包 trait 的实现。trait 是函数或结构体指定它们可以使用什么类型的闭包。
从闭包如何任何处理值、闭包自动、渐进实现一个、多个 Fn
trait
FnOnce
适用于调用一次的闭包。所有闭包都是实现这个 trait,它会将捕获的值移除闭包。FnMut
不会将捕获的值移除闭包,可能会修改值。会被调用 多次。Fn
不会移除捕获的值,也不修改捕获的值。会被调用多次而不改变其环境。
这是Option
的unwrap_or_else()
方法定义
impl Option { pub fn unwrap_or_else(self, f: F) -> T where F: FnOnce() -> T { match self { Some(x) => x, None => f(), } }}
F
就是闭包指定的类型,T
是返回值类型。FnOnce()->T
表明了闭包会被调用一次,有值时Some
,返回值;没有值时None
,f
调用一次。
在使用闭包时,如果我们不需要捕获其环境中的值,则可以不使用闭包,而使用传递函数作为参数。
迭代器
迭代器是处理元素序列的方式。遍历序列中的每一项以及决定序列何时结束的逻辑。
fn main(){ let arr = [1, 2, 3, 4]; for val in arr { println!("{}", val) }}
迭代器都定义了Iterator
trait,并实现next
方法。调用next
返回迭代器的一个项,封装在Some
中,结束后返回None
pub trait Iterator { type Item; fn next(&mut self) -> Option;}
type Item
和Self::Item
定义了 trait 的关联类型。表明了迭代器返回值类型为Item
可以通过next()
方法迭代获取值:
fn main(){ let arr = [1, 2, 3, 4]; let mut iter = arr.iter(); println!("{:?}", iter.next()); println!("{:?}", iter.next());}
iter()
生成一个不可变引用的迭代器。对于迭代器实例iter
必须是mut
可变的。
into_ter()
获取到 arr 所有权的迭代器。iter_mut()
可以获取到可变引用迭代器。
消费适配器
调用next()
方法的方法被称为消费适配器。
fn main() { let arr = [1, 2, 3, 4]; let total: i8 = arr.iter().sum(); println!("{}", total);}
这些方法总是会获取迭代器的所有权并反复调用 next
来遍历迭代器。sum()
方法返回调用next
方法获取值,最终返回和值。
迭代器适配器
将当前迭代器变为不同类型的迭代器。可以链式调用多个迭代器适配器,但是每次调用都必须调用消费适配器来获取调用结果。
fn main(){ let arr = [1, 2, 3, 4]; let arr2: Vec<_> = arr.iter().map(|val| val + 1).collect(); for val in arr2 { println!("{}", val) }}
map()
方法接受一个闭包函数,可以在遍历元素上执行任何操作。进行了一次迭代适配器操作,然后通过collect()
方法获取调用的结果值。
智能指针
指针是一个包含内存地址的变量。智能指针是一类数据结构,表现同指针,并拥有额外的元数据和功能。
智能指针通常使用结构体实现,实现了Deref
和Drop
trait。deref trait
允许智能指针结构体实例表现的像引用一样;drop trait
允许智能指针离开作用域时自定义运行代码
标准库中常用的智能指针:
Box
用于在堆上分配值Rc
引用计数类型,其数据可以有多个所有者Ref
通过、RefMut RefCell
访问,这是一个在运行时执行借用规则的类型。
Box
智能指针 box 允许将一个值放在堆上而不是栈上。留在栈上的则是指向堆数据的指针。
在以下情况下可以考虑使用:
- 编译时未知大小的类型,又想在确切大小的上下文中使用这个类型的值。
- 当有大量数据不被拷贝的情况下转移所有权的时候
- 当有一个值只关心它的类型是否实现特定 trait,而不是具体类型的时候
fn main(){ let b = Box::new(100); println!("{}", b);}
直接声明创建 box 类型变量,并分配了一个值100
存储在堆上, 可以直接访问变量访问值。
通过cons
list 数据结构定义递归数据类型
它是construct function
的缩写,利用两个参数构造一个新的列表.最后一项值包含了Nil
值,标识结束
enum List { Cons(i32, Box), Nil,}use crate::List::{Cons, Nil};fn main(){ let list = Cons(1, Box::new(Cons(2, Box::new(Cons(3, Box::new(Nil))))));}
Cons
可能会无限嵌套下去,为了保证 rust 编译时计算需要的大小,只能通过Box
来帮助 rust 计算出List
需要的大小。
Deref
trait 重载解引用运算符*
之前已经使用过*
解引用值,可以获取到指针指向引用的值。
fn main(){ let mut s = String::from("hboot"); let s1 = &mut s; *s1 += " admin"; println!("{}", s)}
s1
是 s 的可变引用,再通过*
解引用后,可以修改存储在堆上的数据。
也可以通过Box
代替引用,和*
拥有相同的功能。
fn main(){ let s = String::from("hboot"); let mut s1 = Box::new(s); *s1 += " admin"; println!("{:?}", s1);}
Box
会拷贝s
在栈上的指针数据,导致存储在堆上的数据所有权被转移,s
在后续变的不可用。
自定义实现一个智能指针MyBox
,它可以做到上面的解引用操作
#[derive(Debug)]struct MyBox(T);impl MyBox { fn new(val: T) -> MyBox { MyBox(val) }}
实现了一个元组结构体,自定义实例new
方法,接受一个参数进行初始化操作。还需要实现解引用功能,Deref
trait 由标准库提供,实现 deref 方法
use std::ops::Deref;impl Deref for MyBox { type Target = T; fn deref(&self) -> &Self::Target { &self.0 }}
上述的解引用例子,则可以由MyBox
代替实现。type Target = T
定义了 trait 的关联类型,&self.0
访问元组结构体的第一个元素。
fn main(){ let s = String::from("hboot"); let s1 = MyBox::new(s); // *s1 += " admin"; println!("{:?}", *s1);}
因为实现的是Deref
所以不能修改,修改时需要实现DerefMut
trait。
实现了Deref
trait 的数据类型,在函数传参时,可做到隐式转换,而不需要手动去转换为参数需要的类型。
fn print(val: &str) { println!("{}", val)}fn main(){ // 输出上面的示例 s1 print(&s1);}
对于数据的强制转换,只能将可变引用转为不可变引用;不能将不可变引用转为可变引用。
Drop
trait 运行清理代码
实现了Drop
trait 的数据,在离开作用域时,会调用其实现的drop
方法,它获取一个可变引用。
为上述的MyBox
实现Drop
,无需引入,Drop
trait 是 prelude 的。
impl Drop for MyBox { fn drop(&mut self) { println!("mybox drop value"); }}
再次调用执行,可以看到最终在程序执行完毕后,打印输出了mybox drop value
. drop
会自动执行,而无需手动调用。
如果想要提前销毁资源,则需要std::mem::drop
,可以调用drop
方法
fn main(){ drop(s1); // 手动清理后,后续不能再使用s1 // print(&s1);}
Rc
引用计数启用多所有权模式
在图形结构中,每个节点都有多个边指向,所以每个节点都会拥有指向它的边的所有权。
通过使用Rc
类型,记录被引用的数量,来确定这个值有没有被引用。如果为 0 没有被引用,则会被清理。
Rc
只适用于单线程
创建Rc
类型的变量s
,然后通过Rc::clone
克隆变量s
生成s1\s2
.
use std::rc::Rc;fn main(){ let s = Rc::new(String::from("hboot")); let s1 = Rc::clone(&s); let s2 = Rc::clone(&s); println!("s:{},s1:{},s2:{}", s, s1, s2)}
这里可以看到s1、s2
没有获取s
的所有权,它们仍然同时生效。Rc::clone
不同于深拷贝,只会增加引用计数。
可以通过strong_count()
方法查看被引用次数
fn main(){ let s = Rc::new(String::from("hboot")); println!("s create - {}", Rc::strong_count(&s)); let s1 = Rc::clone(&s); println!("s1 create - {}", Rc::strong_count(&s)); { let s2 = Rc::clone(&s); println!("s2 create - {}", Rc::strong_count(&s)); } println!("s2 goes out of scope - {}", Rc::strong_count(&s));}
执行测试输出为
通过不可变引用,Rc
允许程序在多个部分之间只读地共享数据。
RefCell
允许修改不可变引用
根据 rust 的不可变引用规则,被引用的变量是不允许修改。但是在某些模式下,可以做到修改,也就是内部可变性模式。
内部可变性通过在数据结构中使用unsafe
代码来模糊 rust 的不可变性和借用规则。unsafe
不安全代码表明我们需要手动去检查代码而不是让编译器检查。
RefCell
类型是在代码运行时作用检测不可变或可变借用规则,而通常的规则检测是在编译阶段。
特点:
- 可以在允许出现特定内存安全的场景中使用。
- 需要确认你的代码遵守了借用规则,但是 rust 无法理解
- 只能用于单线程
RefCell
在运行时记录借用,通过borrow()
和borrow_mut()
方法,会返回Ref
和RefMut
智能指针,并实现了Deref
trait.
定义一个MixName
trait,然后结构体User
实现了它,并实现它的方法mix
.
use std::cell::RefCell;pub trait MixName { fn mix(&self, suffix: &str);}struct User { name: RefCell,}impl User { fn new() -> User { User { name: RefCell::new(String::from("hboot")), } }}impl MixName for User { fn mix(&self, suffix: &str) { self.name.borrow_mut().push_str(suffix); }}
mix
方法修改了 self 内部属性name
的值,但是我们可以看到&self
时不可变引用,这归功于RefCell
创建值,使得不可变借用可以修改其内部值。
fn main(){ let user = User::new(); user.mix(" hello"); println!("{:?}", user.name.borrow());}
执行程序可以看到内部的值已经被修改了。RefCell
会在调用borrow
时,记录借用次数,当离开了作用域时,借用次数减一。
RefCell
只能有一个所有者,结合Rc
使其拥有多个可变数据所有者。
use std::cell::RefCell;use std::rc::Rc;fn main(){ let s = Rc::new(RefCell::new(String::from("hboot"))); let s1 = Rc::clone(&s); let s2 = Rc::clone(&s); *s.borrow_mut() += " good"; println!("{:?}", s);}
通过RefCell
来创建变量,然后通过Rc
开启多所有权,这样在*s.borrow_mut() += " good";
,修改后,变量s、s1、s2
的值都发生了变更。
但是这只能在单线中使用,如果想要多线程使用,则需要使用并发安全的Mutex
类型。
无畏并发
并发编程 - 代表程序的不同部分相互独立的运行。
并行编程 - 代表程序不同部分同时执行。
thread
多线程运行代码
多线程运行代码可以提高程序的执行效率。也会造成一些问题
- 多个线程在不同时刻访问同一数据资源,形成竞争
- 相互等待对方,造成死锁
- 一些情况下出现的难以修复的 bug
使用thread::spawn
创建一个线程,它接受一个闭包函数
use std::thread;fn main() { thread::spawn(|| { println!("hello!"); }); println!("rust!");}
可以看到输出,先是rust!
,也就是主线程先执行。可以多次执行cargo run
以观察结果,会出现新线程没有打印输出,这是因为主线程结束,新线程也会结束,而不会等待新线程是否执行完毕。
可以通过线程休眠,展示这一特点
use std::thread;use std::time::Duration;fn main() { thread::spawn(|| { thread::sleep(Duration::from_millis(2)); println!("hello!"); }); println!("rust!");}
程序基本没有什么机会切换到新线程去执行,也看不到新线程的打印输出。
可以通过thread::spawn
的返回值线程实例,然后调用join()
方法,来等待线程结束
let thread = thread::spawn(|| { thread::sleep(Duration::from_millis(2)); println!("hello!");});println!("rust!");thread.join().unwrap();
再次执行,可以看到新线程的打印输出。join()
会阻塞当前线程,知道线程实例thread
执行完毕。可以将thread.join().unwrap();
放在主线程输出之前,优先执行
thread.join().unwrap();println!("rust!");
通过move
关键字强制闭包获取其所有权,thread::spawn
创建线程给的闭包函数没有任何参数,需要使用主线程里的变量
let name = String::from("hboot");let thread = thread::spawn(move || { thread::sleep(Duration::from_millis(2)); println!("hello! - {}", name);});
新线程强制获取了环境中变量的所有权,保证了新线程执行不会出错。如果是引用,那么由于新线程的执行顺序,可能会在主线程执行过程使引用失效,从而导致新线程执行报错
线程间消息传递
通过channel
实现线程间消息的传递并发。
通过mpsc::channel
创建通信通道,这个通道可以有多个发送端,但只能有一个接收端.
use std::sync::mpsc;fn main(){ let (send, receive) = mpsc::channel(); thread::spawn(move || { let name = String::from("rust"); send.send(name).unwrap(); }); let receive_str = receive.recv().unwrap(); println!("get thread msg :{}", receive_str);}
mpsc::channel()
生成一个通过,返回一个元组,第一个是发送者,第二个是接收者。然后创建一个新线程,通过实例对象send
发送一条信息;在主线程中通过实例对象receive
接受数据。
不管是send()
发送方法还是recv()
方法,它们都返回Result
类型,如果接受端或发送端被清除了,则会返回错误。
接受recv()
方法是阻塞线程的,也就是必须接收到一个值。还有一个方法try_recv()
方法则不会阻塞,需要频繁去调用,在有可用消息时进行处理。
新线程将变量name
发送出去,那么它的所有权也被转移 出去了,后续不能使用它
send.send(name).unwrap();// 在发送后,不能再使用改变量println!("{}", name);
当在子线程中连续多次发送多个值时,可以通过迭代器遍历receive
获取值
fn main(){ let (send, receive) = mpsc::channel(); thread::spawn(move || { send.send(1).unwrap(); send.send(10).unwrap(); send.send(100).unwrap(); }); for receive_str in receive { println!("{}", receive_str); }}
上述例子只是单发送者,可以通过clone()
方法克隆send
发送对象,然后传给另一个线程
fn main(){ let (send, receive) = mpsc::channel(); let send_str = send.clone(); thread::spawn(move || { send_str.send("hello").unwrap(); send_str.send("rust").unwrap(); }); thread::spawn(move || { send.send("good").unwrap(); send.send("hboot").unwrap(); }); for receive_str in receive { println!("{}", receive_str); }}
创建两个线程,一个线程传入时克隆的send_str
,它们都发送消息,然后在主线程中,接收到所有消息。
多个线程由于执行顺序导致打印输出的顺序也不尽相同。这依赖于系统,我们可以通过线程休眠做实验,观察到输出的顺序不同
线程间共享状态
除了相互之间发送消息外, 还可以通过共享数据,来传递数据状态变化。
通过Mutex
创建共享数据,在需要使用的线程中通过lock()
获取锁,以访问数据。
use std::sync::{Mutex};fn main()[ let name = Mutex::new(String::from("hboot")); { let mut name = name.lock().unwrap(); *name += " good!"; } println!("{:?}", name.lock().unwrap());]
新创建的数据hboot
,在局部作用域中获取锁,然后解引用后变更值,最终打印输出可以看到变更后的数据。
Mutext
是一个智能指针,调用lock()
返回了一个MutexGuard
智能指针,它实现了Deref
来指向内部数据,同时也提供Drop
实现了当离开作用域时自动释放锁。
正因为这样,我们在编码时,不会因为忘记释放锁而导致其他线程访问不了数据。
如果想要在多个线程中访问共享数据,因为线程需要转移所有权,这样导致共享数据每次只能在一个线程中使用,通过Arc
来创建多所有者,使得共享数据可被多个线程同时访问。
use std::sync::{Arc, Mutex};use std::thread;fn main(){ let name = Arc::new(Mutex::new(String::from("hboot"))); let mut thread_arr = vec![]; for val in ["admin", "test", "hello", "rust"] { let name = Arc::clone(&name); let thread = thread::spawn(move || { let mut name = name.lock().unwrap(); *name += val; }); thread_arr.push(thread); } for thread in thread_arr { thread.join().unwrap(); } println!("{:?}", name.lock().unwrap())}
Arc
拥有和Rc
相同的 api,它可以用于并发环境的类型。这是一个原子引用计数类型。
Mutex
同RefCell
一样,提供了内部可变性,通过获取内布值的可变引用修改值。当然,Mutex
也会有出现相互引用锁死的风险,两个线程需要锁住两个资源而各自已经锁了一个,造成了互相等待的问题。
Sync
和Send trait
扩展并发
除了使用 rust 标准库提供的处理并发问题,还可以使用别人编写的并发功能
当尝试编写并发功能时,有两个并发概念:
通过
Send trait
表明实现了Send
的类型值的所有权可以在线程间传递。rust 几乎所有类型都是Send
, 还有一些不能Send
,比如Rc
,它只能用于单线程,通过
Sync trait
表明实现了Sync
的类型可以安全的在多个线程中拥有其值的引用。Rc
都不是、RefCell Sync
类型的。
根据这两个概念,可以手动创建用于并发功能的并发类型,在使用时需要多加小心,以维护其安全保证。
标签:
推荐
- rust 自动化测试、迭代器与闭包、智能指针、无畏并发
- “拼车”上太空!长征火箭发射机会首次公开竞拍
- 有效沟通的重要性和必要性 有效沟通的重要意义
- 直击WAIC丨图灵奖得主Joseph Sifakis:AGI需要新范式,实现自主性仍然遥远
- 如何停用您的Threads帐户
- 顺义区继续发布高温橙色预警,今日最高气温38℃左右,请注意防范
- 上半年新注册登记新能源汽车312.8万辆
- 全球平均气温4天内3创新高 世界首富被晒伤!高温会带来哪些经济逆风?
- 12代i3和10代i5(10代i3和10代i5参数)
- 广州2023中考放榜手机在线观看入口
- 李玟老公发讣告!通篇称赞喊挚爱,原定7月离婚,继女被骂到注销
- 新四军与八路军的区别(关于新四军与八路军的区别介绍
- 武夷岩茶和信阳毛尖哪个好
- 手机丢了如何定位追踪快速找回来oppo(手机丢了怎么定位手机位置oppo)
- 最贵8元/小时,共享充电宝成“价格刺客”?
- 盘古大模型3.0来了!华为重大发布:将重塑千行百业
- 博时颐泰混合基金:可能触发基金合同终止情形的提示
- 盛夏时节 消暑纳凉有方
- 女性30岁以后生孩子(《从大学毕业生士兵中选拔军官暂行办法》专科直招士官考学是否有年龄限制)
- 金隅集团07月07日被沪股通减持293.48万股
- 光驱自动反复弹出怎么回事win7 光驱自动反复弹出怎么回事
- 土耳其放宽与里拉存款相关的准备金要求规则。
- 期登村(对于期登村简单介绍)
- 汉得信息拟定增募资3亿元 用于AIGC相关项目开发
- 美股开盘:三大指数集体低开 阿里巴巴涨逾3%
- 清源股份(603628):向阳而生 逐光不停
- 行业首例!太平洋保险与OceanBase完成全险种核心迁至国产数据库
- 中欧班列长安号上半年开行量同比增长近五成
- 2023厦门中考分数公布时间
- 分析师称 AI 热潮助推股价,微软市值将紧随苹果突破 3 万亿美元
- 今日券商观点汇总(7月7日)
- 在哪里修改wifi密码 没有路由器wifi密码怎么修改密码
- 史涵:修身齐家,不悔人生
- 天问一号研究成果揭示火星气候转变 为地球气候演化方向提供借鉴
- 2023雨果奖入围名单公布 4位中国作家入围最佳短篇小说
- 大疆rs3pro(宝骏rs3有什么颜色)
- 《剑灵2》即将推出,赛博提供预约平台,有关上线时间和配置需求
- 新华社专访中国女篮主教练郑薇:依靠精神支撑重回亚洲之巅
- 京津冀河北梆子青年群英会闭幕庆典举行
- 光庭信息:股东接连减持,热门概念股为何被资本“甩卖”?
- 基于地理空间感知型表征学习的轨迹相似度计算
- 25人获聘为西城区诉前人民调解委员会人民调解员
- 【央广时评】“宁可十防九空,不可失防万一”
- 重庆高新区启动暑期教师全员研训 努力培养更多新时代的“大先生”
- 中央气象台:苏皖辽吉内蒙古等地有较强降雨和强对流天气 华北黄淮等地有持续性高温
- 女子花60多万买府谷法拍房,却迟迟拿无法收房
- 华为异腾成首个万卡AI集群,机构看好相关合作商
- 吕婷华(吕婷华)
- 西安卖地面积位列全国第一,下半年将如何?
- 科技融入增“智” 物流业降本增效
- 舜禹股份: 落实投资者关系管理相关规定的安排、股利分配决策程序、股东投票机制建立情况
- 平山县外国语中学(关于平山县外国语中学的基本详情介绍)
- 豪恩汽电(301488.SZ):已与日产、大众、PSA全球、吉利、福特、铃木、现代起亚、比亚迪、小鹏汽车、理想汽车等国内外知名品牌车企深度合作
- 潍坊市奎文区中成村镇银行被罚30万元:因贷款管理不到位等
- 今日恒生指数跌3.02%,南向资金净买入32.28亿港元
- 一图了然|TA那么爱笑,为什么还会得抑郁症?
- 缸体材料铝合金和铸铁有什么区别(缸体材料铝合金和铸铁哪个好)
- 天外飞仙,武磊第1次射门就轰进世界波,国足神仙球对手毫无反应
- 世界最资讯丨群贤荟萃,共谋发展!剑河举办首届产业发展论坛
- 延吉市河南社区卫生服务中心加强安全生产工作 全球简讯
- 女子火灾中扔下婴儿被路人合力接住:电动车在楼道里充电致起火_世界热门
- 鲅鱼的鱼线怎么去除
- 南京康语自闭症言语康复中心
- 深圳市中城联投基金管理有限公司(关于深圳市中城联投基金管理有限公司介绍)
- 官宣加盟!恭喜内马尔!抢齐达内饭碗,皇马吃大亏,再拿一冠封神|当前视讯
- 班巴正式签约76人!担任恩比德替补冲冠 湖人无缘底薪签回他
- 极端天气影响农业生产 各地多措并举做好田间管理
- “家长会连开三天”:热议背后的真问题|当前报道
- 焦点简讯:携手并进 共赴新程 ——阳光人寿河北分公司隆重举办乔迁典礼
- 环球新动态:武汉市汉阳区启动2023年暑期关爱行动
- 马斯克:中国会有很强的人工智能能力 全面自动驾驶将在今年年末到来|今亮点
- 西秦刺绣“绣”出和美乡村
- 汇率波动下跨境资金流动依然保持基本平衡 人民币资产长期投资价值凸显
- 2022 年中级经济师《金融专业知识与实务》考前模拟卷(84)
- 最新资讯:新兴行业 关于新兴行业的介绍
- “楚骚浪漫——2023湖南全省画院书法篆刻作品展”在长沙开展 焦点速讯
- 世界百事通!仓鼠区分雌雄的方法(仓鼠区分雌雄)
- 速读:2023年共下达大学生创新创业教育项目预算1亿元
- 世界关注:三伏未至,40℃+高温已经说麻了!台风还迟迟不现身吗?
- 美团无人机V4将在2023世界人工智能大会上公布亮相
- “真金白银”护航 中国外贸企业闯关求变-环球信息
- 亚热带生态所等举办科普活动|当前讯息
- 这个传统粤式小吃,10个广东人9个都吃过!
- 天天速讯:「轻造科技」推出 MES 平台,服务中小型制造企业
- 天天播报:合力泰近一年诉讼仲裁涉案金额5.9亿 占净资产10.8%
- 环球即时:普洱有序推进公职律师队伍建设 推动实现公职律师应设尽设
- 今日聚焦!安顺经开区金刺梨种植管理现阶段工作推进会召开
- 宝马匈牙利德布勒森工厂_关于宝马匈牙利德布勒森工厂介绍|环球热消息
- 因为遇见你金依蓓相认是第几集_因为遇见你演员表依蓓
- 摔角动态小魔女与莎夏今日在阿布扎比创下新历史
- 新动态:重载稳定好,可靠寿命长,临工叉装新势力——LFT30H反转双摇臂叉装机
- 静组词语_汉字静怎么组词
- 中信集团“打假”:不法分子冒用子公司名义设立公司|天天讯息
- 最新资讯:7月4日基金净值:太平睿庆混合A最新净值1.0191,涨0.14%
- 天天关注:英国6月制造业PMI降至半年低点
- 书法四字佳句_书法常用四字好词大全
- 天天关注:“专业电竞 悦享操控”iQOO 11S登场 首销售价3799元起
- 大文豪苏轼,是唐代诗人柳宗元的超级“粉丝”? 全球速读
- 长城科技(603897.SH)2022年度每股派0.4元 股权登记日为7月11日
- 华表奖唯一指定用车,岚图为中国电影人护航 今日关注
X 关闭
行业规章
X 关闭