英文:
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<Cell<()>>,
}
static GLOBAL_DATA: Lazy<TypeNotSync> = Lazy::new(||
// Let'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<std::option::Option<fn() -> TypeNotSync>>` cannot be shared between threads safely
--> src/resources.rs:75:21
|
75 | static GLOBAL_DATA: Lazy<TypeNotSync> = Lazy::new(|| unimplemented!());
| ^^^^^^^^^^^^^^^^^ `Cell<std::option::Option<fn() -> TypeNotSync>>` cannot be shared between threads safely
|
= help: within `once_cell::unsync::Lazy<TypeNotSync>`, the trait `Sync` is not implemented for `Cell<std::option::Option<fn() -> TypeNotSync>>`
= 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<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: 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<TypeNotSync> = ...
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::OnceCell
或 once_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<TypeNotSync> = 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<TypeNotSync> = 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<String> = Lazy::new(||
"initialization".to_string()
);
for _ in 0..4 {
if rand::random() {
println!("{}", *global_data);
} else {
println!("no initialization");
}
}
}
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<Target = String>
without making Lazy
visible in the API. It's also a good type to express an expensive deref.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论