multithreading - 使用 `Rc` 和 `thread_local!` 在多个线程之间共享包含 `OnceCell` 引用的静态延迟初始化对象

标签 multithreading concurrency static rust lazy-initialization

我已将测试分为几个相似的部分。在每个部分中,结果都会与静态测试字符串进行比较,该字符串以专用测试语言(此处称为 dum)编写,并使用 pest 进行解析。 .


这是我的 MWE 的全局结构。

$ tree
.
├── Cargo.lock
├── Cargo.toml
├── src
│   └── main.rs
└── tests
    ├── dum.pest
    ├── section_1.rs
    └ .. imagine more similar sections here.
  • Cargo.toml
[package]
...
edition = "2018"

[dev-dependencies]
pest = "*"
pest_derive = "*"
once_cell = "*"
lazy_static = "*"

  • main.rs 仅包含 fn main() {}
  • dum.pest 是一个虚拟 any = { ANY* }
  • section_1.rs 序言是:
use pest_derive::Parser;
use pest::{iterators::Pairs, Parser};

// Compile dedicated grammar.
#[derive(Parser)]
#[grammar = "../tests/dum.pest"]
pub struct DumParser;

// Here is the static test string to run section 1 against.
static SECTION_1: &'static str = "Content to parse for section 1.";

// Type of the result expected to be globally available in the whole test section.
type ParseResult = Pairs<'static, Rule>;

现在,我第一次天真的尝试使解析结果可用于所有测试函数是:

// Naive lazy_static! attempt:
use lazy_static::lazy_static;
lazy_static! {
    static ref PARSED: ParseResult = {
        DumParser::parse(Rule::any, &*SECTION_1).expect("Parse failed.")
    };
}
#[test]
fn first() {
    println!("1: {:?} parsed to {:?}", &*SECTION_1, *PARSED);
}
#[test]
fn second() {
    println!("2: {:?} parsed to {:?}", &*SECTION_1, *PARSED);
}

这不能编译。根据pest ,这是因为它们使用无法在线程之间安全共享的内部 Rc 引用,并且我认为 cargo test 确实为每个 #[test]< 旋转一个新线程 函数。

建议的解决方案涉及使用 thread_local!OnceCell,但我无法弄清楚。以下两种尝试:

// Naive thread_local! attempt:
thread_local! {
    static PARSED: ParseResult = {
        println!(" + + + + + + + PARSING! + + + + + + + "); // /!\ SHOULD APPEAR ONLY ONCE!
        DumParser::parse(Rule::any, &*SECTION_1).expect("Parse failed.")
    };
}
#[test]
fn first() {
    PARSED.with(|p| println!("1: {:?} parsed to {:?}", &*SECTION_1, p));
}
#[test]
fn second() {
    PARSED.with(|p| println!("2: {:?} parsed to {:?}", &*SECTION_1, p));
}

// Naive OnceCell attempt:
use once_cell::sync::OnceCell;
thread_local! {
static PARSED: OnceCell<ParseResult> = {
    println!(" + + + + + + + PARSING! + + + + + + + "); // /!\ SHOULD APPEAR ONLY ONCE!
        let once = OnceCell::new();
        once.set(DumParser::parse(Rule::any, &*SECTION_1).expect("Parse failed."))
        .expect("Already set.");
        once
    };
}
#[test]
fn first() {
    PARSED.with(|p| println!("1: {:?} parsed_to {:?}", &*SECTION_1, p.get().unwrap()));
}
#[test]
fn second() {
    PARSED.with(|p| println!("2: {:?} parsed_to {:?}", &*SECTION_1, p.get().unwrap()));
}

编译和运行都很好。但是 cargo test -- --nocapture 的输出 建议每个测试函数实际上进行一次解析:

running 2 tests
 + + + + + + + PARSING! + + + + + + +
 + + + + + + + PARSING! + + + + + + +
1: "Content to parse for section 1." parsed_to [Pair { rule: any, span: Span { str: "Content to parse for section 1.", start: 0, end: 31 }, inner: [] }]
2: "Content to parse for section 1." parsed_to [Pair { rule: any, span: Span { str: "Content to parse for section 1.", start: 0, end: 31 }, inner: [] }]
test first ... ok
test second ... ok

test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

这表明我的两次尝试都失败了。

这些方法有什么问题?
如何使每个部分仅进行一次解析?

最佳答案

为什么 lazy_static! 不合适?

cargo test 是否在每个测试中启动一个新线程实际上是无关紧要的。

static 变量是全局的,因此可能在线程之间共享,因此即使没有生成任何线程,它也必须同步 .

并且由于 Rc 不是 Sync(无法在线程之间共享),因此这不起作用。

为什么thread_local!不合适?

顾名思义,每个线程有一个 thread_local! 变量。

thread_local! 中的代码实际上不会在线程创建后立即运行;因为该变量是在第一次访问时延迟实例化的。

如何使每个部分仅进行一次解析?

不要直接使用pest的输出。

如果您对 pest 的输出进行后处理并创建一个 Sync 的结构,那么您可以使用 lazy_static 存储它> 并且只会解析一次。

实际上,您可以更进一步,完全避免 lazy_static。如果您可以用纯粹的 const 方式表达结构,那么您可以使用 build.rs 脚本或过程宏在编译时将字符串转换为模型。但对于测试来说,这可能不值得付出努力。

关于multithreading - 使用 `Rc` 和 `thread_local!` 在多个线程之间共享包含 `OnceCell` 引用的静态延迟初始化对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57134076/

相关文章:

c - STREAM OpenMP 基准解读

java - Android - 无法对非静态字段进行静态引用

c - 当非静态函数声明跟在静态函数声明之后会发生什么?

c - 两个静态变量的相同内存位置分配

java - 多线程 Java - 从 Web 应用程序暂停长时间运行的任务

java - 多线程是语言(如 java)的属性还是操作系统的属性?

java - Java 客户端中的 RabbitMQ channel 和线程

java - 对于在静态哈希表上同步的 java.util.Calendar 构造函数,我们可以做些什么?

java - 更新键/值的集合并不能提供所需的性能

python concurrent.futures.ProcessPoolExecutor : Performance of . submit() vs .map()