I am experiencing a strange behavior in Python. When I used a lambda as a target of a thread, the behavior was inconsistent.
The first example is like this:
import time,threading
locker= threading.RLock()
def func(obj):
while obj['count']>0:
with locker: print 'thread',obj,id(obj)
obj['count']-= 1
time.sleep(0.1)
with locker: print 'finished',obj,id(obj)
def make_thread1():
threads= []
objs= {}
for i in range(2):
objs[i]= {}
objs[i]['id']= i
objs[i]['count']= (i+2)*2
t= threading.Thread(name='func'+str(i), target=lambda: func(objs[i]))
t.start()
threads.append(t)
return threads,objs
if __name__=='__main__':
threads,objs= make_thread1()
for t in threads:
t.join()
There were two patterns of results. One was
thread {'count': 4, 'id': 0} 139911658041704
thread {'count': 6, 'id': 1} 139911658041984
thread {'count': 3, 'id': 0} 139911658041704
thread {'count': 5, 'id': 1} 139911658041984
thread {'count': 4, 'id': 1} 139911658041984
thread {'count': 2, 'id': 0} 139911658041704
thread {'count': 3, 'id': 1} 139911658041984
thread {'count': 1, 'id': 0} 139911658041704
thread {'count': 2, 'id': 1} 139911658041984
finished {'count': 0, 'id': 0} 139911658041704
thread {'count': 1, 'id': 1} 139911658041984
finished {'count': 0, 'id': 1} 139911658041984
This is a result I expected. However when running this code several times, sometimes it resulted like this:
thread {'count': 6, 'id': 1} 140389870428800
thread {'count': 5, 'id': 1} 140389870428800
thread {'count': 4, 'id': 1} 140389870428800
thread {'count': 3, 'id': 1} 140389870428800
thread {'count': 2, 'id': 1} 140389870428800
thread {'count': 1, 'id': 1} 140389870428800
finished {'count': 0, 'id': 1} 140389870428800
finished {'count': 0, 'id': 1} 140389870428800
When making the threads, lambda:func(objs[0])
and lambda:func(objs[1])
were defined as the target functions respectively, but actually both target functions were lambda:func(objs[1])
(but different instances).
I cannot understand why this happened.
Well, one possibility would be that I was using a local variable i
in making the lambda functions. But it should be evaluated when t.start()
was executed...? Then why there were two patterns of the results?
For more investigation, I modified the code without lambda:
class TObj:
def __init__(self):
self.objs= None
def f(self):
func(self.objs)
def make_thread2():
threads= []
classes= {}
for i in range(2):
classes[i]= TObj()
classes[i].objs= {}
classes[i].objs['id']= i
classes[i].objs['count']= (i+2)*2
t= threading.Thread(name='func'+str(i), target=classes[i].f)
t.start()
threads.append(t)
return threads,classes
if __name__=='__main__':
threads,classes= make_thread2()
for t in threads:
t.join()
This code worked perfectly:
thread {'count': 4, 'id': 0} 140522771444352
thread {'count': 6, 'id': 1} 140522771445472
thread {'count': 3, 'id': 0} 140522771444352
thread {'count': 5, 'id': 1} 140522771445472
thread {'count': 2, 'id': 0} 140522771444352
thread {'count': 4, 'id': 1} 140522771445472
thread {'count': 1, 'id': 0} 140522771444352
thread {'count': 3, 'id': 1} 140522771445472
finished {'count': 0, 'id': 0} 140522771444352
thread {'count': 2, 'id': 1} 140522771445472
thread {'count': 1, 'id': 1} 140522771445472
finished {'count': 0, 'id': 1} 140522771445472
I want to understand the reasons why the inconsistency of the first code happened.
I also want to know how to make lambda functions in loops safely. If the above problem was caused by using a loop variable i
in a lambda function, we should avoid to generate lambda functions in loops. It would be inconvenient; I could code like the latter one, but it was longer. Are there any good ideas?