文盘Rust -- 生命周期问题引发的 static hashmap 锁 | 京东云技术团队

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。

代码长这样

1use std::collections::HashMap; 2use std::sync::RwLock; 3 4lazy_static::lazy_static! { 5 static ref GLOBAL_MAP: RwLock<HashMap<String,String>> = RwLock::new({ 6 let map = HashMap::new(); 7 map 8 }); 9} 10 11 12 13

基本的数据存取这样实现

1use std::collections::HashMap; 2use std::sync::RwLock; 3 4lazy_static::lazy_static! { 5 static ref GLOBAL_MAP: RwLock<HashMap<String,String>> = RwLock::new({ 6 let map = HashMap::new(); 7 map 8 }); 9} 10 11fn main() { 12 for i in 0..3 { 13 insert_global_map(i.to_string(), i.to_string()) 14 } 15 print_global_map(); 16 println!("finished!"); 17} 18 19fn insert_global_map(k: String, v: String) { 20 let mut gpw = GLOBAL_MAP.write().unwrap(); 21 gpw.insert(k, v); 22} 23 24fn print_global_map() { 25 let gpr = GLOBAL_MAP.read().unwrap(); 26 for pair in gpr.iter() { 27 println!("{:?}", pair); 28 } 29} 30 31 32 33

insert_global_map函数用来向GLOBAL_MAP插入数据,print_global_map()用来读取数据,上面程序的运行结果如下

1("0", "0") 2("1", "1") 3("2", "2") 4 5 6 7

下面我们来实现一个比较复杂一点儿的需求,从 GLOBAL_MAP 里取一个数,如果存在后面进行删除操作,直觉告诉我们代码似乎应该这样写

1use std::collections::HashMap; 2use std::sync::RwLock; 3 4lazy_static::lazy_static! { 5 static ref GLOBAL_MAP: RwLock<HashMap<String,String>> = RwLock::new({ 6 let map = HashMap::new(); 7 map 8 }); 9} 10 11fn main() { 12 for i in 0..3 { 13 insert_global_map(i.to_string(), i.to_string()) 14 } 15 print_global_map(); 16 get_and_remove(1.to_string()); 17 println!("finished!"); 18} 19 20fn insert_global_map(k: String, v: String) { 21 let mut gpw = GLOBAL_MAP.write().unwrap(); 22 gpw.insert(k, v); 23} 24 25fn print_global_map() { 26 let gpr = GLOBAL_MAP.read().unwrap(); 27 for pair in gpr.iter() { 28 println!("{:?}", pair); 29 } 30} 31 32fn get_and_remove(k: String) { 33 println!("execute get_and_remove"); 34 let gpr = GLOBAL_MAP.read().unwrap(); 35 let v = gpr.get(&*k.clone()); 36 let mut gpw = GLOBAL_MAP.write().unwrap(); 37 gpw.remove(&*k.clone()); 38} 39 40 41 42 43

上面这段代码输出长这样

1("0", "0") 2("1", "1") 3("2", "2") 4execute get_and_remove 5 6 7 8 9

代码没有结束,而是hang在了get_and_remove函数。 为啥会出现这样的情况呢?这也许与生命周期有关。gpr和gpw 这两个返回值分别为 RwLockReadGuard 和 RwLockWriteGuard,查看这两个

struct 发现确实可能引起死锁

1must_not_suspend = "holding a RwLockWriteGuard across suspend \ 2 points can cause deadlocks, delays, \ 3 and cause Future's to not implement `Send`" 4 5 6 7

问题找到了就可以着手解决办法了,既然是与rust的生命周期有关,那是不是可以把读和写分别放在两个不同的生命周期里呢,于是对代码进行改写

1use std::collections::HashMap; 2use std::sync::RwLock; 3 4lazy_static::lazy_static! { 5 static ref GLOBAL_MAP: RwLock<HashMap<String,String>> = RwLock::new({ 6 let map = HashMap::new(); 7 map 8 }); 9} 10 11fn main() { 12 for i in 0..3 { 13 insert_global_map(i.to_string(), i.to_string()) 14 } 15 print_global_map(); 16 get_and_remove(1); 17 println!("finished!"); 18} 19 20fn insert_global_map(k: String, v: String) { 21 let mut gpw = GLOBAL_MAP.write().unwrap(); 22 gpw.insert(k, v); 23} 24 25fn print_global_map() { 26 let gpr = GLOBAL_MAP.read().unwrap(); 27 for pair in gpr.iter() { 28 println!("{:?}", pair); 29 } 30} 31 32fn get_and_remove_deadlock(k: String) { 33 println!("execute get_and_remove"); 34 let gpr = GLOBAL_MAP.read().unwrap(); 35 let _v = gpr.get(&*k.clone()); 36 let mut gpw = GLOBAL_MAP.write().unwrap(); 37 gpw.remove(&*k.clone()); 38} 39 40fn get_and_remove(k: i32) { 41 let v = { 42 let gpr = GLOBAL_MAP.read().unwrap(); 43 let v = gpr.get(&*k.to_string().clone()); 44 match v { 45 None => Err(anyhow!("")), 46 Some(pair) => Ok(pair.to_string().clone()), 47 } 48 }; 49 let vstr = v.unwrap(); 50 println!("get value is {:?}", vstr.clone()); 51 let mut gpw = GLOBAL_MAP.write().unwrap(); 52 gpw.remove(&*vstr); 53} 54 55 56 57 58

正确输出

1("1", "1") 2("0", "0") 3("2", "2") 4get value is "1" 5("0", "0") 6("2", "2") 7finished! 8 9 10 11

Rust的生命周期是个很有意思的概念,从认识到理解确实有个过程。

源码地址

作者:京东科技 贾世闻

来源:京东云开发者社区 转载请注明来源

点赞
收藏

评论区

加载中...

相关推荐

Oracle 分组与拼接字符串同时使用

SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(

MySQL部分从库上面因为大量的临时表tmp_table造成慢查询

背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_

PPDB:今晚老齐直播

【今晚老齐直播】今晚(本周三晚)20:0021:00小白开始“用”飞桨(https://www.oschina.net/action/visit/ad?id1185)由PPDE(飞桨(https://www.oschina.net/action/visit/ad?id1185)开发者专家计划)成员老齐,为深度学习小白指点迷津。

FLV文件格式

1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  

mysql设置时区

mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0

SpringBoot整合Redis乱码原因及解决方案

问题描述:springboot使用springdataredis存储数据时乱码rediskey/value出现\\xAC\\xED\\x00\\x05t\\x00\\x05问题分析:查看RedisTemplate类!(https://oscimg.oschina.net/oscnet/0a85565fa