首页 > 其他分享 >对于一个在知乎评论区内的OnceCell的深入研究

对于一个在知乎评论区内的OnceCell的深入研究

时间:2023-06-14 22:55:06浏览次数:48  
标签:cell 知乎 get text 深入研究 world OnceCell Op

#[derive(Debug)]
struct Op {
    text: String,
}
impl Op {
    fn new() -> Self {
        Op {
            text: "hello".to_string(),
        }
    }
}
#[test]
fn test_std_onceCell_static_op() {
    static mut cell: OnceCell<Op> = OnceCell::new();
    unsafe {
        cell.get_or_init(|| Op::new());
        let option = cell.get();
        println!("{:?}", option);
        let handle = thread::spawn(move || {
            let option1 = cell.get_mut().unwrap();
            println!("{:?}", cell.get().unwrap());
        });
    }
}
 

我以为OnceCell里面装的类型实现了Sync就可以多线程使用了,但是看你写的错误示例,我又试了一下:

use std::cell::{OnceCell};

struct Op{
    text:String,
}

fn is_sync<T:Sync>(){}

#[test]
fn check_trait(){
    is_sync::<String>();
    is_sync::<Vec<i32>>();
    is_sync::<OnceCell<Op>>(); //error[E0277]: `std::cell::OnceCell<check_trait::Op>` cannot be shared between threads safely
}
 

这次却报错了,这是为什么呢?OnceCell没有实现Sync但是第一段代码为什么可以正常运行[发呆]

上面这是整个问题的描述。

对于最上面的那个,首先使用的线程数也不多,才一个,并且没有join操作,会在main函数执行完之后线程都没有结束, 所以不会报错。

都说OnceCell是单线程版本,在多线程版上去做写操作会有问题的。

Chatgpt的解释

在你提供的代码中,你确实创建了两个并发线程,然后在这两个线程中调用了 cell.get_mut().unwrap() 方法,试图获取 OnceCell 的可变引用。然而,这是一个很危险的操作,因为 OnceCell 并没有为并发环境提供正确的同步机制。实际上,根据 Rust 的文档,OnceCell::get_mut 方法在并发环境中的行为是未定义的。这就意味着你的代码可能会在某些情况下正常工作,但在其他情况下出错,或者产生不可预测的结果。

你的代码能够运行成功可能是因为线程调度的结果导致了数据竞态条件没有发生。这是因为两个线程可能没有在同一时间尝试获取 OnceCell 的可变引用。然而,你不能依赖这种行为,因为线程调度的结果是非确定性的,它取决于很多因素,如操作系统的调度策略、系统的负载、CPU的数量等等。

这就是为什么你应该总是使用适当的同步原语来保护并发环境中的共享数据。在 Rust 中,这通常意味着使用诸如 Mutex 或 RwLock 这样的类型来保护数据。如果你试图在没有适当同步的情况下访问共享数据,那么你的代码可能会在某些情况下工作,但在其他情况下出错,或者产生不可预测的结果。

对于你的代码,如果你希望在多个线程中共享和修改 Op 实例,你应该使用 Mutex 或者 RwLock 来代替 OnceCell。这样可以保证在任何时候只有一个线程可以修改 Op,而其他线程可以安全地读取 Op 的值。

下面这是一个多线程调用的版本:

#[test]
fn test_multi_oncecell_example() {
    // use std::cell::OnceCell;
    use std::sync::OnceCell;
    use std::thread;

    static mut cell: OnceCell<Op> = OnceCell::new();
    unsafe {
        cell.get_or_init(|| Op::new());
        let option = cell.get();
        println!("{:?}", option);
        let mut handler_result = vec![];
        //  将thead数,数值太小不会出错。改为300 报错了
        for idx in 0..300 {
            let handler = thread::spawn(move || {
                let option1 = cell.get_mut().unwrap();
                println!("{:?}", cell.get().unwrap());
                option1.text = format!("world. {}", idx);
                println!("{:?}", cell.get().unwrap());
            });

            handler_result.push(handler);
        }

        for handler in handler_result {
            handler.join().unwrap();
        }

        let option = cell.get();
        println!("{:?}", option);
    }
}
 

最终也会引发错误:

将thead数 改为300 报错了

running 1 test
Some(Op { text: "hello" })
Op { text: "hello" }
thread '<unnamed>' panicked at 'byte index 6 is out of bounds of ``��`', library/core/src/fmt/mod.rs:2472:30
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Op { text: "Op { text: "world. 0" }
Op { text: "world. 0" }
Op { text: "world. 3" }
Op { text: "world. 3" }
thread '<unnamed>' panicked at 'byte index 1 is not a char boundary; it is inside '\0' (bytes 0..1) of `��E�J`', library/core/src/fmt/mod.rs:2472:30
Op { text: "\0Op { text: "world. 9" }
Op { text: "world. 6" }
Op { text: "world. 6" }
Op { text: "world. 16" }
Op { text: "world. 16" }
Op { text: "world. 8" }
Op { text: "world. 8" }
Op { text: "world. 8" }
Op { text: "world. 18" }
Op { text: "world. 18" }
Op { text: "world. 19" }
Op { text: "world. 19" }
Op { text: "world. 19" }
Op { text: "world. 19" }
Op { text: "world. 21" }
oncecell_and_oncelock_example-7c768fb3d49cd1dd(2276,0x172173000) malloc: *** error for object 0x600001fad610: pointer being freed was not allocated
oncecell_and_oncelock_example-7c768fb3d49cd1dd(2276,0x170f07000) malloc: *** error for object 0x600001fad610: pointer being freed was not allocated
oncecell_and_oncelock_example-7c768fb3d49cd1dd(2276,0x172173000) malloc: *** set a breakpoint in malloc_error_break to debug
Op { text: "world. 21" }
oncecell_and_oncelock_example-7c768fb3d49cd1dd(2276,0x170f07000) malloc: *** set a breakpoint in malloc_error_break to debug
Op { text: "world. 22" }
Op { text: "world. 22" }
Op { text: "world. 23" }
error: test failed, to rerun pass `-p oncecell-and-oncelock-example --lib`

Caused by:
  process didn't exit successfully: `/Users/davirain/rust/all-in-one-rust/target/debug/deps/oncecell_and_oncelock_example-7c768fb3d49cd1dd test_multi_oncecell_example --nocapture` (signal: 6, SIGABRT: process abort signal)
 

使用OnceLock可以解决这个问题,下面是修改后的代码:

#[test]
fn test_multi_oncelock_example() {
    use std::sync::OnceLock;
    use std::thread;

    static mut cell: OnceLock<Op> = OnceLock::new();
    unsafe {
        cell.get_or_init(|| Op::new());
        let option = cell.get();
        println!("{:?}", option);
        let mut handler_result = vec![];
        //  将thead数,数值太小不会出错。改为300 报错了
        for idx in 0..300 {
            let handler = thread::spawn(move || {
                let option1 = cell.get_mut().unwrap();
                println!("{:?}", cell.get().unwrap());
                option1.text = format!("world. {}", idx);
                println!("{:?}", cell.get().unwrap());
            });

            handler_result.push(handler);
        }

        for handler in handler_result {
            handler.join().unwrap();
        }

        let option = cell.get();
        println!("{:?}", option);
    }
}
 

对于 Rust 中的 OnceCell 类型,即使存储的 T 类型实现了 Sync, OnceCell 本身也并不保证线程安全。这是因为 OnceCell 的设计目标是单次写入和多次读取,而不包括在多个线程间安全共享和修改。

如果你需要在多个线程间共享并可能会改变的数据,应该使用 Mutex 或 RwLock。如果你需要一种类型只被初始化一次并且可以在多个线程之间安全共享,你应该使用 once_cell::sync::OnceCell 或 std::lazy::OnceCell (在 Rust 1.55.0 以后的版本中可用)。这些类型确保了只进行一次初始化,并且实现了 Sync,允许在多个线程之间安全共享。

下面这是一个错误的例子:

#[test]
fn test_op_no_implement_syn() {
    // use std::cell::OnceCell;

    is_sync::<String>();
    is_sync::<Vec<i32>>();

    // is_sync::<OnceCell<i32>>(); //error[E0277]: `std::cell::OnceCell<check_trait::Op>` cannot be shared between threads safely
    // 这是因为OnceCell没有实现Sync
}

标签:cell,知乎,get,text,深入研究,world,OnceCell,Op
From: https://www.cnblogs.com/Davirain/p/17481567.html

相关文章

  • OnceCell和OnceLock的介绍
    OnceCell和OnceLock都是Rust标准库中用于实现懒加载的数据结构,它们能够确保一个变量只被初始化一次。OnceCell是用于单线程环境下的懒加载数据结构。它可以用来存储某个值,并在需要时进行初始化,但是只能在单线程环境下使用。在多线程环境下,使用OnceCell会导致数据竞争问题......
  • 我让 ChatGPT 回答了知乎收藏最高的 Top 20 个问题
    文/高扬 按上一次所讲的方式,我向它请教了知乎上的收藏数/关注数最多的Top20问题。 经测试,比如“有什么终生受用的技能”这类人生泛用型问题基本都能回答。 对于推荐音乐、电影、网购等类型,它回答的不是太好,可能我用的是GPT3.5版本,还并没有能力浏览网页或访问数......
  • 知乎live笔记:背景调查
    一、什么是背景调查调查候选人的身份、提供的材料信息、履历的真实性,以及过往工作表现。有时还包括诉讼情况、社会关系等内容。二、为什么要做背景调查规避风险胜任力风险法律风险职业操守风险成本风险背景调查的价值筛除提供了虚假信息的候选人全面了解候选人的素质......
  • 艾思科技小程序开发:如何利用知乎、百家号等平台推广
    小程序的兴起,使得越来越多的企业开始将自己的业务移植到小程序平台上。在小程序的开发过程中,一个好的小程序不仅仅需要技术领域的专业知识,还需要一定的商业思维和营销能力。如何在小程序开发完成后进行有针对性和效果最优的推广呢?本文将围绕如何利用知乎、百家号等平台推广小程序......
  • 知乎问题:如何说服技术老大用 Redis ?
    这个问题很微妙,可能这位同学内心深处,觉得Redis是所有应用缓存的标配。缓存的世界很广阔,对于应用系统来讲,我们经常将缓存划分为本地缓存和分布式缓存。本地缓存:应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自......
  • 知乎微信接口
    微信精选段子http://v.juhe.cn/weixin/query?key=d046cd1f569ed13d951f0258902ef9b2&ps=10知乎最新日报列表http://news-at.zhihu.com/api/4/news/latest知乎详情http://news-at.zhihu.com/api/4//news/{KaTeXparseerror:Expected'EOF',got'}'atposition3:......
  • 知乎使用指南
    知乎食用指南目前知乎近版本有一下恶心人的地方那个加入大量广告,每次刷新都夹杂大量广告为了增加广告,将左右翻页变成恶心的上下滑动翻译,在滑动中间增加广告,并且明显感受到左右滑动比上下滑动要好杂七杂八内容太多,导致内容卡顿从初中的时候就开始看知乎,结果知乎越来越恶心,以......
  • java实现获取百度/微博/头条/知乎热榜数据
    ​ java实现定时获取百度/微博/头条/知乎热榜数据,做一个热榜数据榜单。目录一、效果展示二、热搜榜单三、程序代码一、效果展示效果预览地址:https://www.ewba......
  • 主题挖掘LDA和情感分析图书馆话题知乎用户问答行为数据|附代码数据
    全文链接:http://tecdat.cn/?p=16890最近我们被客户要求撰写关于主题挖掘LDA和情感分析的研究报告,包括一些图形和统计输出。当前是大数据盛行的时代,各种用户信息行为数据......
  • ChatGPT留给知乎、小红书的时间不多了
    大家好啊,今天我打算给大家整点好活!挑战一下用ChatGPT打入各平台内部。知乎挑战!首先了解一下“知乎体”知乎体是以专业知识为基础,以清晰的条理对问题进行阐述,并解决该问......