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的生命週期是個很有意思的概念,從認識到理解確實有個過程。
作者:京東科技 賈世聞
來源:京東雲開發者社群 轉載請註明來源