views:

164

answers:

4
import threading

mydata = threading.local()

def run():
    # When will the garbage collector be able to destroy the object created
    # here? After the thread exits from ``run()``? After ``join()`` is called?
    # Or will it survive the thread in which it was created, and live until
    # ``mydata`` is garbage-collected?
    mydata.foo = object()

t = threading.Thread(target=run)
t.start()
t.join()
A: 

By making a couple simple changes to your program and forcing a garbage collection after each step of the threading, it seems that foo cannot be collected until the program is finished - in other words, after the thread goes out of scope.

import threading
import gc

mydata = threading.local()

class x:
    def __del__(self):
        print "x got deleted!"

def run():
    mydata.foo = x()

t = threading.Thread(target=run)
print "t created"
gc.collect()
t.start()
print "t started"
gc.collect()
t.join()
print "t joined"
gc.collect()
print "Done!"

output (using Python 2.6, Windows):

>C:\temp\py\t.py
t created
t started
t joined
Done!
x got deleted!
Mark Rushakoff
Good analysis, but the missing bit is that _mydata_ going away is what you need -- I need to open a new answer to show formatted code, but the gist is much the same as yours.
Alex Martelli
+1  A: 

Mark had it almost right -- essentially "mydata" will hold references to all the TL variables in it, whatever thread they were created from. To wit...:

import threading
import gc

mydata = threading.local()

class x:
    def __del__(self):
        print "x got deleted!"

def run():
    mydata.foo = x()

t = threading.Thread(target=run)
print "t created"
gc.collect()
t.start()
print "t started"
gc.collect()
del mydata
print "mydata deleted"
gc.collect()
t.join()
print "t joined"
gc.collect()
print "Done!"

Emits:

t created
t started
x got deleted!
mydata deleted
t joined
Done!

gc actually plays no role here in CPython, so you can simplify the code down to:

import threading

mydata = threading.local()

class x:
    def __init__(self):
        print "x got created!"
    def __del__(self):
        print "x got deleted!"

def run():
    mydata.foo = x()

t = threading.Thread(target=run)
print "t created"
t.start()
print "t started"
del mydata
print "mydata deleted"
t.join()
print "t joined"
print "Done!"

and still see...:

t created
x got created!
t started
x got deleted!
mydata deleted
t joined
Done!
Alex Martelli
A: 

Thanks! It seems that Mark's program behaves differently under CPython 2.5 and 2.6:

import threading
import gc
import platform

print "Python %s (%s)" % (platform.python_version(), " ".join(platform.python_build()))

mydata = threading.local()

class x:
    def __del__(self):
        print "x got deleted!"

def run():
    mydata.foo = x()

t = threading.Thread(target=run)
print "t created"
gc.collect()
t.start()
print "t started"
gc.collect()
del mydata
print "mydata deleted"
gc.collect()
t.join()
print "t joined"
gc.collect()
print "Done!"

Emits (under Ubuntu 8.04 i386):

Python 2.5.2 (r252:60911 Jul 31 2008 19:40:22)
t created
t started
mydata deleted
x got deleted!
Exception in thread Thread-1:
Traceback (most recent call last):
  File "/usr/lib/python2.5/threading.py", line 486, in __bootstrap_inner
    self.run()
  File "/usr/lib/python2.5/threading.py", line 446, in run
    self.__target(*self.__args, **self.__kwargs)
  File "./x.py", line 14, in run
    mydata.foo = x()
NameError: global name 'mydata' is not defined

t joined
Done!

And:

Python 2.6.2 (r262:71600 Sep 19 2009 17:24:20)
t created
t started
x got deleted!
mydata deleted
t joined
Done!
Carlos Valiente
A: 

Here is my answer, since I am failing to see the conclusion in the previous answers.

I started wondering the same thing and tried a test program that is similar to the ones in other answers and my conclusion was that they do get GCed sooner than the end of the program, which means, these references can be determined as garbage once the thread itself dies.

import time
import threading
import gc

data = threading.local()

class Resource(object):
    def __init__(self):
        self.name = threading.currentThread().name
        print 'create: %s' % self.name

    def __del__(self):
        print 'delete: %s' % self.name

def access_thlocal():
    data.key = Resource()

for i in range(0, 10):
    threading.Thread(target=access_thlocal).start()
time.sleep(1)
print "Triggering GC"
gc.collect()
time.sleep(1)

The output:

create: Thread-1
create: Thread-2
delete: Thread-1
create: Thread-3
delete: Thread-2
create: Thread-4
delete: Thread-3
create: Thread-5
delete: Thread-4
create: Thread-6
delete: Thread-5
create: Thread-7
delete: Thread-6
create: Thread-8
delete: Thread-7
create: Thread-9
delete: Thread-8
create: Thread-10
delete: Thread-9
Triggering GC
delete: Thread-10

As you can see, the delete's seem to happen as soon as the thread dies.

haridsv