Search code examples
pythonpython-multithreading

Sending variable in function doesn't work when threading python


Currently trying to do an idle animation which constantly updates the character when idle = True, trying to also keep it mainly in a function for later use as well.

However, when I updated idle = False outside of the function, it didn't stop the animation.

I came across threading, which seemed to solve some issues for a bit.

import time
import threading
b = True
def test_func():
    global b
    while b:
        print '1'

threading.Thread(target=test_func).start()
b = False

This worked, but it would be easier for the function to accept things like

def test_func(b):

and have that be used instead of a lot of global variables. Especially when I use it for multiple things, it would be helpful to have it just accept which one will be checked instead of way more lines. When trying to do

import time
import threading
b = True
def test_func(b):
    while b:
        print '1'

threading.Thread(target=test_func(b)).start()
b = False

the (b) seems to make the threading not work as it constantly goes instead of stopping when b = False

(this is my small test code where b represents the boolean for if it continues to update and the print '1' part is in place of the character updating.) (also I tried using lambda because that's what I remember worked in other cases when I needed to pass a variable like that but I tested it and it doesn't work)


Solution

  • When you write threading.Thread(target=test_func(b)).start() the function is called as part of collecting the arguments to Thread, before the thread is started; in addition, the function gets a copy of the variable, rather than the variable itself.

    In order to make something like this work, you need to resolve both halves:

    • Make it accept an argument, but then still be a function that will be called

      This can be done in a few ways. Probably the cleanest would be to make test_func part of an object, so that you pass b to the constructor and then have a method as the thread:

      threading.Thread(target=Worker(b).test_func).start()
      

      That's usually the best, because it gives you a place for all your thread-related data and functions (they all live in the same object).

      If that's too much, if you really do have just one function that you need to call like this, you can use functools.partial or a closure:

      threading.Thread(target=partial(test_func, b)).start()
      
    • Use a mutable value which you can modify in-place

      When you pass a boolean into a function, that function effectively gets a copy. You'll need to use some mutable value (object, list, etc) which you can change in-place.

      If you use an object for the first item, that's probably easiest:

      w = Worker()
      threading.Thread(target=w.test_func).start()
      w.b = False
      

      Otherwise, you can make b a dict or list or similar, then change a particular entry within it:

      threading.Thread(target=partial(test_func, b)).start()
      b['active'] = False