#7233·tokio

Can the unified mutex for Inject and Idle in Tokio's Scheduler be safely split?

Author: foreverjunCreated Apr 2, 2025Updated Sep 7, 2026
LabelsC-questionA-tokioM-runtimeT-performance

Hello, I’ve been diving into the Tokio source code to explore potential performance improvements, specifically around the global task queue. I’m considering replacing the inject queue with a lock-free implementation to reduce contention. However, I noticed that inject and idle in the Synced struct are currently protected by a single Mutex:

pub(crate) struct Synced {
    pub(super) idle: idle::Synced,
    pub(crate) inject: inject::Synced,
}

This is defined in worker.rs. I initially assumed this was necessary to ensure state consistency—e.g., to prevent a thread from parking right after a task is added to inject, missing it due to a race. But having looked through the code I haven't found a critical section where something like this can happen.

This change was added in 2023. Before that, inject and idle had separate Mutexes, and the scheduler seemed to work fine. It seems that the “park/unpark” logic hasn't changed significantly since then. I'd appreciate it if anyone could help me:

  1. Does the unified Mutex for inject and idle provide specific advantages in the current design that I might be missing?
  2. Would separating the global queue (inject) from Synced and making it lock-free be safe, or could it introduce subtle issues (e.g., races between parking and task injection)?
  3. Since the code historically worked with separate Mutexes, is there a reason the single Mutex is preferred now beyond simplicity? I’d appreciate any insights or pointers to parts of the code I should double-check. Thanks!