Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Why no multithreading with Unity objects?

Discussion in 'General Discussion' started by Sequence, Aug 7, 2013.

  1. Sequence

    Sequence

    Joined:
    Feb 4, 2012
    Posts:
    44
    Anybody know why Unity made their classes so as to prevent any sort of threading/multicore code? This would open up so many possibilities yet Unity keeps it locked into one thread. Yes, I know you can do some calculations in other threads, but let's be real here. Why restrict it?


    Anyone who's tried has probably received:

    "Internal <MethodNameHere> can only be called from the main thread."

    EDIT: Not asking for true multithreaded-safeness here, just ease up on all the restrictions for people that know how to correctly multithread.
     
    Last edited: Aug 7, 2013
  2. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,221
    Internally Unity does a lot of multi threading and scheduling, but game level code can not be multi threaded for a variety of reasons. The primary one being that when the engine is ready for gameworld updates it is in a known and valid state. If for example you attempted to change a particle system during a threaded particle system update what would you expect to happen?

    That being said threading is supported in game code so long as the calls you make are not into unity API. What you can do is use a producer / consumer based architecture to generate calls you would like to make and then in a game update execute them. This means you can utilise more CPU, and issue calls into unity at the 'correct' time.
     
  3. Deleted User

    Deleted User

    Guest

    Hey Tim, my guess is that Sequence is aware of that. But why dont we have access to Methods that are not changing any unity object?
    For Example: I have an Level of Detail system currently for sale on the Asset Store wich is Multi Threaded. To optimise my calculation Thread I would like to do Frustrum culling but I didnt have access to
    Code (csharp):
    1.  
    2. static Plane[] CalculateFrustumPlanes(Matrix4x4 worldToProjectionMatrix);
    3.  
    and
    Code (csharp):
    1.  
    2. static bool TestPlanesAABB(Plane[] planes, Bounds bounds);
    3.  
    these methods dont interuppt any Unity Object (at least not from our point of view), so why dont we have access to these?
     
  4. Sequence

    Sequence

    Joined:
    Feb 4, 2012
    Posts:
    44
    Oh, I see. I'm realizing more and more how everything won't work multithreaded. Even creating a gameObject in another thread seems fine until you realize that it needs to get added to the global gameObject list. (Which is on the main thread). I see it's a lot harder than you'd expect.



    This makes sense (to me at least) and should probably be opened up. I don't know if static functions are thread safe or not I guess.

    But man, if Unity was multi-thread capable, that would be the ultimate.
     
  5. Meltdown

    Meltdown

    Joined:
    Oct 13, 2010
    Posts:
    5,816

    Frustum culling is done internally and automatically by Unity. I would think trying to do it yourself when Unity has already done it internally would cause problems, as in Tim's particle system example above?
     
  6. Sequence

    Sequence

    Joined:
    Feb 4, 2012
    Posts:
    44
    I don't think element_wsc wants to calculate the frustum planes for a camera, but just for an arbitrary matrix4x4 and get an array of planes. I don't think this should cause any issues internally right?
     
  7. jc_lvngstn

    jc_lvngstn

    Joined:
    Jul 19, 2006
    Posts:
    1,508
    I don't think you can even use Quaternions or Vectors, which does not make sense to me. These would be handy when trying to do calculations in other threads, instead you have to create your own.
     
  8. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    You can do alot with multithreading in Unity actually...

    one thing to keep in mind is that simple types in C# are atomic

    This means you can safely declare a bunch of Global ints or floats, then have an alternate thread update and make changes to them, while the main thread reads them in. With really no extra code at all, as long as your using simple types, your main Update loop will easily stay at 60 fps because it is not having to lock any variables. It may have to pause for like a fraction of a millisecond if it tries to read an int at the same time another thread is reading the int, but the reading of an int is so fast that I found it makes no performance hit at all. 60 fps can easily be maintained even with like an array of 10,000 ints being read and written to and from by multiple threads, while the Update loop is trying to access that same array.

    If you start thinking in terms of that, you can actually put alot of game logic into alternate threads. Make it so all serious game logic executes at a certain rate in alternate thread, then the Update loop on the main thread just polls all the global variables that were updated in alternate thread, then makes the calls to Unity's internal system to update whats on screen.

    This is how the game 'enrgy' in my profile works. Over 70% of the CPU load is actually happening on alternate threads in that game. It simulates a bunch of particles moving around, which all the particle movement is done on a seperate thread. The Update loop just reads what has been done on the alternate thread, then loads thats data into the particle system.
     
    Last edited: Aug 8, 2013
    kaiyum likes this.
  9. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,614
    Precisely. It's only MonoBehaviours which need to run in Unity's main thread, and there's no reason that you have to implement your logic directly inside them. You can simply use them as adapters to apply logic calculated outside of the scene (in whatever threading structure you want) to objects inside the scene.

    Having said that, I've so far never needed to do any such thing.
     
  10. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Actually, you can use them, and matrices too. I'm not sure about all the methods, but no problems so far.
     
  11. Gigiwoo

    Gigiwoo

    Joined:
    Mar 16, 2011
    Posts:
    2,981
    How does this work in code? I'm completely unfamiliar with how to code threading into C#/Unity. What you're doing sounds cool and I'd like to know how.

    Gigi
     
  12. jaybennett

    jaybennett

    Joined:
    Jul 10, 2012
    Posts:
    165
    Yes, please! Share with us :)
     
  13. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    There isn't a whole lot to explain really. Well there is actually a TON to explain, but without specific cases it's hard to explain that. The pattern of multithreading I describe is really specific to a specific way the game internally functions.

    Basically this is all you do at the base.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Threading;
    5.  
    6. public class ThreadedGameLogicObject : MonoBehaviour
    7. {
    8.     private Thread thread;
    9.     private Transform thisTransform;
    10.     private Vector3 currentPos;
    11.    
    12.     private void Start()
    13.     {
    14.         thisTransform = this.transform;
    15.        
    16.         thread = new Thread(ThreadUpdate); 
    17.         thread.Start();
    18.     }
    19.    
    20.     private void Update()
    21.     {
    22.         thisTransform.position = currentPos;
    23.     }
    24.    
    25.     //I used FixedUpdate function to modulate the speed at which the threads execute
    26.     //there is potentially like a dozen different ways to do this, and this one might not be ideal
    27.     //my game used no physics, so I could change the fixed timestep to whatever I needed to make this work
    28.     private void FixedUpdate()
    29.     {
    30.         updateThread = true;
    31.     }
    32.    
    33.     private bool runThread = true;
    34.     private bool updateThread;
    35.     private void ThreadUpdate()
    36.     {
    37.         while (runThread)
    38.         {
    39.             if (updateThread)
    40.             {
    41.                 updateThread = false;
    42.            
    43.                 currentPos += 1f; //some crazy ass function that takes forever to do here
    44.             }
    45.         }  
    46.     }
    47.    
    48.     private void OnApplicationQuit()
    49.     {
    50.         EndThreads();  
    51.     }
    52.    
    53.     //This must be called from OnApplicationQuit AND before the loading of a new level.
    54.     //Threads spawned from this class must be ended when this class is destroyed in level changes.
    55.     public void EndThreads()
    56.     {
    57.         runThread = false;
    58.         //you could use thread.abort() but that has issues on iOS
    59.        
    60.         while (thread.IsAlive())
    61.         {
    62.             //simply have main loop wait till thread ends
    63.         }
    64.     }
    65.  
    66. }
    Then you just have to really think about how your laying out your functions.

    simple types are Atomic, but processes are not atomic

    So for example, lets assume globalFloatArray is something accessed by many different threads:

    This simple line IS atomic:
    float myVar = globalFloatArray[10];

    However this is not:
    if ( globalFloatArray[10] != null )
    float localFloat = globalFloatArray[10];
    //use localFloat in operation

    Because between doing the null check on floatArray[10] and actually attempting to read out the variable, it is possible that floatArray[10] could have been set to null by another thread.

    The standard C# way you to deal with this, if you search google, you will come up with something like this:

    lock(lockObject)
    {
    if ( globalFloatArray[10] != null )
    float localFloat = globalFloatArray[10];
    //do your stuff with local float
    }

    Which is atomic. However I found you must absolutely avoid locking variables accessed from the main thread. If your alternate thread locks a variable, then your main thread tries to access it during the lock, your main thread will wait until it is unlocked. This will cause frame stutter. There is serious overhead to locking a variable, it does not happen quickly. It should be avoided.

    What can be done is you can program in a way that you never need to do anything but simple reads and writes to simple types.

    So in the example above you could do something like this

    float localFloat = globalFloatArray[10]
    if (localFloat != null)
    //do your stuff with localFloat
    globalFloatArray[10] = localFloat;

    This is atomic, accomplishes the same thing essentially, and doesn't use locks. Multiple threads can all be reading in and out of globalFloatArray at a full 60 fps in this manner. But you have to think about how to set that up so that it is atomic without locks.

    The game in my profile, enrgy, uses this base design pattern. It seems to run without any issues on Windows, OSX and iOS.

    However when you get into it, you may find that not everything can be adapted to this type of pattern very easily. Some things may be impossible. It also must be kept in mind that the thread updating your transform position is out of sync with your update thread. So ideally you alternate thread is executing faster than your Update loop, and for each cycle of the Update loop, your alternate thread will have done something to make things move. But it could be possible depending on how much code you put in your alternate thread that your alternate thread executes slower than your main update loop. At which point your alternate thread may only be outputting a new position only 15 times a second, while your main update loop is updating 60 times a second. You will have to interpolate the output from your alternate thread.

    enrgy has two alternate threads running. One which goes faster than the update loop, and one that goes about 1/4 the speed of the update loop. The thread that goes 1/4 the speed is basically the path finding algorithm, it is a very intense calculation, it uses up the entire second CPU on the iPad, and it runs at full speed with no time limits or pauses, constantly updating the path finding grid based on user input. The second alternate thread moves all the particles, this one can run twice the speed of Update loop, and this one is time constrained to go at a certain rate. This thread smoothly moves the particles along the grid that the path finding algorithm laid out, and also does all the game logic of having one particle attack another particle, or test if a particle is hitting anything.

    The main thread and Update loop then reads all these particle positions out of a huge global array, clumps together nearby particles to be represented by a single larger particle, then uploads them into a ParticleSystem for display.

    Runs reliably as far as I can tell.
     
    Last edited: Aug 8, 2013
  14. Raigex

    Raigex

    Joined:
    Jul 6, 2012
    Posts:
    154
    @techmage
    Your hack/solution to the multithreading problem blew my mind. I had thoughts on such things before but never could figure out how to work against the if(null == object) problems. Your solutions is genius
     
  15. hippocoder

    hippocoder

    Digital Ape

    Joined:
    Apr 11, 2010
    Posts:
    29,723
    Techmage, is there any reason to use FixedUpdate at all when you can use InvokeRepeating ? just curious!
     
  16. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    Nope.

    I just used FixedUpdate cause it was there and I wasn't using it. InvokeRepeating would probably better.

    Even better than that is figuring out some timing mechanism that doesn't rely on Unity's main loop at all. I am sure it is possible and not too diffucult, I just haven't needed it so I haven't gone through the process of figuring it out. You would need to use some kind of built in .NET timer that is thread safe, because obviously you can't call Time.time from a seperate thread.
     
  17. makeshiftwings

    makeshiftwings

    Joined:
    May 28, 2011
    Posts:
    3,350
    That's not atomic and definitely doesn't accomplish the same thing. Each individual line is atomic itself but the block as a whole, and the globalArray, is not atomic. You would need to use a lock if access to the array was supposed to be atomic. For instance, if you had two threads running versions of that code with different things in the "do stuff" section like the first moves a position by an inch and the second thread moves it by a mile, you could get thread A grabbing a local value of 1, then B grabbing a local value of 1, and B making a very important change to 5000 and exiting, and then A ignoring that important change and overwriting it with 1.1. And your important change that was supposed to teleport the enemy 5000 feet away gets lost and it only moves an inch.

    If you don't actually need to make sure that each thread is accessing the same values, then it doesn't actually need to be atomic, and you can use the code you wrote. But I wouldn't call the code you wrote atomic just because it doesn't have locks, as that's not what the word means.
     
  18. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    I guess calling it atomic is too much. It does require a very specific use to work properly.

    What I describe is not very generic or open, it does require alot of very specific assumptions.
     
  19. superpig

    superpig

    Drink more water! Unity Technologies

    Joined:
    Jan 16, 2011
    Posts:
    4,649
    WRT the fixedupdate thing: Can't you just use .NET threadpool threads? ThreadPool.QueueUserWorkItem and so on.
     
  20. bpears

    bpears

    Joined:
    Aug 23, 2012
    Posts:
    249
    Wow, such an interesting thread.. It's over my head for now but, interesting! So essentially, this is how to access more cores, on muticore CPU's?
     
  21. Tim-C

    Tim-C

    Unity Technologies

    Joined:
    Feb 6, 2010
    Posts:
    2,221
    So with the unity codebase these functions call into native code, we automagically make sure that any function that does this is not allowed to be called from a non main mono thread (we are taking the safe route).

    We can (and do!) mark functions up on a per function level as threadsafe. If there is a utility function or simple function that does not interact with object lifetime we have NOT marked up as thread safe please raise a bug. We will take a look, and if it is safe, mark it as such and you will be able to use it.
     
  22. Gigiwoo

    Gigiwoo

    Joined:
    Mar 16, 2011
    Posts:
    2,981
    @techmage - Thanks much for your detailed explanation! I learned something very useful. Gracias.

    Gigi
     
  23. im

    im

    Joined:
    Jan 17, 2013
    Posts:
    1,408
    check this out
    5.5 Atomicity of variable references

    http://msdn.microsoft.com/en-us/library/aa691278(VS.71).aspx

    still good to use volatile

    http://msdn.microsoft.com/en-us/library/x13ttww7.aspx

    and lock when needed

    http://msdn.microsoft.com/en-us/library/c5kehkcz.aspx

    personally i was thinking of having game logic on different threads. basically the unity thread doing the rendering. it just goes and gets it from like cached results of the game logic thread i could have to result sets one with values that the unity thread will use containing the last valid results and a second buffer where the game logic thread is generating results for. when the game logic thread finishes a pass it just switch the reference that the unity thread will use to reference the result buffer. when that happens the work buffer is the old result buffer and the old result buffer becomes the work buffer and it keep doing this while game is running flag is true. that way 0 time of unity thread id dedicated to calculations. i can have lots of these kinds of buffers for other threads i may need so that the game logic can be broken up as much as possible.
     
    Last edited: Sep 6, 2013
  24. minionnz

    minionnz

    Joined:
    Jan 29, 2013
    Posts:
    391
  25. im

    im

    Joined:
    Jan 17, 2013
    Posts:
    1,408
    loom is one of the things on my list of many thing to look more into

    there is also unity threading helper
    http://forum.unity3d.com/threads/90128-Unity-Threading-Helper

    still .net out of the box supports very easy to use native threading


    i mean how much easier than this (borrowed form techmage to show a point)

    Code (csharp):
    1.  
    2.     using UnityEngine;
    3.     using System.Collections;
    4.     using System.Threading;
    5.      
    6.     public class ThreadedGameLogicObject : MonoBehaviour
    7.     {
    8.         private Thread thread;
    9.         private Transform thisTransform;
    10.         private Vector3 currentPos;
    11.        
    12.         private void Start()
    13.         {
    14.             thisTransform = this.transform;
    15.            
    16.             thread = new Thread(ThreadUpdate);
    17.             thread.Start();
    18.  
    19.             InvokeRepeating("UpdateTick", 0f, .1f);
    20.         }
    21.        
    22.         public void UpdateTick()
    23.         {
    24.             updateThread = true;
    25.         }
    26.        
    27.         private void Update()
    28.         {
    29.             thisTransform.position = currentPos;
    30.         }
    31.        
    32.         private bool runThread = true;
    33.         private bool updateThread;
    34.         private void ThreadUpdate()
    35.         {
    36.             while (runThread)
    37.             {
    38.                 if (updateThread)
    39.                 {
    40.                     updateThread = false;
    41.                
    42.                     currentPos += 1f; //some crazy ass function that takes forever to do here
    43.                 }
    44.             }  
    45.         }
    46.        
    47.         private void OnApplicationQuit()
    48.         {
    49.             EndThreads();  
    50.         }
    51.        
    52.         //This must be called from OnApplicationQuit AND before the loading of a new level.
    53.         //Threads spawned from this class must be ended when this class is destroyed in level changes.
    54.         public void EndThreads()
    55.         {
    56.             runThread = false;
    57.             //you could use thread.abort() but that has issues on iOS
    58.            
    59.             while (thread.IsAlive())
    60.             {
    61.                 //simply have main loop wait till thread ends
    62.             }
    63.         }
    64.      
    65.     }
    66.  
     
    Last edited: Sep 7, 2013
  26. minionnz

    minionnz

    Joined:
    Jan 29, 2013
    Posts:
    391
    That actually looks pretty useful - looks like a little more plumbing than those extensions, but that could be abstracted away to a base class (eg: Moved to ThreadedGameObject class):

    Code (csharp):
    1.  
    2.  
    3. public class MyGameObject : ThreadedGameObject {
    4.  
    5.         private Vector3 v = Vector3.zero;
    6.  
    7.     // Use this for initialization
    8.     void Start () {
    9.  
    10.            this.Threaded(ThreadedUpdate);
    11.            this.Threaded(BackgroundLogic, 0f, 2f);
    12.        
    13.     }
    14.        
    15.         void Update() {
    16.                this.transform.position = v;
    17.         }
    18.     // ThreadedUpdate is called once per frame
    19.     void ThreadedUpdate()
    20.     {
    21.         // Possibly use a "Set" extension method that allows us to get/set properties safely between threads?
    22.         this.transform.Set(t => t.position, Vector3.zero);
    23.     }
    24.    
    25.  
    26.     // BackgroundLogic is called once every 2 seconds
    27.     void BackgroundLogic()
    28.     {
    29.           v = Vector3.zero;
    30.     }
    31. }
    32.  
     
    Last edited: Sep 7, 2013
  27. im

    im

    Joined:
    Jan 17, 2013
    Posts:
    1,408
    yes abstracting it would be better

    im not sure suff like this is thread safe

    Code (csharp):
    1.  
    2.   void ThreadedUpdate()
    3.  
    4.     {
    5.  
    6.         // Possibly use a "Set" extension method that allows us to get/set properties safely between threads?
    7.  
    8.         this.transform.Set(t => t.position, Vector3.zero);
    9.  
    10.     }
    11.  
    12.  
    13.  
    14.     void BackgroundLogic()
    15.  
    16.     {
    17.  
    18.           v = Vector3.zero;
    19.  
    20.     }
    21.  
    it may need to be in lock {} or lock(v) lock(transform) and then we dont know if unity is doing lock on transform. so it may need to be volatile or copied to safetransform and that one locked and copies from transform by code running on unity thread
     
  28. techmage

    techmage

    Joined:
    Oct 31, 2009
    Posts:
    2,133
    That is going to get nutty.

    The thing about Vector3 values, as I understand is that the singular floats are atomic. The entire Vector3 isn't.

    So you could end up with one thread trying to set X Y Z, but another thread doing it at the same exact time ending up in one thread setting X Y and the other thread setting Z, leaving you with an unpredicted Vector3.

    Now this can be OK if, your game can function in the chance some values of the X and Y are set in one thread and Z in another.

    So if your two threads are making minor movements to the Vector3, each one being like .001f or .1f of movement, so then them swapping back and forth could be ok. The correct value kind of gets averaged out in the rapid updating from two threads, and one thread doesn't make such a radical movement to it to make it look off.

    Like say you have one thread going

    transform.position += new Vector3(.1f,.01f,.2f);

    then the other thread going

    transform.position += new Vector3(.01f,.2f,0f);

    And these two threads are going at it full speed, sometimes after one Update cycle some of those variables will have been updated by one thread, the other by the other thread. The movement of the object will generally look correct, the same as if they had been locked because the values are small and update so fast they will sort of average out.

    Now you COULD lock them, but I found it necessary to avoid locking in the Update thread, it will make your game hiccup. Maybe a few locks are ok, I don't remember if I could get away with just one, its been too long since I did these tests to really remember. But I did notice they are a very expensive call to make, and even if you are using just one, it is still an expensive call and ideal to avoid it.

    In instances where you would want to lock something you could do something like this

    Vector3 position;
    bool thread1Set = true;
    bool thread2Set = false;

    void Thread1()
    {

    if (thread1set == false)
    }
    position += 1;
    thread1set = true;
    thread2set = false;
    }

    }

    void Thread2()
    {

    if (thread2set == false)
    }
    position += 1;
    thread2set = true;
    thread1set = false;
    }

    }

    One thread will only set it's value after the other thread. They will jump back and forth.

    However that still has the issue that your Update look may grab the position Vector halfway through it being altered by another thread. To get around that you'd probably have to do something like this:


    Vector3 position;
    bool thread1Set = false;
    bool thread2Set = true;
    bool thread1UpdateReady;
    bool thread2UpdateReady;

    void Thread1()
    {

    if (thread1set == false thread1UpdateReady == false)
    }
    position += 1;
    thread1set = true;
    thread2set = false;
    thread1UpdateReady = true;
    }

    }

    void Thread2()
    {

    if (thread2set == false thread2UpdateReady == false)
    }
    position += 1;
    thread2set = true;
    thread1set = false;
    thread2UpdateReady = true;
    }

    }


    void Update
    {

    if (thread1UpdateReady thread2UpdateReady)
    {
    thisTransform.position = position;
    thread1UpdateReady = false;
    thread2UpdateReady = false;
    }

    }

    This will make it so that the Update will grab the position after the other two threads have done their task, and the other two threads will wait until the Update loop does this.

    You want to set it up so that your Update loop is not waiting on your threads, but rather your threads are waiting on your Update loop.

    You also have to take into account that your update loop will not grab a new position vector 3 every update... You will have to interpolate it. Which means this still won't be as predictable as just doing something straight in the Update loop. Patterns like this are really only ideal when what your moving can have a bit of unpredictability to it.
     
    Last edited: Sep 8, 2013