你可以如何使用unsync::OnceCell或unsync::Lazy来处理一个不是sync的类型?

huangapple go评论125阅读模式
英文:

How can I use unsync::OnceCell or unsync::Lazy for a type that is not sync?

问题

我正在研究once_cell的功能,以延迟初始化全局/静态实例。文档中的示例集中在Sync情况下,即它使用sync::OnceCell / sync::Lazy

我想知道对于不是Sync的类型,它将如何工作。简单地将sync::Lazy翻译为unsync::Lazy会导致(使用这个技巧来模拟非同步类型):

use once_cell::unsync::Lazy;

// 一个不是同步的类型示例
struct TypeNotSync {
    _marker: PhantomData<Cell<()>>
}

static GLOBAL_DATA: Lazy<TypeNotSync> = Lazy::new(|| 
  // 让我们暂时忽略实际的初始化...
  unimplemented!()
);

这不会编译,编译器坚持认为底层类型必须是Sync

error[E0277]: `Cell<std::option::Option<fn() -> TypeNotSync>>` 无法在线程之间安全共享
   --> src/resources.rs:75:21
    |
75  | static GLOBAL_DATA: Lazy<TypeNotSync> = Lazy::new(|| unimplemented!());
    |                     ^^^^^^^^^^^^^^^^^ `Cell<std::option::Option<fn() -> TypeNotSync>>` 无法在线程之间安全共享
    |
    = help: 在`once_cell::unsync::Lazy<TypeNotSync>`中,`Sync`特性未为`Cell<std::option::Option<fn() -> TypeNotSync>>`实现
    = note: 如果您想在多个线程之间进行别名和更改,请使用`std::sync::RwLock`
note: 需要因为它出现在类型`Lazy<TypeNotSync>`中
   --> /home/fabian/.cargo/registry/src/index.crates.io-6f17d22bba15001f/once_cell-1.18.0/src/lib.rs:716:16
    |
716 |     pub struct Lazy<T, F = fn() -> T> {
    |                ^^^^
    = note: 共享的静态变量必须具有实现`Sync`的类型

在直接使用unsync::OnceCell时也会发生相同的情况。

我现在有点困惑了。我本来期望unsync版本的OnceCell / Lazy不需要我的类型是Sync。实际上,查看参考文档,unsync::OnceCell本身似乎并不要求T: Sync。看起来更像是编译器因为static的原因而强加的,因为在函数内部使用局部变量let not_global_data: Lazy<TypeNotSync> = ...会编译。然而,这违背了使用OnceCell的目的——毕竟,目标是获得“延迟静态”。

所以问题变成了:如何将OnceCell与非同步类型结合使用?

英文:

I'm looking into once_cell's functionality to lazily initialize a global/static instance. The example in the documentation focuses on the Sync case, i.e., it uses sync::OnceCell / sync::Lazy.

I'm wondering how that would work for a type that is not Sync. Simply translating the sync::Lazy to unsync::Lazy would result in (using this trick to emulate a non-sync type):

use once_cell::unsync::Lazy;

// Example of a type that is not sync
struct TypeNotSync {
    _marker: PhantomData&lt;Cell&lt;()&gt;&gt;,
}

static GLOBAL_DATA: Lazy&lt;TypeNotSync&gt; = Lazy::new(|| 
  // Let&#39;s ignore the actual initialization for now...
  unimplemented!()
);

This doesn't compile, and the compiler insists that the underlying type has to be Sync.

error[E0277]: `Cell&lt;std::option::Option&lt;fn() -&gt; TypeNotSync&gt;&gt;` cannot be shared between threads safely
   --&gt; src/resources.rs:75:21
    |
75  | static GLOBAL_DATA: Lazy&lt;TypeNotSync&gt; = Lazy::new(|| unimplemented!());
    |                     ^^^^^^^^^^^^^^^^^ `Cell&lt;std::option::Option&lt;fn() -&gt; TypeNotSync&gt;&gt;` cannot be shared between threads safely
    |
    = help: within `once_cell::unsync::Lazy&lt;TypeNotSync&gt;`, the trait `Sync` is not implemented for `Cell&lt;std::option::Option&lt;fn() -&gt; TypeNotSync&gt;&gt;`
    = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock`
note: required because it appears within the type `Lazy&lt;TypeNotSync&gt;`
   --&gt; /home/fabian/.cargo/registry/src/index.crates.io-6f17d22bba15001f/once_cell-1.18.0/src/lib.rs:716:16
    |
716 |     pub struct Lazy&lt;T, F = fn() -&gt; T&gt; {
    |                ^^^^
    = note: shared static variables must have a type that implements `Sync`

The same happens when using unsync::OnceCell directly.

I'm a bit confused now. I'd expected the unsync flavors of OnceCell / Lazy should not require my type to be Sync. In fact, looking at the reference docs, unsync::OnceCell itself doesn't seem to require T: Sync. It looks like it is rather imposed by the compiler because of the static, because using a local let not_global_data: Lazy&lt;TypeNotSync&gt; = ... inside a function compiles. However, this defeats the purpose of using OnceCell -- after all, the goal was to get a "lazy static".

So the question becomes: How can OnceCell be used in combination with a non-sync type?

答案1

得分: 2

不能。

如果一个类型不是Sync,在不同的线程中拥有多个共享引用是不安全的。OnceCell允许你这样做,所以在static中使用非Sync类型是不安全的,这是根据Sync的定义来说的。

RefCell为例:如果你将它放在一个static中,即使在OnceCell的后面,你仍然可以从多个线程并发修改它,从而引发数据竞争。OnceCell不会改变这一点。它只影响类型的初始化。

如果你有一个既不是Sync也不是Send的类型,那么你不能安全地将它用在static中。一个可能的方法(也许在某个crate中实现了)是在一个静态变量中(不安全地)拥有它,并且只允许一个线程使用它,通过通道发送命令(并接收结果)给这个线程。

如果类型不是Sync但是是Send,你可以使用Mutex。它具有使非Sync(但是Send)类型变为Sync的独特属性,因为它从不产生对它们的共享引用:它只允许可变引用,而不是并发地共享引用。

英文:

It cannot.

If a type is not Sync, is it not safe to have multiple shared references to it in different threads. OnceCell let you do that, so it is not safe to use in static with non-Sync types, by the definition of Sync.

Take RefCell, for example: if you put it in a static, even behind a OnceCell, you can modify it concurrently from multiple threads and cause a data race. OnceCell does not change that. It only affects the initialization of the type.

If you have a type that is neither Sync nor Send, then you cannot use it in static safely. A possibility (maybe implemented in some crate) is to have a static with it (unsafely) with only one thread allowed to use it, and send commands (and receive back results) to this thread via channels.

If the type is not Sync but is Send, you can use a Mutex. It has the unique property of making non-Sync (but Send) types Sync, because it never yields shared references to them: all it allows is mutable references, not concurrently.

答案2

得分: 2

由于 !Sync 意味着某种类型无法安全地从多个线程访问,显然一个普通的 static 不能安全地持有这样的值,因为它可以从所有线程访问。

std::cell::OnceCellonce_cell::unsync::OnceCell 的明显用例是线程本地静态变量:

thread_local!{
    static X: OnceCell<TypeNotSync> = OnceCell::new();
}
// 使用 `X` 每个线程有一个实例。
英文:

Since !Sync means a type cannot safely be accessed from multiple threads a plain static obviously cannot safely hold such a value since it can be accessed from all threads.
The obvious usecase for a std::cell::OnceCell/once_cell::unsync::OnceCell are thread local statics:

thread_local!{
    static X: OnceCell&lt;TypeNotSync&gt; = OnceCell::new();
}
// use `X` with one instance per thread.

答案3

得分: 1

你可以使用 thread_local 来创建非同步的静态值。

thread_local! {
    static GLOBAL_DATA: Lazy<TypeNotSync> = Lazy::new(|| 
        unimplemented!()
    );
}

尽管如此,这在使用 thread_local 时已经实现了懒初始化,所以这是相当无用的。

once_cell 中的非 Sync 类型主要适用于那些你不知道值是否会被初始化以及何时初始化的情况。

fn main() {
    let global_data: Lazy<String> = Lazy::new(||
        "初始化".to_string()
    );

    for _ in 0..4 {
        if rand::random() {
            println!("{}", *global_data);
        } else {
            println!("未初始化");
        }
    }
}

这允许你将初始化逻辑与其余部分分离,这可能会使代码更清晰,同时也可以消除初始化时对 mut 的要求。

你还可以将其不透明地传递为 impl Deref<Target = String>,而无需在 API 中公开 Lazy。这也是表示昂贵的解引用操作的良好类型。

英文:

You can use thread_local to create non-sync static values.

thread_local! {
    static GLOBAL_DATA: Lazy&lt;TypeNotSync&gt; = Lazy::new(|| 
        unimplemented!()
    );
}

This is pretty useless, though, since thread_local already lazily initializes the values.

The non-Sync types in once_cell are mostly useful for cases where you do not know if or when a value will be initialized.

fn main() {
    let global_data: Lazy&lt;String&gt; = Lazy::new(||
        &quot;initialization&quot;.to_string()
    );

    for _ in 0..4 {
        if rand::random() {
            println!(&quot;{}&quot;, *global_data);
        } else {
            println!(&quot;no initialization&quot;);
        }
    }
}

This allows you to separate initialization logic from the rest, which may create clearer code, as well as remove the requirement of mut from initialization.

You can also pass this around opaquely as impl Deref&lt;Target = String&gt; without making Lazy visible in the API. It's also a good type to express an expensive deref.

huangapple
  • 本文由 发表于 2023年8月11日 05:17:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/76879354.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定