Multi-Threading in Unity

Stuff I did.

Multi-Threading in Unity

Ever since I started programming with Unity I heard a lot of people say that Unity’s biggest downside is that you can’t use multi-threading except for utilizing CoRoutines (which, by the way, aren’t exactly multi-threading anyways (I’ll get into that in a future post)!) Doing a bunch of research and actually getting some insight on this topic I realized that this is quite the bullshit statement – you can in fact multi-thread within Unity, there are just some things you have to consider when you are attempting it. This article will give you a basic understanding of what multi-threading is, how it works and how to implement it safely in Unity.

Why do people say it isn’t possible?

Well, I can’t say for sure but it is probably based on two things. First of all, Unity did rely on a Mono version that somewhat resembled .NET 3.5 for quite a while. Nowadays, if you are already on Unity 2017 you at least have Mono 4.8 and IL2CPP with support for C# 6 and .NET 4.6 – which, still isn’t perfect and keeps you from using the features for threading newer versions of .NET have, but it doesn’t make it impossible to multi-thread. You do still have System.Threading and all its basic functionalities.

Second of all, Unity does limit you in a way that it will throw you a bunch of RuntimeErrors if you are just trying to construct, use or compare Unity types in any other but the main thread. This does make sense as it is meant to be a safety feature. I will go further into detail on the horrible things that can happen if you work with multiple threads, badly.

What is Multi-Threading?

To understand what multi-threading is you have to know how code usually gets executed. Normally, in a single-threaded program, all functions are executed linearly. Imagine an actual piece of thread with knots in it. The entire thing is the execution and the knots symbolize the functions. If you run the thread through your fingers you will feel one knot after another and that’s how non-multi-threaded code gets executed, one by one in a single line of order. Now, if one of these knots is bigger, it might be harder for you to pull it through your fingers, so it will take longer and while that knot is stuck we can’t proceed, makes sense? Good. Now, this is where multi-threading enters the stage, because what we are doing is just adding a second piece of thread so we can execute things parallel, in an attempt to prevent getting stuck waiting for something else.

Why would I need multiple threads?

I understand that it does sound like a lot of work and don’t get me wrong, it is but multi-threading is a great concept that can if executed well, give your game a major performance boost. Let me give you some examples of what multi-threading could be great for, there might be something that is very lengthy but you need to do it while the game is running – think of procedurally generated worlds? Maybe a new chunk of the world needs to get generated because a player is approaching an edge…  another great example, which is also incidentally the reason why I started working with multi-threading: Pathfinding. There are many games that use huge entities of AI Agents – let’s say you have to calculate a Path for a thousand agents. That would probably take quite a while and there is no reason why that should give your player a lag. Doing it in the meantime in the background can save your ass here, performance-wise.

Is Multi-Threading the solution to all performance issues?

No, sadly it isn’t. While the idea of executing difficult or lengthy tasks in a different thread in the background to speed up things, in theory, sounds like a total game changer, literally speaking. In real life, in complex applications where a lot of things are going on it can get very complicated to implement multi-threading. You might end up having to lock a lot of things, race conditions could be a problem or simply just having to wait until another thread has finished its thing. These things are the very real reason why multi-threaded code can in fact easily be much slower than single-threaded code.

How to implement it in Unity?

Now, let’s talk about implementing multi-threading in Unity. First of all, it is worth noting that Unity does, in fact, do some multi-threading internally – a good example here is the physics engine which is running on a separate thread, this is also the reason why we have FixedUpdate(). The only thing Unity keeps you from doing is accessing/modifying most of the Unity types. So you can’t for example access a game object in a separate thread. And why is that? What Unity does internally is before doing anything to a game object, it simply checks the thread’s ID and makes sure it is one and there is a very real reason for Unity to do that. The problematic here is that if you were to change something in the main thread and then also in a secondary thread, there isn’t only the option that you could, depending on where you are at in your execution, end up with either one of the results, you could also just have bad luck and end up with something which is half of each result. What do I mean by that?

Let’s say we want to set an objects transform.position to Vector3(0,0,0) in the main thread. But then we are also changing said transform.position in our secondary thread but we change it there to Vector3(10,10,10). What happens is that we are rendering our object to be at the position we wanted it to be at but what happens if we are midway through the rendering and the position changes? We could end up with a new position of Vector3(0,0,10) which is neither one of our options, so in other words – it’s complete bullshit data.

Now, even if you can’t access or modify game objects in a secondary thread, you can still implement multi-threading. A good use for multi-threading is pathfinding, the only thing you have to keep in mind is that you can’t access any objects, so you basically have to end up working with f.e. Vector3() or Vector2(), which both work for multi-threading.

I mentioned the possibility of ending up with crappy data before, this is a thing that can happen very easily with multi-threading, no matter what you are doing in your second thread – but there are ways to prevent that. What we have to do is lock an object while we are modifying it, to make sure that our threads can’t try to change it at the same time. What locking does is it basically makes the object, or whatever you are locking untouchable until the code that is within your lock is executed. I’ll make sure to add a lock to one of my example scripts so you can see what I mean by that.
This does sound like a pretty easy thing to do right? Just lock everything to make sure….. WRONG! The problem here is that every lock produces a little bit of overhead, which will eventually bring down your performance again.

In general, multi-threading is a highly complex thematic and you actually do have to have a lot of knowledge to actually proceed with it safely. There are many things you have to keep in mind and consider when trying to multi-thread your program. I don’t want you to run around thinking that you are an expert in multi-threading just because you read this article of mine – you’re not.  I am just trying to give you some insight and maybe spark the will for you to do some more research of your own.

There is a multitude of patterns you can follow to proceed multi-threading, I wrote short example scripts for the two main ones and I hope they will help you to understand everything you read before better and maybe gain the last bit of grasp that was missing.

I.) A Thread that ends automatically as soon as it’s task is fulfilled

This is basically the beginner version of multi-threading. We are basically initiating a new thread with one task and we let it end automatically. We don#t have to worry about joining our threads together and about what happens if you want to quit our program.

using UnityEngine;
using System.Threading;
/*Use System.Threading to access all the types you will need for basic 
 *multi-threading.*/

public class MultiThreadedBehaviour : MonoBehaviour
{
    private Thread myThread;
    private bool changeObjInMainThread;
    private Vector3 myOjbsVector;

    private void Start()
    {
        /*We are initializing and starting the new Thread in the Start()-Method 
         *giving it a delegate which basically is the new Threads task.*/
        myThread = new Thread(MultiThreadedMethod);
        myThread.Start();
    }

    private void Update()
    {
        if (changeObjInMainThread)
        {
            lock (myOjbsVector)
            {
                myOjbsVector = new Vector3(0, 0, 0);
            }
        }
    }

    private void MultiThreadedMethod()
    {
        /*We are locking the object to make sure we won't end up with bad
         *data because two things try to change a value at the same time.*/
        lock (myOjbsVector)
        {
            myOjbsVector = new Vector3(10, 10, 10);
        }
    }
}

II.) A thread that stays alive until we end it manually

In this version, we are keeping our thread alive as long as we don’t set threadedTaskComplete to true. This has the downside that we will have to take care of joining our thread together with the main thread when we are ending our program.

using UnityEngine;
using System.Threading;                                 
/*Use System.Threading to access all the types you will need for basic 
 *multi-threading.*/

public class MultiThreadedBehaviour : MonoBehaviour
{

     private bool threadRunning;
    private bool threadedTaskComplete;
    private Thread myThread;

    private void Start()
    {
        /*We are initializing and starting the new Thread in the Start()-Method 
         *giving it a delegate which basically is the new Threads task.*/
        myThread = new Thread(MultiThreadedMethod);
        myThread.Start();
    }

    private void MultiThreadedMethod()
    {
        /*We need these booleans for our while-loop and to later exit out of the 
         *mutli-threading/joining our threads together when exiting out 
         *of our application.*/
        threadRunning = true;
        threadedTaskComplete = false;

        while (threadRunning && !threadedTaskComplete)
        {
            //Do whatever you want to execute in your new thread in here.
        }

        threadRunning = false;
    }

    private void OnDisable()
    {
        /*This is very important to prevent funky things from happening when you
         *are exiting your application and your thread might still be running. 
         *You should ALWAYS shut it down and join the threads together.*/ 
        if (threadRunning)
        {
            threadRunning = false;
            myThread.Join();
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *