我在Python中遇到了一个奇怪的行为。当我使用lambda作为线程的目标时,行为是不一致的。
第一个例子是这样的:
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()
有两种结果模式。一个是
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
这是我预期的结果。但是,当多次运行此代码时,有时会产生如下结果:
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
创建线程时,lambda:func(objs[0])
和lambda:func(objs[1])
分别被定义为目标函数,但实际上两个目标函数都是lambda:func(objs[1])
(但实例不同)。
我无法理解为什么会这样。
嗯,有一种可能性是我在制作lambda函数时使用局部变量i
。但是在执行t.start()
时应该对它进行评估......?那为什么有两种结果模式?
为了进行更多调查,我修改了没有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()
这段代码完美无缺:
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
我想了解第一个代码不一致的原因。
我也想知道如何安全地在循环中创建lambda函数。如果上述问题是由lambda函数中的循环变量i
引起的,我们应该避免在循环中生成lambda函数。这会很不方便;我可以编码像后者,但它更长。有什么好主意吗?
答案 0 :(得分:0)
在Deferred evaluation with lambda in Python
中描述了将lambda与循环变量一起使用的想法我们可以使用默认参数值在lambda中使用循环(局部)变量。在我的例子中,创建一个lambda函数,如:
t= threading.Thread(name='func'+str(i), target=lambda i=i: func(objs[i]))
这与使用类对象的第二种解决方案相同。
关于另一点:当Thread.start
评估目标时?这取决于系统。它可能会有所不同如果有延迟,则会在i
递增后进行评估。这可能是造成不一致的原因。