Multi-thread problem

Godot Version

4.4.1 stable

Question

I’m trying to make a simple example using multithread, but I do not get what I expected.
Goal : I have 3 labels on screen and 1 button
Label0 is displaying an incrementing number.
If you click on the button,
Label1 and Label2 are also showing incrementing numbers, but as they are managed by two separate threads, I expect all of these numbers to increment in parallel.
And that’s not what I get…
First Label0 increments, but as soon as I click the button, everything freezes and at the end of thread1 and thread2, display is refreshed and I see the final numbers.
Note that if I put a greater ending number (like 10000000), the program crashes…
What am I doing wrong ?

here is the code :

extends Node2D

var thread1: Thread
var thread2: Thread

@onready var label_0: Label = $Label0
@onready var label_1: Label = $Label1
@onready var label_2: Label = $Label2

var nb: int = 0

func _ready() -> void:
	thread1 = Thread.new()
	thread2 = Thread.new()

func _process(_delta: float) -> void:
	label_0.text = str(nb)
	nb += 1

func _on_button_button_down() -> void:
	thread1.start(count.bind(200000, label_1))
	thread2.start(count.bind(200000, label_2))

func count(n: int, label: Label) -> void:
	for i in range(n):
		var val: float = float(i)/100.0
		var _res = sin(val)+cos(val)+tan(val)
		set_label.call_deferred(label, i)

func set_label(label: Label, i: int) -> void:
	label.text = str(i)
	
func _exit_tree():
	thread1.wait_to_finish()
	thread2.wait_to_finish()

The problem is that your threads dont sleep, they just run as fast as possible. So there will be 200000 x2 deferred calls to process in the next frame. Which is why you are experiencing the freeze on the main thread.

2 Likes

thanks.
For those who wants to know, my final working example is like this :

extends Node2D

var thread1: Thread
var thread2: Thread

@onready var label_0: Label = $Label0
@onready var label_1: Label = $Label1
@onready var label_2: Label = $Label2
@onready var label_info: Label = $LabelInfo

var nb0: int = 0
var nb1: int = 0
var nb2: int = 0
var nbmax: int = 0
var nbmaxinc: int = 10000

func _ready() -> void:
	thread1 = Thread.new()
	thread2 = Thread.new()

func _process(_delta: float) -> void:
	label_0.text = str(nb0)
	label_1.text = str(nb1)
	label_2.text = str(nb2)
	label_info.text = str(thread1.is_alive()) + "/" + str(thread2.is_alive()) + "/" + str(nbmax)
	nb0 += 1

func _on_button_button_down() -> void:
	var _err: Error
	nbmax += nbmaxinc
	
	if not thread1.is_alive():
		if thread1.is_started():
			thread1.wait_to_finish()
		_err = thread1.start(count.bind(1))
		
	if not thread2.is_alive():
		if thread2.is_started():
			thread2.wait_to_finish()
		_err = thread2.start(count.bind(2))

func count(_n: int) -> void:
	match _n:
		1:
			while nb1 < nbmax:
				do_calculation()
				nb1 += 1
		2:
			while nb2 < nbmax:
				do_calculation()
				nb2 += 1	
				
func do_calculation():
	# simulation of heavy calculation
	for i in 1000:
		var val: float = float(i)/100.0
		var _res = sin(val)+cos(val)+tan(val)	

func _exit_tree():
	nb1 = nbmax
	nb2 = nbmax
	
	if thread1.is_started():
		thread1.wait_to_finish()
	if thread2.is_started():
		thread2.wait_to_finish()
1 Like

I would have done something like

Sleep for a little bit of time before going to the next loop iteration.

func count(n: int, label: Label) -> void:
	for i in range(n):
		var val: float = float(i)/100.0
		var _res = sin(val)+cos(val)+tan(val)
		set_label.call_deferred(set_label, i)
		OS.delay_ms(1.0/60.0)

Or

Call deferred after the loop is finished

func count(n: int, label: Label) -> void:
	var result = 0
	for i in range(n):
		var val: float = float(i)/100.0
		result = sin(val)+cos(val)+tan(val)
	set_label.call_deferred(set_label, result)

2 Likes

The fact is that I do not want these threads to sleep : I wanted to test two heavy calculations in parallel of the main game thread, with no pause, but tracking the progression of these background calculations.
I just wonder if they are really executed on separated CPU / cores… It’s not very clear when I display the computer CPU load.

That’s up to the OS’s process scheduler, assuming (as I believe it is…) Godot is using OS threads.

You should be able to get a per-core view of your CPU load. If you do that and only one core is lit up, that’ll be your answer.

Oh yes that’s what I do, but the graphics are not very clear. 3 or 4 cores from my 6 cores seems to activate, which is weird…

Are you on linux? You might try looking at htop or maybe powertop.

no : windows 10 x64

3 or 4 threads active sounds correct, one for Godot’s main thread, your two threads. Maybe a fourth for physics or more likely your OS changing the process’ core.

1 Like