Godot 4: How many usable threads? Does Thread.start() always return OK?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By chimeforest

Intro
I’m working on a piece of image processing software, and I’d like to implement some multi threading. I’ve already got the processing on one thread, and the UI updates on another so that the whole thing doesn’t freeze when processing an image. However, I’d like to split the processing thread up even further so it can work on multiple parts of the image at the same time.

Main
In order to do that, I wanted to see how many threads I could use. OS.get_processor_count() returns how many threads the system can support, but surely some of those might be in use by other programs? So I wrote a little script to see how many threads I could access at once.

func _ready():
	get_usable_threads()

func get_usable_threads():
	print("%s - processor count %s"%[Time.get_ticks_msec(),OS.get_processor_count()])    
	var i = 0
	var thrds = []
	while i < 100:
		thrds.append(Thread.new())
		var er = thrds[thrds.size()-1].start(do_thing)
		if er != OK:
			break
		i += 1
	print("%s - useable threads: %s"%[Time.get_ticks_msec(),i])
	return i

func do_thing():
    #old do_thing: wait 100 secs
    #OS.delay_msec(100000)
    #new do_thing: get prime numbers in a long/stupid way to eat up time/pocessing power
    var MAX = 5000
    var prime_numbers = [1]
    for i in range(2,MAX+1):
            var p = true
            for ii in range(i-1,1,-1):
                    if i%ii == 0:
                            p = false
                            break
            if p:
                    prime_numbers.append(i)
    print("%s - Ended: %s"%[Time.get_ticks_msec(),OS.get_thread_caller_id()])

It makes an empty array, adds a thread to the array, asks the thread to do a task, then adds +1 to the thread count. It does this until Thread.new() does not return OK.

But it always returns OK.
ALWAYS
OS.get_processor_count() returns 8 on my system, but I’ve run this code up to 500 threads, and it makes them. My computer slows to a crawl, basically unusable, and Godot will still make new threads.

The Questions

  • How can I find out how many threads I can/should use without slowing the system?
  • Can I just use OS.get_processor_count()? Should I use less to make sure I’m not going to bog down the system?
  • Does Thread.start() always return OK? The documentation says it should return ERR_CANT_CREATE if it can’t make a new thread, but I’ve asked it to make 100,000 threads(with the old do_thing) and it always returned OK. Ask for 1,000,000 though and my computer freezes and crashes… presumable because Godot always said OK to making more threads.
:bust_in_silhouette: Reply From: Wakatta

Confusion

You are vastly misunderstanding some key concepts here
You won’t hit a limit because Thread is an abstract concept and not a physical thing like a processor.

CPU -has-> CORES -has-> THREADS
OS.get_processor_count() returns the cores of a dual core processor or the physical amount of processors in a system (there are systems with literally 2 CPU’s)

Most modern processors will have aproxx. 2 threads per core (so in your 8 core system it would be 16 threads)

Since each application represents a thread on execution does that mean you can only run 16 apps?
That was rhetorical because the answer is NO.

Could go further into how context switching works or how the OS manages a thread pool with processes but will give you that as homework (remember to show your working)

Explanation

The key thing to know is thread management is handled by the Operating System and are assigned by priority while some may be set as waiting or inactive.
The problem with your above example is you run all threads concurrently when you really should only have as much as your cores active at any given time (half the total amount)

Tips

As stated above

  • In an active process have as much active threads as there are cores
  • In a background one try to limit to 1 or half the amount of processor cores
  • Its best to have a thread cycle
    Thread A -starts-> Thread B -starts-> Thread C -starts-> Thread D.
    Thread D finishes and encourages Thread C to finish which encourages Thread B to finish which encourages Thread A to finish

Thank you for your detailed answer, and your advice on how many threads I should be using. I do appreciate that. And I will have to look more into thread cycles and how threads can “encourage” one another to finish, sounds interesting.

However, you are incorrect about OS.get_processor_count(). It does not return the number of physical cores, but the number of logical cores(aka “threads”), as stated in the documentation and based on my own observations this is correct. My computer has 4 physical cores, and godot says I have 8 logical cores.

I do understand the concept of context switching, it’s computer hardware 101. A computer can do multiple things by switching between processes really fast.

I think what is mostly confusing me is why the documentation says that it will return an error if it can’t create a thread, but it NEVER returns that error, even if it means crashing the computer. In what scenario would it return that error?

Obviously in my actual program I’m not going to be running 1000s of threads at the same time, so it’s not that big of a deal atm. This was more of a stress test to better understand what I’m dealing with.

chimeforest | 2023-01-13 16:54

Odd for me only the actual CPU cores are returned by OS.get_processor_count()
Maybe need to activate CPU Virtualization in the BIOS or something.

I think what is mostly confusing me is why the documentation says that
it will return an error if it can’t create a thread

This is purely speculation but that may have to-do with the initialization of the Thread and not the execution. For example lets say the calling object gets freed or the userdata type does not match the input or there is an error in the function executed by the Thread

Wakatta | 2023-01-14 06:43