In the first model I wait until the previous thread has ended, before I start a new one.
In the second model I start the thread only once in start() which keeps alive with a while(true) loop.
I could test both scenarios without any problems, despite I figured out that running a while(true) loop in a thread won’t end the thread when stopping the unity editor player
I personally would prefer one clean thread with a loop but I am not sure about the while loop killing, I thought about a timeout check, when no request comes in after x seconds then KTHXBYE
And yes, the thread is always doing the same thing, just calculating integer values.
So I am curious what model is less hungry. What u guys think ?
Running the thread once and kept alive with a while loop produced about double cpu load. Interesting: FPS went up about 16%, as my logic steps per second running in the thread went down about 50%.
Just to make it clear: I did NOT run the code in the while loop directly without request ^^. Operations only were done upon request (from update)
So running my logic thread by thread directly in Update() is, as it seems, much cheaper for the cpu, at little cost for the overall fps.
I typically have a while (IsAlive) loop for my threads. Then, when I want the thread to end safely, I just change IsAlive to false, and it will end after the next cycle. I also have error handling to catch Thread.Interrupts and Thread.Abort.
Since you haven’t provided any actual code, I can’t guess why your CPU load is messed up, but I can tell you that I regularly run multiple threads, and find a huge CPU benefit. If you’re not getting a benefit, then odds are you’re doing something wrong. I’d profile your code and find out what your bottleneck is.
I’m not sure exactly what you’re attempting to do… but it sounds like you’re trying to have a thread that turns on and off based on conditions. Like maybe it’s AI and it only runs certain code when the player is near, so you’re testing for when it gets close, start up the thread, and when it leaves, you stop the thread.
Or do it with a while loop in the other option… like a while(playerNear) { … do work } type scenario?
A while loop is going to be expensive because it’s constantly performing work to check if the while loop should continue.
Instead one usually puts a thread to sleep until some signal.
This can be done with a WaitHandle… such as the EventWaitHandle:
It’d be something like this:
public class SomeScript
{
private bool _doWork;
private Thread _workThread;
private EventWaitHandle _handle = new EventWaitHandle(false, EventResetMode.ManualReset);
private int _work;
void OnEnable()
{
_workThread = new Thread(this.WorkerThread);
this.DoWork = _doWork; //set our state
_workThread.Start();
}
void OnDisable()
{
_handle.Set();
_workThread = null; //make sure the thread is cleaned up in case of the game closing
}
public bool DoWork
{
get
{
return _handle.;
}
set
{
_doWork = value;
if(_doWork)
{
_handle.Set();
}
else
{
_handle.Reset();
}
}
}
private void WorkerThread()
{
while(this.enabled)
{
_handle.WaitOne();
_work++;
Thread.Sleep(1000); //simulate work
Debug.Log(string.Format("Did work: {0}", _work));
}
}
}
(note - not fully tested, just slapped together in notepad, could contain bugs)
No idea if it’s more efficient than starting new threads each time with the Update check that you referred to.
Honestly though… I’m willing to bet starting a new thread each time is probably better though since we don’t know how often this might occur. I don’t know though, I don’t know your use case. Creating new threads often because it goes between working and not frequently may be more costly… where as if it goes into and out of work rarely, then keep a thread sleeping for that time is a bit wasteful as well.
Creating threads is generally considered to be a fairly expensive operation. You might want to look into System.Threading.ThreadPool. Or keep a single thread around and wait using the technique @lordofduct describes.
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
namespace Assets.Scripts.Dameon.Threading
{
/// <summary>
/// This should supply all the needed functionality for data-crunching delegates to tie into.
/// </summary>
public class BaseThreadWorker : IThreadWorker
{
private MonoThread _parent;
/// <summary>
/// A list of delegates to be called each processing cycle.
/// </summary>
public IList<Action> ThreadProcessors { get; set; }
/// <summary>
/// Is this thread currently active?
/// Setting this to false should be a safe way to stop the worker.
/// </summary>
public bool IsActive { get; set; }
/// <summary>
/// Is this worker waiting for permission to continue to the next processing cycle?
/// Set this to false to give the worker permission to continue.
/// </summary>
public bool IsThreadWaiting { get; set; }
public BaseThreadWorker(MonoThread parent)
{
_parent = parent;
ThreadProcessors = new List<Action>();
}
/// <summary>
/// This is the main cycle for the worker.
/// Should be supplied as an argument when creating a thread.
/// var thread = new Thread(RunThread)
/// </summary>
public virtual void RunThread()
{
IsActive = true;
while (IsActive)
{
try
{
var enumerator = ThreadProcessors.GetEnumerator();
while (enumerator.MoveNext())
{
enumerator.Current.Invoke();
}
}
catch (ThreadAbortException)
{
IsActive = false;
}
catch (Exception e)
{
Debug.LogException(e);
}
try
{
IsThreadWaiting = true;
while (IsActive && IsThreadWaiting)
Thread.Sleep(16);
}
catch (ThreadInterruptedException)
{
IsThreadWaiting = false;
}
catch (ThreadAbortException)
{
IsActive = false;
}
}
}
}
}
Then I use a manager class to track workers, create appropriate amounts of threads, etc…
using System;
using System.Collections.Generic;
using System.Threading;
using UnityEngine;
using UnityEngine.Profiling;
namespace Assets.Scripts.Dameon.Threading
{
/// <summary>
/// Creates worker threads and monitors them, restarting as necessary.
/// </summary>
public class MonoThread : MonoBehaviour
{
[SerializeField]
private float _workerTimeout = 1000;
private IList<WorkerStatus> _workers;
private float _activeTime;
private int _maxThreads;
private void Awake()
{
_maxThreads = Environment.ProcessorCount;
if (_maxThreads < 1)
_maxThreads = 1;
_workers = new List<WorkerStatus>(_maxThreads);
}
private void Update()
{
var enumerator = _workers.GetEnumerator();
var readyWorkers = 0;
while (enumerator.MoveNext())
{
if (enumerator.Current.Worker.IsThreadWaiting)
{
enumerator.Current.LastWorkerResponseTime = Time.time;
readyWorkers++;
}
if (Time.time - enumerator.Current.LastWorkerResponseTime > _workerTimeout)
{
enumerator.Current.WorkerThread.Abort();
enumerator.Current.WorkerThread = new Thread(enumerator.Current.Worker.RunThread);
enumerator.Current.WorkerThread.Start();
enumerator.Current.LastWorkerResponseTime = Time.time;
}
}
if ((_workers.Count > 0) && (readyWorkers == _workers.Count))
{
InterruptWorkers();
readyWorkers = 0;
}
}
/// <summary>
/// Creates workers and begins them processing a supplied list of callbacks.
/// </summary>
/// <param name="Callbacks">A list of delegates that accept no parameters, which will be processed.</param>
public void StartWorkers(IList<Action> Callbacks)
{
for (var i = 0; i < _maxThreads && i < Callbacks.Count; i++)
{
var workerStatus = new WorkerStatus();
workerStatus.Worker = new BaseThreadWorker(this);
workerStatus.WorkerThread = new Thread(workerStatus.Worker.RunThread);
_workers.Add(workerStatus);
}
int currentThread = 0;
var enumerator = Callbacks.GetEnumerator();
while (enumerator.MoveNext())
{
_workers[currentThread].Worker.ThreadProcessors.Add(enumerator.Current);
currentThread++;
if (currentThread >= _maxThreads)
currentThread = 0;
}
var threadEnumerator = _workers.GetEnumerator();
while (threadEnumerator.MoveNext())
{
threadEnumerator.Current.WorkerThread.Start();
}
}
/// <summary>
/// Stops workers after they have finished their current processing cycle.
/// </summary>
public void StopWorkersSafely()
{
var enumerator = _workers.GetEnumerator();
while (enumerator.MoveNext())
{
enumerator.Current.Worker.IsActive = false;
}
_workers.Clear();
}
/// <summary>
/// Sends a System.ThreadAbortException to the thread associated with each worker.
/// </summary>
public void AbortWorkers()
{
var threadEnumerator = _workers.GetEnumerator();
while (threadEnumerator.MoveNext())
{
Debug.Log("Aborting thread...");
threadEnumerator.Current.WorkerThread.Abort();
}
_workers.Clear();
}
/// <summary>
/// Tells each thread to continue its processing. Setting ThreadWaiting to false should do this.
/// </summary>
public void InterruptWorkers()
{
var enumerator = _workers.GetEnumerator();
while (enumerator.MoveNext())
{
enumerator.Current.LastWorkerResponseTime = Time.time;
enumerator.Current.Worker.IsThreadWaiting = false;
}
}
private class WorkerStatus
{
public IThreadWorker Worker { get; set; }
public Thread WorkerThread { get; set; }
public float LastWorkerResponseTime { get; set; }
}
private void OnDisable()
{
StopWorkersSafely();
}
private void OnDestroy()
{
AbortWorkers();
}
// Just to be really sure. We don't want to leave any threads laying around.
~MonoThread()
{
AbortWorkers();
}
}
}
Note that these are older versions than my current…there’s some more advanced error handling in my current iteration, and a few bugs fixed. These are just guides.