feat: implement multi-threaded JobScheduler with worker threads and dependency management

This commit is contained in:
2026-05-02 17:53:45 +09:00
parent 0265a386ba
commit b0220a2350
4 changed files with 27 additions and 100 deletions

View File

@@ -144,61 +144,12 @@ internal class WorkerThread : IDisposable
continue;
}
// Try to acquire a reference count for the job. This ensures that the job won't be removed while we're processing it.
// This is critical that if thread A reads the job, but suddenly os scheduler delay the thread for just a moment, and thread B completes the job and removes it from the system, when thread A resumes, it might be accessing invalid memory.
// By acquiring a reference count, we ensure that even if the job is completed while we're processing it, it won't be removed until we're done.
var rcSpin = new SpinWait();
var rcAcquired = false;
int rc;
while (true)
var currentState = Volatile.Read(ref jobInfo.state);
if (JobUtility.GetState(currentState) == JobState.Scheduled)
{
_scheduler.GetJobInfoReference(handle, out var currentExist);
if (!currentExist)
{
break;
}
var stateVal = Volatile.Read(ref jobInfo.state);
var state = JobUtility.GetState(stateVal);
if (state == JobState.Completed || state == JobState.Invalid)
{
break;
}
var newState = stateVal + JobUtility.RC_ONE;
if (state == JobState.Scheduled)
{
newState = (newState & ~JobUtility.STATE_MASK) | JobUtility.JOBSTATE_RUNNING;
}
// Attempt to acquire a reference count by incrementing the state value. If the state has changed since we read it, we need to retry.
if (Interlocked.CompareExchange(ref jobInfo.state, newState, stateVal) == stateVal)
{
_scheduler.GetJobInfoReference(handle, out currentExist);
if (!currentExist)
{
rc = JobUtility.ReleaseRC(ref jobInfo.state);
if (rc == 0)
{
_scheduler.MarkJobComplete(handle);
}
break;
}
rcAcquired = true;
break;
}
rcSpin.SpinOnce(-1);
}
if (!rcAcquired)
{
continue;
// Try to flag as running, but don't loop if we fail. Someone else might have already flagged it.
var newState = (currentState & ~JobUtility.STATE_MASK) | JobUtility.JOBSTATE_RUNNING;
Interlocked.CompareExchange(ref jobInfo.state, newState, currentState);
}
if (jobInfo.pExecutionFunc != null)
@@ -214,7 +165,7 @@ internal class WorkerThread : IDisposable
jobInfo.pExecutionFunc(jobInfo.dataID, jobInfo.dataGeneration, ref jobInfo.jobRanges, in ctx);
}
rc = JobUtility.ReleaseRC(ref jobInfo.state);
var rc = JobUtility.ReleaseRC(ref jobInfo.state);
if (rc == 0)
{
_scheduler.MarkJobComplete(handle);