首页 > 其他分享 >文盘Rust -- 生命周期问题引发的 static hashmap 锁

文盘Rust -- 生命周期问题引发的 static hashmap 锁

时间:2023-09-05 14:14:29浏览次数:36  
标签:map global hashmap MAP -- GLOBAL 文盘 let static

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

源码地址

作者:京东科技 贾世闻

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

标签:map,global,hashmap,MAP,--,GLOBAL,文盘,let,static
From: https://www.cnblogs.com/Jcloud/p/17679395.html

相关文章

  • django-celery定时任务(beat)
    前言Celery可以异步执行,也可以通过定时任务触发Django中使用Celery要在Django项目中使用Celery,您必须首先定义Celery库的一个实例(称为“应用程序”)如果你有一个现代的Django项目布局,比如: 创建一个celery模块,来定义celery实例importo......
  • springboot加载bean失败:No matching autowired candidates found
    场景:之前在培训轮岗,一直没有干活,最近开始干活遇到xxljob,打算自己学习了解一下。在按照文档配置执行器项目时,发现怎么启动,xxlJobExecutor都没有被加载进来。解决:后来经过查阅,原来是springBoot启动默认扫描的是启动类所在的包以及其子包,而我的文件为:因此bean注入失败。把......
  • day21 - 二叉树part07
    530. 二叉搜索树的最小绝对差详解/***Definitionforabinarytreenode.*structTreeNode{*intval;*TreeNode*left;*TreeNode*right;*TreeNode():val(0),left(nullptr),right(nullptr){}*TreeNode(intx):val(x),left......
  • zookeeper集群安装(CentOS7 + zookeeper 3.7.0)
    Linux系统-部署-运维系列导航 zookeeper介绍ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。 zo......
  • Jenkins集成Blue Ocean
    Jenkins集成BlueOcean1.安装参考:https://www.jenkins.io/doc/book/blueocean/getting-started/要将BlueOcean插件套件安装到Jenkins实例,按步骤执行以下操作:1.使用管理员用户登录到Jenkins。2.在可用插件中,搜索blueocean,点击安装即可。2.配置配置的话,有两......
  • 前端项目环境搭建笔记
    @目录简介一、vue3组件通信方式1.1props1.2自定义事件1.2.1原生DOM事件1.2.2自定义事件1.3全局事件总线1.4v-model1.5useAttrs1.6ref与$parent1.7provide与inject1.8pinia1.9slot二、搭建后台管理系统模板2.1项目初始化2.1.1环境准备2.1.2初始化项目2.2项目配置一、eslint......
  • Java 三种IO实现一个简单聊天室
    目录Java三种IO实现一个简单聊天室同步阻塞IO同步非阻塞IO异步IOJava三种IO实现一个简单聊天室在Java平台,IO有三种模型.以TCP套接字为例三种不同模型实现简单聊天室服务端和客户端.启动服务端后,启动多个客户端,相互之间可以发消息.同步阻塞IO因为同步阻塞IO不能......
  • Linux防火墙相关操作
    小工具:在线检测域名或者ip的端口是否开放端口扫描  http://coolaf.com/tool/portLinux防火墙相关操作1、安装firewalld在CentOS7中默认是安装的。CentOS7还支持以图形界面的方式配置防火墙,即firewall-config工具,默认也是安装的。安装firewalld和firewall-config工具[ro......
  • python 组件安装
    1.cx_oracle安装 步骤1:下载组件(cx_Oracle-8.1.0-cp38-cp38-win_amd64.whl)后,放置...Python\Python38\Lib\site-packages目录下,如D:\Programs\Python\Python38\Lib\site-packages 步骤2:CMD,CD至site-packages目录后,执行命令:pipinstall组件名称,如pipinstallcx_Oracle-8.1......
  • 多个word转化成PDF文件后再合并成一个PDF文件
    """**将多个word文档转化成PDF文件,最后合并成一个PDF文件**"""`importosfromwin32comimportclientfromPyPDF2importPdfMerger#使用PdfMergerdefwordToPdf(folder):#将多个word文档转化成PDF文件os.chdir(folder)file_type='docx&#......