Tomas
Tomas

Reputation: 533

Python: update argument in thread

I was wondering if it would be possible to start a new thread and update its argument when this argument gets a new value in the main of the program, so something like this:

i = 0

def foo(i):
    print i
    time.sleep(5)

thread.start_new_thread(foo,(i,))

while True:
    i = i+1

Thanks a lot for any help!

Upvotes: 5

Views: 10037

Answers (2)

abarnert
abarnert

Reputation: 365697

An argument is just a value, like anything else. Passing the value just makes a new reference to the same value, and if you mutate that value, every reference will see it.

The fact that both the global variable and the function parameter have the same name isn't relevant here, and is a little confusing, so I'm going to rename one of them. Also, your foo function only does that print once (possibly before you even increment the value), then sleeps for 5 seconds, then finishes. You probably wanted a loop there; otherwise, you can't actually tell whether things are working or not.

So, here's an example:

i = []

def foo(j):
    while True:
        print j
        time.sleep(5)

thread.start_new_thread(foo,(i,))

while True:
    i.append(1)

So, why doesn't your code work? Well, i = i+1 isn't mutating the value 0, it's assigning a new value, 0 + 1, to i. The foo function still has a reference to the old value, 0, which is unchanged.

Since integers are immutable, you can't directly solve this problem. But you can indirectly solve it very easily: replace the integer with some kind of wrapper that is mutable.

For example, you can write an IntegerHolder class with set and get methods; when you i.set(i.get() + 1), and the other reference does i.get(), it will see the new value.

Or you can just use a list as a holder. Lists are mutable, and hold zero or more elements. When you do i[0] = i[0] + 1, that replaces i[0] with a new integer value, but i is still the same list value, and that's what the other reference is pointing at. So:

i = [0]

def foo(j):
    print j[0]
    time.sleep(5)

thread.start_new_thread(foo,(i,))

while True:
    i[0] = i[0]+1

This may seem a little hacky, but it's actually a pretty common Python idiom.


Meanwhile, the fact that foo is running in another thread creates another problem.

In theory, threads run simultaneously, and there's no ordering of any data accesses between them. Your main thread could be running on core 0, and working on a copy of i that's in core 0's cache, while your foo thread is running on core 1, and working on a different copy of i that's in core 1's cache, and there is nothing in your code to force the caches to get synchronized.

In practice, you will often get away with this, especially in CPython. But to actually know when you can get away with it, you have to learn how the Global Interpreter Lock works, and how the interpreter handles variables, and (in some cases) even how your platform's cache coherency and your C implementation's memory model and so on work. So, you shouldn't rely on it. The right thing to do is to use some kind of synchronization mechanism to guard access to i.

As a side note, you should also almost never use thread instead of threading, so I'm going to switch that as well.

i = []
lock = threading.Lock()

def foo(j):
    while True:
        with lock:
            print j[0]
        time.sleep(5)

t = threading.Thread(target=foo, args=(i,))
t.start()

while True:
    with lock:
        i[0] = i[0]+1

One last thing: If you create a thread, you need to join it later, or you can't quit cleanly. But your foo thread never exits, so if you try to join it, you'll just block forever.

For simple cases like this, there's a simple solution. Before calling t.start(), do t.daemon = True. This means when your main thread quits, the background thread will be automatically killed at some arbitrary point. That's obviously a bad thing if it's, say, writing to a file or a database. But in your case, it's not doing anything persistent or dangerous.

For more realistic cases, you generally want to create some way to signal between the two threads. Often you've already got something for the thread to wait on—a Queue, a file object or collection of them (via select), etc. If not, just create a flag variable protected by a lock (or condition or whatever is appropriate).

Upvotes: 8

Drew
Drew

Reputation: 104

Try globals.

i = 0

def foo():
    print i
    time.sleep(5)

thread.start_new_thread(foo,())

while True:
    i = i+1

You could also pass a hash holding the variables you need.

args = {'i' : 0}

def foo(args):
    print args['i']
    time.sleep(5)

thread.start_new_thread(foo,(args,))

while True:
    args['i'] = arg['i'] + 1

You might also want to use a thread lock.

import thread

lock = thread.allocate_lock()
args = {'i' : 0}

def foo(args):
    with lock:
        print args['i']
    time.sleep(5)

thread.start_new_thread(foo,(args,))

while True:
    with lock:
        args['i'] = arg['i'] + 1

Hoped this helped.

Upvotes: 0

Related Questions