Multithreading with real-time constraints

How can I ensure that my main-thread will run in real-time, i.e. additional threads won’t make the main thread stutter?

Detailed Explanation:
I’ve got a randomly generated world that is closely based on voxels. The world itself is divided into several smaller parts called Chunks. The geometry for those chunks is created in separate threads. The main thread takes the output of these threads (vertices) and puts them into Unity’s Mesh objects. There is some additional information that needs to be generated, namely waypoints for NPC’s. Those waypoints shall be calculated in separate threads as well.
It’s okay if the threads for the creation of the chunks and for the waypoints take a lot of time but I don’t want them to slow down the main thread.

Which options do I have to accomplish this? Can I force threads to run on certain CPU’s only? Can I work with priorities? What’s the best practice?

So far I’ve already used C# threads with semaphores and the C# ThreadPool but neither of them kept the main thread running in real-time. Any ideas?

In Mono/.Net you can not select which cpu core a thread runs on. They are managed threads, and the framework will decide where to put the thread.

I’m not sure how well the version of Mono, that Unity uses, is at selecting where to run the thread. .Net is usually pretty good at it though.

Technically (you’ll probably need the pro version), you could write some native code in C++, or try p/invoke, and have your own forced threading system. Note this will NOT integrate with the mono/.net threads at all, and has a lot of potential issues that could arise.

Hi lordofduct and thanks for you answer,

your hint led me to a post on stackoverflow: Set thread processor affinity in Microsoft .Net which in turn made me program the following static method that needs to be called from with the thread in question:

public static void DoNotRunOnMainCPU()
{
    System.Threading.Thread.BeginThreadAffinity();

#pragma warning disable 618
    int osThreadId = AppDomain.GetCurrentThreadId();
#pragma warning restore 618

    ProcessThread thread = Process.GetCurrentProcess().
        Threads.Cast<ProcessThread>().
        Where(t => t.Id == osThreadId).Single();

    long cpuMask = 1;

    if (Environment.ProcessorCount > 1)
    {
        cpuMask = 0;

        for (int cpu = 1; cpu < Environment.ProcessorCount; ++cpu)
        {
            cpuMask |= 1L << cpu;
        }
    }

    thread.ProcessorAffinity = new IntPtr(cpuMask);
}

However, it doesn’t work for some reason. When I call DoNotRunInMainCPU() within a method of a thread the code after this method won’t be executed. I will have to investigate it further.

I really hope that you are wrong and that there is a way to accomplish what I need but your answer was valuable to me nonetheless.

Resurrecting an old thread because I can’t find one recent : does anybody found a way to lock a managed thread to a specific core with unity? It seems the mono version used by unity does not implement Process.Threads. The list is always empty. So I’m looking for a multiplatform workaround…

I don’t think it’s a wise decision. For the more generic and better approach try using new Jobs / ECS system.
You won’t be needing thread cpu switching if you partition your tasks accordingly.

I’m looking into this right now and all the copying around frightens me off and it requires a lot more work for what will probably be a small win in the end. I’m currently optimizing an already finished game for the console ports. I would agree if it was to start a new project…

EDIT: in my case, I was asking because I was rolling my own task scheduler (a simple parallel for actually). I was having huge 10ms random stalls on xbox which I believe are due to some tasks running on core 2 and FMOD overtaking the core from time to time.

As expected, with unity’s job system, all the performance win is lost in the unfortunately necessary NativeArray to managed list conversion…

If it’s multiplatform code, then I’d be very very wary of trying to set the processor affinity, at least in the general sense. If it’s xbox specific code, then that may be better, but still not completely desirable as that means specific code for each platform you support.

If the game is already finished and it’s just the 10ms spikes you’re trying to get rid of, then I’d limit your focus to eliminating the spikes. Did your job system implementation get rid of the spikes at least?

The spikes were coming from a multithreading optimisation I did while porting, they were not there in the first place. I have a lot to do aside from that.

I already have platform specific code, I just tend to minimize it. I wouldn’t mind having an a list of worker cores per platform…

The screenshot you see is the result of the job system version of my same multithreading optimisation. It doesn’t have any spikes. Judging from the 4 worker cores, I guess unity makes the jobs run on core 0, 3, 4, 5 and 6, just like I wanted it in the first place :slight_smile:
But there is that huge overhead from getting back the data from nativearray to managed, that completely makes the optimisation pointless… I’m going to try to find other ways of moving the data from native to managed. If I can’t find a fast one, or find a way of starting managed threads on specific CPU cores, I’ll keep the code single threaded and the game will never run at 60…

hi bro,do you resolved the problem? i also be
troubled by this problem,can you give me some tips?

Yes, with a hack that enables me to run managed code with unity’s job system. Bear in mind that all the safeguards that unity implemented to avoid race conditions and stuff will not work so you have to carefully write your code.

using Unity.Jobs;
using System;
using System.Runtime.InteropServices;

// Usage:
// struct MyJob : ManagedJob.IWork
// {
//     void Execute() { }
// }
// ...
// ManagedJob job = new ManagedJob() { Work = new MyJob(); }
// JobHandle handle = job.Schedule();
// handle.Complete();
// job.Dispose();
struct ManagedJob<T> : IJob, IDisposable where T : class, ManagedJob<T>.IWork // T must be class so that Work is mutable
{
    public interface IWork
    {
        void Execute();
    }

    GCHandle handle;
    public T Work
    {
        set
        {
            handle = GCHandle.Alloc(value);
        }
        get
        {
            return (T)handle.Target;
        }
    }

    void IJob.Execute()
    {
        IWork task = (IWork)handle.Target;
        task.Execute();
    }

    public void Dispose()
    {
        handle.Free();
    }
}

struct ManagedParallelFor<T> : IJobParallelFor, IDisposable where T : class, ManagedParallelFor<T>.IWork
{
    public interface IWork
    {
        void Execute(int index);
    }

    GCHandle handle;
    public T Work
    {
        set
        {
            handle = GCHandle.Alloc(value);
        }
        get
        {
            return (T)handle.Target;
        }
    }

    void IJobParallelFor.Execute(int index)
    {
        IWork task = (IWork)handle.Target;
        task.Execute(index);
    }

    public void Dispose()
    {
        handle.Free();
    }
}

thanks

hi bro, i’am use job to caculate my fight core,but the fight performance are same as using c# thread, Is there anything to watch out for? Shouldn’t the big core run faster?