What to use instead of std::sync::Once?

We need a synchronization point, something that only one thread may access at a time. std::sync::Once provides that in beta, using the following pattern:

static SYNC: Once = Once::new();
let mut thing: Option<String> = None;
let _ = catch_unwind(AssertUnwindSafe(|| SYNC.call_once_force(|_| {
  // only one thread may be doing the operations in here at a time.
  thing = whatever;
  panic!()
})));

but anyway this seems to make ppl angry so what do you use instead?

If I understand your post correctly, you are looking for alternatives to Once::call_once_force specifically. (I had a hard time understanding this at first, so I'm posting to clarify it for others. I'd encourage you to include a bit more detail/context in future posts.)

What do you mean? What objections are discouraging you from using this method?

Because Once should be used when you want the thing to only be called once and shouldn't be used to synchronize access like this or something

Ensuring exactly-once initialization in a multi-threaded context, which is the job of Once, inherently involves synchronization, so that objection doesn't make sense to me. Even if it did, it would apply equally to any use of Once, not just this specific method.

If you want general synchronization that's not related to exactly-once initialization, you should use std::sync::Mutex instead.

Well, Mutex doesn't go into static.

You could use a parking_lot::Mutex.

1 Like

Hi,

you might want to check lazy_static!
With this you can wrap the Mutex into a static variable.

1 Like

There's also https://crates.io/crates/once_cell as an alternative to lazy_static

2 Likes

once_cell is being gradually incorporated into std, so it'll eventually be possible in stable without crates. This works on nightly today:

use std::lazy::SyncLazy;
use std::sync::Mutex;

static LAZY: SyncLazy<Mutex<()>> = SyncLazy::new(Mutex::default);

However, since SyncLazy is essentially a mutex already, putting a sync::Mutex inside it does seem a bit overkill. parking_lot's Mutex doesn't have this problem since you can just make it a regular (i.e., non-lazy) static.

I think Once::call_once_force is okay in scenarios like this. It looks like it has a wait queue so it's not just a spinlock. It might be nice if there was another way in the standard library but I wouldn't stress about it. The calling code is ugly, though -- I'd try to wrap that up in a nice wrapper so I could just call SYNC.with_lock(|| whatever).

However, you might want to consider what it is you're actually synchronizing access to. In the original example you synchronize access to a local variable, something that should probably be done with a plain Mutex instead. If what you're doing in your real code resembles that, there might be something to the idea that you're abusing Once.

2 Likes

We were gonna use it to synchronize access to env vars (specifically get+remove) but it might be better if we change our code around to not need that. (say, by loading a copy of all env vars at startup and using a Mutex or something on that copy.)

SyncLazy isn't a mutex... it may contain a mutex as an implementation detail, but that's not relevant. It's perfectly fine to put a mutex inside a SyncLazy.

Sure, but SyncLazy already has some degree of synchronization built in, just not in a way that's useful to the OP. Putting a Mutex inside it adds another "layer" of synchronization, which makes the outer layer not really necessary -- it's only required because Mutex::new isn't a const fn. Technically it could have a non-negligible effect on performance, although that seems unlikely. It just feels kind of clunky to me.

They are different kinds of synchronization, but yeah I see what you mean