genderphasing

parked threads

a niche but neat api

mood: delightedsoftwarerust

so i've been writing a lot of multithreaded code in rust lately – not async, os threads – i discovered an api in the process that's been incredibly useful, if niche: thread parking!

for example, here's one use i've seen of it, implementing what amounts to a tiny sync-to-async bridge, from reqwest:

struct ThreadWaker(Thread);

impl Wake for ThreadWaker {
  fn wake(self: Arc<Self>) {
    self.wake_by_ref();
  }

  fn wake_by_ref(self: &Arc<Self>) {
    self.0.unpark();
  }
}

the full explanation of how this works is, honestly, a lot, but the short version is that trait Wake is how an async runtime knows how to resume a task. wrapping it around the parking api effectively turns a thread into an async task – you'd just make sure your otherwise-blocking calls have a thread::park() call, and they'll resume when the async runtime wants to poke them again. and that's pretty neat!

i've also found it useful in building simple "background work" systems, especially when i need to interface with process priorities, since obviously async tasks can't be prioritized at the kernel level.

for example, in one project i had one realtime thread to handle inputs within the hard requirements, and queue up background work to be done vaguely "later". originally, every background thread just pulled immediately from a queue, but that led to some pretty severe disk i/o overhead that interfered with the realtime work. (i know, realtime work doing disk i/o, but it is what it is.)

at the same time, I needed the work to continue, since there were (softer) time constraints on it that might get missed if everything waited while the realtime bit ran. plus, the realtime bit exceeding the spec was the main way to indicate lag, so if there's enough work that it can't be done within the realtime… time, it's fine for the realtime bits to start exceeding spec.

so, the fix was pretty simple: have the background threads park when they went idle, and the realtime thread wake them back up when it was done. no contention when things are running smoothly, but work continues when load is high. and when we finally switched to ssds, the load naturally reduced, since we were just letting the machine lag rather than synthetically detecting and reporting slowness, and the machine stopped lagging!

it's worth explicitly noting that this is a very niche type of situation. in most cases you don't have hard realtime requirements, so you can just write everything as an "async soup" and let things finish as they finish. when you do have hard realtime requirements, usually you cannot breach them, so you need strategies to guarantee them instead of letting them overload.

but in that kind of niche situation, there isn't really another option; anything you built would just be a reimplementation of the parking api. and that is why i find it so delightful: it's a simple, intuitive api that i don't recall seeing elsewhere and can be used for a wide variety of niche use-cases that would otherwise require a bunch more rigging.