问题分析
以下是一段每隔半小时重复执行测试用例的脚本,func是传入的测试函数,在执行func前后,会打印操作次数
def repeat(func, action):
try:
log.info(u'******开始并发%s******' % action)
thread_list = []
for i in range(repeat_count):
def run():
log.info(u'***第%s次并发%s%svnf开始***' % (i + 1, action, concurrent_count))
func(concurrent_count)
log.info(u'***第%s次并发%s%svnf结束***\n' % (i + 1, action, concurrent_count))
t = Thread(target=run)
t.start()
thread_list.append(t)
time.sleep(60 * 30)
log.info('\n')
except Exception as e:
log.error('!!!!!![%s] failed, message is[%s]!!!!!!' % (action, e.message))
这个脚本之前都能够正常执行,准备的打印操作次数,就是***第%s次并发%s%svnf开始***,以及***第%s次并发%s%svnf结束***\n
但是今天突然发现***第5次并发实例化200vnf开始***被打印了2次,这是什么情况呢?
原来是掉到闭包的坑里了!
上诉for循环中的run函数,里面的变量i位于Enclosing(嵌套函数的外层函数内部)嵌套作用域
在真正执行时,变量i的值可能已经变了,与定义闭包时的值不同
关于作用域,可以参考https://dev.zte.com.cn/topic/#/48178
上诉for循环里有个半小时的延时,如果任务能否在半小时内执行完,那么打印就会正常,因为i的值还未发生变化。但是,如果某个任务A超过半小时,for循环就会进入下一次任务,这时i就会发生变化,当任务A执行完成时,打印的次数就会是已经发生变化的i。这就是今天突然发现***第5次并发实例化200vnf开始***被打印了2次的原因
解决方案
将变量i作为参数传给run函数,run函数内部再定义一个闭包,由run函数来生成闭包。而不是直接在闭包中使用i。即将i作为参数传递给run的count,并在run内部定义inner函数,inner函数内部使用count变量。
这样count就属于run函数的Local作用域,只受run函数内部影响,不会受for循环中i的影响
def repeat(func, action):
try:
log.info(u'******开始并发%s******' % action)
for i in range(repeat_count):
def run(count):
def inner():
log.info(u'***第%s次并发%s%svnf开始***' % (count + 1, action, concurrent_count))
func(concurrent_count)
log.info(u'***第%s次并发%s%svnf结束***\n' % (count + 1, action, concurrent_count))
return inner
t = Thread(target=run(i))
t.start()
time.sleep(60 * 30)
log.info('\n')
except Exception as e:
log.error('!!!!!![%s] failed, message is[%s]!!!!!!' % (action, e.message))
总结
使用Python闭包时,如果是在for循环内部生成闭包,并且闭包中使用了Enclosing嵌套作用域中的变量(比如上面的i),最好不要直接使用,而是将变量作为参数传给一个函数,该函数内部再定义一个闭包,由该函数来生成闭包