Can Coroutines(await) work with Mutexes?

Godot Version

4.3

Question

I was wondering if Mutex works with coroutines?
Are coroutines considered threads?

I’m using quite a few coroutines.
And I wanted to do an optimization that would require me to change a shared index.
Worst case, I could change it to use threads instead. But coroutines are much easier to create on-the-fly. So for simple systems, I prefer to use them.

Here’s a an example.

func _ready():
	for i in 5:
		start_coroutine()


func start_coroutine():
	print("Started coroutine")
	await get_tree().create_timer(randf_range(1.0, 2.0)).timeout
	# Some code
	# Changes some index. Critical area
	# Some code
	print("Coroutine done")

coroutines are not threads, mutexes do work with coroutines but since it’s not a separate thread there is little point.

I’ve found some interesting discoveries.

It seems mutexes mostly work inside coroutines.

But if you use an await inside of a locked area, it’ll break the mutex. Basically, it seems using await inside thread space can lead to inconsistent behavior.

Trying to use an await inside of a thread can sometimes give you the following error (more info on this at the end)

E 0:00:00:0712 coroutines.gd:22 @ start_coroutine(): Class 'GDScriptFunctionState' already exists. <C++ Error> Condition "classes.has(name)" is true. <C++ Source> core/object/class_db.cpp:726 @ _add_class2() <Stack Trace> coroutines.gd:22 @ start_coroutine()

Now, about the coroutines and mutexes.
Here is the latestest testcode I was using:

extends Node2D

var mutex: Mutex
var thread: Thread
var thread2: Thread
var thread3: Thread

func _ready():
	mutex = Mutex.new()
	#thread = Thread.new()
	#thread.start(start_coroutine.bind(1))
	#thread2 = Thread.new()
	#thread2.start(start_coroutine.bind(2))
	#thread3 = Thread.new()
	#thread3.start(start_coroutine.bind(3))
	for i in 5:
		start_coroutine(i)


func start_coroutine(id):
	print("Started coroutine")
	await get_tree().create_timer(randf_range(0.5, 1.0)).timeout
	# Some code
	# Changes some index. Critical area
	mutex.lock()

	print("Critical area... ", id)
	for i in randi_range(100000000,100000000):
		pass
	#await get_tree().create_timer(randf_range(0.1, 0.5)).timeout
	print("End critical area... ", id)

	mutex.unlock()
	# Some code
	print("Coroutine done")

Good results. Mutex is working:

Started coroutine
Started coroutine
Started coroutine
Started coroutine
Started coroutine
Critical area... 1
End critical area... 1
Coroutine done
Critical area... 0
End critical area... 0
Coroutine done
Critical area... 4
End critical area... 4
Coroutine done
Critical area... 2
End critical area... 2
Coroutine done
Critical area... 3
End critical area... 3
Coroutine done

But if you un-comment the await inside the start_coroutine the mutex simply stops working, and all goes crazy.

Results:

Started coroutine
Started coroutine
Started coroutine
Started coroutine
Started coroutine
Critical area... 1
End critical area... 1
Coroutine done
Critical area... 2
Critical area... 0
Critical area... 3
Critical area... 4
End critical area... 2
Coroutine done
End critical area... 0
Coroutine done
End critical area... 3
Coroutine done
End critical area... 4
Coroutine done

Also, using the threads with the await inside can lead to inconsistent errors.
Sometimes it happens, sometimes it doesn’t.

I was using a randf_range() to create a timer, so maybe when it tries to create two timers with the same value, at the same time, it gives this error. I’m not sure.

1 Like