2021年上半年,擼了個rust cli開發的框架,基本上把交互模式,子命令提示這些cli該有的常用功能做進去了。項目地址:[https://github.com/jiashiwen/interactcli-rs。](https://github.com/jiashiwen/interactcli- ...
2021年上半年,擼了個rust cli開發的框架,基本上把交互模式,子命令提示這些cli該有的常用功能做進去了。項目地址:https://github.com/jiashiwen/interactcli-rs。
春節以前看到axum已經0.4.x了,於是想看看能不能用rust做個服務端的框架。
春節後開始動手,在做的過程中會碰到各種有趣的問題。於是記下來想和社區的小伙伴一起分享。社區里的小伙伴大部分是DBA和運維同學,如果想進一步瞭解更底層的東西,代碼入手是個好路數。
我個人認為想看懂代碼先要寫好代碼,起碼瞭解開發的基本路數和工程的一般組織模式。但好多同學的主要工作並不是專職開發,所以也就沒有機會下探研發技術。代碼這個事兒光看書是不管用的。瞭解一門語言最好的方式是使用它。
那麼,問題來了非研發人員如何熟悉語言呢?詠春拳里有句拳諺:”無師無對手,樁與鏡中求“。解釋兩句,就是在沒有師兄弟練習的情況下,對著鏡子和木人樁練習。在這裡我覺得所謂樁有兩層含義,一個是木人樁,就是練習的工具,一個是”站樁“,傳統武術訓練基本功的方法。其實在實際的工作中DBA和運維同學會有很多場景需要編程,比如做一些運維方面的統計工作;分析問題時需要拿到某些數據。如果追求簡單用Python的話可能對於其他語言就沒有涉獵了。如果結合你運維資料庫的原生開發語言,假以時日慢慢就能看懂相關的底層邏輯了。我個人有個觀點,產品研發的原生語言是瞭解產品底層最好的入口。
後面如果在Rust的開發過程中有其他問題,我本人會把問題結合實際也寫到這個系列里,也希望社區里對Rust感興趣的小伙伴一起來”盤Rust“。 言歸正傳,說說這次在玩兒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(1);
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的生命周期是個很有意思的概念,從認識到理解確實有個過程。
作者:京東科技 賈世聞
來源:京東雲開發者社區 轉載請註明來源