|
|
|
 |
Reply From: |
omggomb |
I’m not sure but think it’s because of using the scene tree for the timer. The docs say it’s not thread safe and you should use call_deferred()
to interact with it. Link to the docs.
If I create a semaphore for each thread so that I can use the main thread to control their waiting and replace the yield
after “Eating…” with the following:
# During thread creation
var data = {
#...
semaphore: Semaphore.new()
}
#After eating
call_deferred("_wait_for_timer", data["semaphore"])
data["semaphore"].wait()
#...
func _wait_for_timer(semaphore):
yield(get_tree().create_timer(9), "timeout")
semaphore.post()
it seems to work. But I’m not sure where the culprit lies.
Thank you for the reply! I’ll definitely crack into this when I have some time and see what I can come up with using your suggestion. Could you either comment or add the documentation link where it says that? I’ve been looking at the documentation and seemed to have missed that… so I’d love to know where it is.
navett52 | 2020-04-10 16:29
I’m trying to work in your idea, but I’m a bit confused. Are you replacing the mutex with a semaphore, or are you adding in a semaphore in addition to the mutex?
navett52 | 2020-04-10 23:03
EDIT: I’m not sure anymore whether call_deferred
actually executes stuff on the main thread, but the solution is still correct. I’ll see if I can find an answer to that.
EDIT 2: The page about servers sort of hints to that by mentioning a MessageQueue
. This is what call_deferred
internally uses, so I figure my assumption is correct, since it mentions that the queue is flushed by the scene tree during idle time, and the scene tree most likely runs on the main thread. Relevant link about servers.
Sorry for not adding the doc link, I was lazy :p. I edited my answer to include it.
The semaphore is aditionally to the mutex. But keep in mind that this whole thing is just to simulate work being done, so the real world use of this setup depends. Here is the entire relevant code with some more comments, though I left out the try_lock
line:
func SpawnThread():
var thread = Thread.new()
var data = {
"thread": thread,
"lock": mutex,
"sem": Semaphore.new() # One semaphore for each thread
}
thread.start(self, "threadFunction", data)
func threadFunction(data):
while true:
print("Thread " + str(data["thread"].get_id()) + " has started the loop!")
data["lock"].lock()
print("Thread " + str(data["thread"].get_id()) + " has the lock!")
print("Eating...")
# Don't want get_tree().create_timer to be executed in the thread
# so use call_deferred which will call the relevant
# function from the main thread
# Hand over this thread's semaphore as a way to be notified when the timer
# has timed out.
call_deferred("_wait", data["sem"])
# Wait for the signal that will be sent by the main thread once the timer
# has timed out
data["sem"].wait()
print("Done eating!")
print("Releasing lock")
data["lock"].unlock()
print(str(data["thread"].get_id() + " is waiting for lock..."))
func _wait(sem):
# This gets called from our threads, but it is executed on the main thread
# due to call_deferred, so it's save to interact with the scene tree
# Use yield to wait for the timer to time out
yield(get_tree().create_timer(randf() * 3), "timeout")
# Now notify the thread that has called this function by using the thread's
# own semaphore
sem.post()
omggomb | 2020-04-11 11:19
Thank you very much! I tried code adding the semaphore to the mutex and replacing the mutex, I swear I tried something exactly like this, but I must have gotten a line or two wrong somewhere. I have realized I definitely need to read up on semaphores a bit. Anyway, I greatly appreciate your help! This code works.
A clarifying question, we’re only using call_deferred here because of using the scene tree to create that timer that simulates work, right? Would we need to use call deferred for any action with the scene tree, say like adding a child?
navett52 | 2020-04-11 14:17
Yes, to quote the docs:
Interacting with the active scene tree is NOT thread safe. Make sure
to use mutexes when sending data between threads. If you want to call
functions from a thread, the call_deferred function may be used:
# Unsafe:
node.add_child(child_node)
# Safe:
node.call_deferred("add_child", child_node)
Though keep in mind, that we’re talking about doing this from a different thread, so when interacting with the scene tree from the main thread, you can just use the regular function calls.
omggomb | 2020-04-12 12:36