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

这篇具有很好参考价值的文章主要介绍了文盘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。

代码长这样

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

源码地址

作者:京东科技 贾世闻

来源:京东云开发者社区 转载请注明来源文章来源地址https://www.toymoban.com/news/detail-696364.html

到了这里,关于文盘Rust -- 生命周期问题引发的 static hashmap 锁 | 京东云技术团队的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Rust 基础篇】Rust 生命周期

    Rust 是一门强类型、静态分析的系统编程语言,具有内存安全和并发安全的特性。为了实现这些安全性,Rust 引入了生命周期(lifetimes)的概念。本篇博客将详细介绍 Rust 生命周期的定义、使用和相关概念,以及如何正确处理引用的生命周期。 生命周期描述了引用的有效期,

    2024年02月15日
    浏览(35)
  • Rust学习-生命周期

    Rust 最与众不同的功能 之前学习中,有多种可能类型时必须注明类型;同理,引用的生命周期以一些不同方式相关联时,需要使用泛型生命周期参数来注明关系,这样就能确保运行时实际使用的引用有效 Rust 编译器有一个 借用检查器(borrow checker) 它比较作用域来确保所有的

    2024年02月16日
    浏览(42)
  • Rust-借用和生命周期

    一个变量的生命周期就是它从创建到销毁的整个过程。其实我们在前面已经注意到了这样的现象: 然而,如果一个变量永远只能有唯一一个入口可以访问的话,那就太难使用了。因此,所有权还可以借用。 变量对其管理的内存拥有所有权。这个所有权不仅可以被转移(move),还

    2024年01月19日
    浏览(37)
  • 30天拿下Rust之生命周期

    概述         在Rust中,生命周期是一个非常重要的概念,是保证内存安全和防止悬垂引用的核心机制之一。通过精确地跟踪引用的生命周期,Rust能够在编译阶段就防止许多其他语言在运行时才会遇到的内存问题。在Rust中,生命周期代表了引用的有效时间段。当我们创建

    2024年03月20日
    浏览(45)
  • Rust语法:所有权&引用&生命周期

    垃圾回收管理内存 Python,Java这类语言在管理内存时引用了一种叫做垃圾回收的技术,这种技术会为每个变量设置一个引用计数器(reference counter),来统计每个对象的引用次数。 一旦某个对象的引用数为0,垃圾回收器就会择取一个时机将其所占用的空间回收。 以Python为例子

    2024年02月12日
    浏览(54)
  • Rust之泛型、trait与生命周期

    泛型是具体类型或其他属性的抽象替代。在编写代码时,可以直接描述泛型的行为,或者它与其他泛型产生的联系,而无须知晓它在编译和运行代码时采用的具体类型。 们可以在声明函数签名或结构体等元素时使用泛型,并在随后搭配不同的具体类型来使用这些元素。 当使

    2024年02月13日
    浏览(34)
  • rust踩雷笔记3——生命周期的理解

    生命周期是rust中最难的概念——鲁迅 这一块内容即便是看rust圣经,第一遍也有点懵。今天早上二刷突然有了更直观的认识,记录一下。 概念和基本使用 生命周期就是应用的有效作用域,它的主要作用是避免悬垂引用。 悬垂引用的典型例子: 简而言之就是y引用的变量在y被

    2024年02月12日
    浏览(28)
  • Rust in Action笔记 第四章生命周期、所有权、借用

    第四章用了一个行星通信的例子来阐述整个主题,主要角色有地面站(Ground station)、人造卫星(CubeSat),两者有不同的状态并且能互相发消息通信; Rust有类型安全(type safety)机制来检查函数的类型和返回值,如果一个自定义类型(用struct声明的)包含了非内置类型(如

    2024年02月09日
    浏览(42)
  • # 文盘Rust -- tokio绑定cpu实践

    作者: jiashiwen 原文来源: https://tidb.net/blog/18804515 notice\\\"Rust is a trademark of the Mozilla Foundation in the US and other countries.\\\" tokio 是 rust 生态中流行的异步运行时框架。在实际生产中我们如果希望 tokio 应用程序与特定的 cpu core 绑定该怎么处理呢?这次我们来聊聊这个话题。 首先我们

    2024年02月08日
    浏览(37)
  • 文盘Rust -- tokio绑定cpu实践

    tokio 是 rust 生态中流行的异步运行时框架。在实际生产中我们如果希望 tokio 应用程序与特定的 cpu core 绑定该怎么处理呢?这次我们来聊聊这个话题。 首先我们先写一段简单的多任务程序。 程序非常简单,首先构造一个tokio runtime 环境,然后派生多个 tokio 并发,每个并发执行

    2024年02月07日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包