My module has two functions in it: do_something()
, and change_behavior()
.
The function do_something()
does Thing A by default. After change_behavior()
has been called, do_something()
does Thing B instead.
I want this transition to be thread-specific. That is, any new thread will have Thing A happen when it calls do_something()
, but if that thread calls change_behavior()
, then Thing B will happen instead when it continues to call do_something()
.
Each thread should be independent, so that one thread calling change_behavior()
does not affect the behavior of do_something()
for other threads.
My instinctive solution to this is to have behavior tied to the thread's ID (assessed via threading.get_ident()
). The function do_something()
checks a local table for whether or not the thread's ID is in it, and adjusts its behavior accordingly. Meanwhile, the function change_behavior()
simply adds the current thread to that registry. This works at any given time because there are never two concurrent threads with the same ID.
The problem comes in when the current set of threads joins, and time passes, and the parent thread makes a whole bunch more threads. One of the new threads has the same ID as one of the previous threads, because thread IDs are reused sometimes. That thread calls do_something()
, and because it's already in the registry, it does Thing B instead of Thing A.
To fix this, I need to remove the thread ID from the registry somehow, between when the first thread with that ID ends and when the second thread with that ID starts. Some hypothetical ideas I've come up with:
- Periodically check whether each thread ID is still active. This is problematic because it both wastes CPU resources and can miss if a thread is destroyed and then recreated between ticks
- Attach a method hook to be called whenever the thread joins. I'm not sure how to do this, besides the next idea
- As part of
change_behavior()
, hijack/replace the current thread's._quit()
method with one that first removes the thread's ID from the registry. This seems like bad practice, and potentially breaking.
Another aspect of my use case is that, if possible, I'd like new threads to inherit the current behavior of their parent threads, so that the user doesn't have to manually set every flag they create - but this is more relevant to how I store the information about the state of the tread than it is to when the thread finishes, which makes it marginally less relevant to this particular question.
I'm looking for guidance on whether any of these particular solutions are ideal, standard, or idiomatic, and whether there's an intended thing to do in this use case.
Using threading.local()
was suggested in the comments by @TarunLalwani. I've investigated it, and it is useful, but it doesn't account for the other use case I'd like to take care of - when a parent thread creates new subthreads, I want them to inherit the state of the parent thread. I was thinking of accomplishing this by replacing Thread.__init__()
, but using local()
would be incompatible with this use case in general, since I wouldn't be able to pass variables from parent to child threads.
I've also been experimenting, more successfully, with simply saving my attributes to the threads themselves:
current_thread = threading.current_thread()
setattr(current_thread, my_reference, new_value)
The problem with this is that, for a reason which completely mystifies me, any other variable in the module's namespace whose value is currently current_thread.my_reference
also gets set to new_value
. I have no idea why, and I've been unable to replicate the problem in a MVE (though it happens consistently in my IDE, even after restarting it). As my other currently-active question implies, the objects I'm setting here are references to output streams (every reference to an instance of the intermediary IO streaming I described in that answer is getting replaced by the file descriptor with which this method is being called), if that has anything to do with it, but I can't imagine why the type of object would affect how references work in this case.