Rust 无疑是2018年以来备受关注的语言。随着2021年一月 Rust 基金会的成立,也迎来了亚马逊、Google、微软、华为等重量级玩儿家。2019年初识Rust,看了些基础的书籍。2021年春节前后,撸了个 rust cli 开发的框架,基本上把交互模式,子命令提示这些cli该有的常用功能做进去了。项目地址:https://github.com/jiashiwen/interactcli-rs。春节以前看到 axum 已经 4.x 了,于是想看看能不能用 rust 做个服务端的框架。开发过程中遇到了不少问题,于是记录下来,和各位同学分享一下。
在 Rust 开发过程中,我们经常需要全局变量作为公共数据的存放位置。通常做法是利用 lazy_static/onecell 和 mux/rwlock 生成一个静态的 collection。
代码长这样
use std::collections::HashMap;
use std::sync::RwLock;
lazy_static::lazy_static! {
static ref GLOBAL_MAP: RwLock<HashMap<String,String>> = RwLock::new({
let map = HashMap::new();
map
});
}
基本的数据存取这样实现
use std::collections::HashMap;
use std::sync::RwLock;
lazy_static::lazy_static! {
static ref GLOBAL_MAP: RwLock<HashMap<String,String>> = RwLock::new({
let map = HashMap::new();
map
});
}
fn main() {
for i in 0..3 {
insert_global_map(i.to_string(), i.to_string())
}
print_global_map();
println!("finished!");
}
fn insert_global_map(k: String, v: String) {
let mut gpw = GLOBAL_MAP.write().unwrap();
gpw.insert(k, v);
}
fn print_global_map() {
let gpr = GLOBAL_MAP.read().unwrap();
for pair in gpr.iter() {
println!("{:?}", pair);
}
}
insert_global_map 函数用来向 GLOBAL_MAP 插入数据,print_global_map() 用来读取数据,上面程序的运行结果如下
("0", "0")
("1", "1")
("2", "2")
下面我们来实现一个比较复杂一点儿的需求,从 GLOBAL_MAP 里取一个数,如果存在后面进行删除操作,直觉告诉我们代码似乎应该这样写
use std::collections::HashMap;
use std::sync::RwLock;
lazy_static::lazy_static! {
static ref GLOBAL_MAP: RwLock<HashMap<String,String>> = RwLock::new({
let map = HashMap::new();
map
});
}
fn main() {
for i in 0..3 {
insert_global_map(i.to_string(), i.to_string())
}
print_global_map();
get_and_remove(1.to_string());
println!("finished!");
}
fn insert_global_map(k: String, v: String) {
let mut gpw = GLOBAL_MAP.write().unwrap();
gpw.insert(k, v);
}
fn print_global_map() {
let gpr = GLOBAL_MAP.read().unwrap();
for pair in gpr.iter() {
println!("{:?}", pair);
}
}
fn get_and_remove(k: String) {
println!("execute get_and_remove");
let gpr = GLOBAL_MAP.read().unwrap();
let v = gpr.get(&*k.clone());
let mut gpw = GLOBAL_MAP.write().unwrap();
gpw.remove(&*k.clone());
}
上面这段代码输出长这样
("0", "0")
("1", "1")
("2", "2")
execute get_and_remove
代码没有结束,而是 hang 在了 get_and_remove 函数。为啥会出现这样的情况呢?这也许与生命周期有关。gpr和gpw 这两个返回值分别为 RwLockReadGuard 和 RwLockWriteGuard,查看这两个 struct 发现确实可能引起死锁
must_not_suspend = "holding a RwLockWriteGuard across suspend \
points can cause deadlocks, delays, \
and cause Future's to not implement `Send`"
问题找到了就可以着手解决办法了,既然是与 rust 的生命周期有关,那是不是可以把读和写分别放在两个不同的生命周期里呢,于是对代码进行改写
use std::collections::HashMap;
use std::sync::RwLock;
lazy_static::lazy_static! {
static ref GLOBAL_MAP: RwLock<HashMap<String,String>> = RwLock::new({
let map = HashMap::new();
map
});
}
fn main() {
for i in 0..3 {
insert_global_map(i.to_string(), i.to_string())
}
print_global_map();
get_and_remove(i);
println!("finished!");
}
fn insert_global_map(k: String, v: String) {
let mut gpw = GLOBAL_MAP.write().unwrap();
gpw.insert(k, v);
}
fn print_global_map() {
let gpr = GLOBAL_MAP.read().unwrap();
for pair in gpr.iter() {
println!("{:?}", pair);
}
}
fn get_and_remove_deadlock(k: String) {
println!("execute get_and_remove");
let gpr = GLOBAL_MAP.read().unwrap();
let v = gpr.get(&*k.clone());
let mut gpw = GLOBAL_MAP.write().unwrap();
gpw.remove(&*k.clone());
}
fn get_and_remove(k: i32) {
let v = {
let gpr = GLOBAL_MAP.read().unwrap();
let v = gpr.get(&*k.to_string().clone());
match v {
None => Err(anyhow!("")),
Some(pair) => Ok(pair.to_string().clone()),
}
};
let vstr = v.unwrap();
println!("get value is {:?}", vstr.clone());
let mut gpw = GLOBAL_MAP.write().unwrap();
gpw.remove(&*vstr);
}
正确输出
("1", "1")
("0", "0")
("2", "2")
get value is "1"
("0", "0")
("2", "2")
finished!
Rust 的生命周期是个很有意思的概念,从认识到理解确实有个过程。
-End-