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的生命周期是个很有意思的概念,从认识到理解确实有个过程。

源码地址

作者:京东科技 贾世闻

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

文盘Rust -- 生命周期问题引发的 static hashmap 锁的更多相关文章

  1. rust 生命周期2

    之前定义的结构体,都是不含引用的. 如果想定义含引用的结构体,请定义生命周期注解 #[warn(unused_variables)] struct ImportantExcerpt<'a> ...

  2. 文盘Rust -- struct 中的生命周期

    最近在用rust 写一个redis的数据校验工具.redis-rs中具备 redis::ConnectionLike trait,借助它可以较好的来抽象校验过程.在开发中,不免要定义struct 中的 ...

  3. Rust生命周期bound用于泛型的引用

    在实际编程中,可能会出现泛型引用这种情况,我们会编写如下的代码: struct Inner<'a, T> { data: &'a T, } 会产生编译错误: error[E0309 ...

  4. Tomcat源码分析 | 一文详解生命周期机制Lifecycle

    目录 什么是Lifecycle? Lifecycle方法 LifecycleBase 增加.删除和获取监听器 init() start() stop() destroy() 模板方法 总结 前言 To ...

  5. 文盘Rust -- 本地库引发的依赖冲突

    作者:京东科技 贾世闻 问题描述 clickhouse 的原生 rust 客户端目前比较好的有两个clickhouse-rs 和 clickhouse.rs .clickhouse-rs 是 tcp ...

  6. 文盘Rust -- 把程序作为守护进程启动

    当我们写完一个服务端程序,需要上线部署的时候,或多或少都会和操作系统的守护进程打交道,毕竟谁也不希望shell关闭既停服.今天我们就来聊聊这个事儿. 最早大家部署应用的通常操作是 "nohu ...

  7. 文盘Rust -- rust 连接云上数仓 starwift

    作者:京东云 贾世闻 最近想看看 rust 如何集成 clickhouse,又犯了好吃懒做的心理(不想自己建环境),刚好京东云发布了兼容ck 的云原生数仓 Starwfit,于是搞了个实例折腾一番. ...

  8. 文盘Rust -- 给程序加个日志

    作者:贾世闻 日志是应用程序的重要组成部分.无论是服务端程序还是客户端程序都需要日志做为错误输出或者业务记录.在这篇文章中,我们结合[log4rs](https://github.com/estk/l ...

  9. 文盘Rust -- 用Tokio实现简易任务池

    作者:京东科技 贾世闻 Tokio 无疑是 Rust 世界中最优秀的异步Runtime实现.非阻塞的特性带来了优异的性能,但是在实际的开发中我们往往需要在某些情况下阻塞任务来实现某些功能. 我们看看下 ...

  10. 【译】深入理解Rust中的生命周期

    原文标题:Understanding Rust Lifetimes 原文链接:https://medium.com/nearprotocol/understanding-rust-lifetimes- ...

随机推荐

  1. C盘清理,移动node 依赖和缓存文件

    由于先前安装的node 没有做任何配置,都是傻瓜式下一步,导致了我很多依赖都放置C盘,内存占用过多:也不太好管理所有觉得将它移动到node安装目录 一.新建文件夹 在原本安装的nodejs目录下新建 ...

  2. ASP.NET Core 6框架揭秘实例演示[36]:HTTPS重定向

    HTTPS是确保传输安全最主要的手段,并且已经成为了互联网默认的传输协议.不知道读者朋友们是否注意到当我们利用浏览器(比如Chrome)浏览某个公共站点的时候,如果我们输入的是一个HTTP地址,在大部 ...

  3. 在 Linux 和 Windows 下源码安装 Perl

    Perl 是一种功能丰富的计算机程序语言,运行在超过 100 种计算机平台上,适用广泛,从大型机到便携设备,从快速原型创建到大规模可扩展开发.在生物信息分析领域,Perl 主要是做数据预处理.文本处理 ...

  4. Android Studio 引入kotlin 协程

    首先保证创建了kotlin项目,然后,两个步骤: 1. 加入dependency,在 build.gradle(:app)中,加入 implementation 'org.jetbrains.kotl ...

  5. 如何洞察 C# 程序的 GDI 句柄泄露

    一:背景 1. 讲故事 前段时间有位朋友找到我,说他的程序界面操作起来很慢并且卡顿等一些不正常现象,从任务管理器看了下 GDI句柄 已经到 1w 了,一时也找不出什么代码中哪里有问题,让我帮忙看下,其 ...

  6. .Net 472&6.0 Razor编译时的小差异

    前言 几个月前在进行着.Net 472到6.0的升级,复用原有代码,在对Razor进行迁移中,发现原运行正常的代码,却存在报错,深入研究发现是Core下对Razor编译有一些变动. 问题复现 472 ...

  7. JVM之指针压缩

    做java开发的同学一般都比较熟悉JVM,那么关于指针压缩这块内容是不是也了解呢,不熟悉的小伙伴往下看吧. 首先说明,本文涉及的JDK版本是1.8,JVM虚拟机是64位的HotSpot实现为准. ja ...

  8. Unity iOS Guideline 1.3 - Safety - Kids Category 被拒

    解决办法: 不使用unity 的分析SDK //关闭unity信息收集服务 UnityEngine.Analytics.Analytics.enabled = false; UnityEngine.A ...

  9. Hugging News #0717: 开源大模型榜单更新、音频 Transformers 课程完成发布!

    每一周,我们的同事都会向社区的成员们发布一些关于 Hugging Face 相关的更新,包括我们的产品和平台更新.社区活动.学习资源和内容更新.开源库和模型更新等,我们将其称之为「Hugging Ne ...

  10. 【NestJS系列】核心概念:Controller控制器

    前言 控制器主要是用来处理客户端传入的请求并向客户端返回响应. 它一般是用来做路由导航的,内部路由机制控制哪个控制器接收哪些请求. 路由 为了创建基本控制器,我们需要使用@Controller装饰器, ...