python中奇怪的线程行为

Ste*_*hen 5 python multithreading scope

我有一个问题,我需要将数组的索引传递给我定义内联的函数.然后该函数作为参数传递给另一个函数,该函数最终将其称为回调函数.

问题是,当代码被调用时,索引的值都是错误的.我最终通过创建一个丑陋的解决方法来解决这个问题,但我有兴趣了解这里发生的事情.我创建了一个最小的例子来演示这个问题:

from __future__ import print_function
import threading


def works_as_expected():
    for i in range(10):
        run_in_thread(lambda: print('the number is: {}'.format(i)))

def not_as_expected():
    for i in range(10):
        run_later_in_thread(lambda: print('the number is: {}'.format(i)))

def run_in_thread(f):
    threading.Thread(target=f).start()

threads_to_run_later = []
def run_later_in_thread(f):
    threads_to_run_later.append(threading.Thread(target=f))


print('this works as expected:\n')
works_as_expected()

print('\nthis does not work as expected:\n')
not_as_expected()
for t in threads_to_run_later: t.start()
Run Code Online (Sandbox Code Playgroud)

这是输出:

this works as expected:

the number is: 0
the number is: 1
the number is: 2
the number is: 3
the number is: 4
the number is: 6
the number is: 7
the number is: 7
the number is: 8
the number is: 9

this does not work as expected:

the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
the number is: 9
Run Code Online (Sandbox Code Playgroud)

有人能解释一下这里发生了什么吗?我认为它与封闭范围或其他东西有关,但是一个带有参考的答案解释了这个黑暗(对我来说)python范围的角落对我来说很有价值.

我在python 2.7.11上运行它

Bre*_*bel 3

这是 python 中闭包和作用域工作方式的结果。

所发生的事情被i限制在函数的范围内not_as_expected。因此,即使您lambda向线程提供一个函数,它所使用的变量也会在每个 lambda 和每个线程之间共享。

考虑这个例子:

def make_function():
    i = 1
    def inside_function():
        print i
    i = 2
    return inside_function

f = make_function()
f()
Run Code Online (Sandbox Code Playgroud)

你认为它会打印什么数字?i = 1函数定义之前还是之后i = 2

它将打印(ie )的当前值。函数创建时的值是什么并不重要,它总是会使用当前值。同样的事情也发生在你的函数上。i2ilambda

即使在您的预期结果中,您也可以看到它并不总是正常工作,它会跳过5并显示7两次。在这种情况下发生的情况是,每个 lambda通常在循环进入下一次迭代之前运行。但在某些情况下(例如5),循环在控制权传递给其他线程之一之前设法完成两次迭代,并i递增两次并跳过一个数字。在其他情况下(例如7),两个线程设法在循环仍处于同一迭代中时运行,并且由于i两个线程之间没有更改,因此会打印相同的值。

如果你这样做:

def function_maker(i):
    return lambda: print('the number is: {}'.format(i))

def not_as_expected():
    for i in range(10):
        run_later_in_thread(function_maker(i))
Run Code Online (Sandbox Code Playgroud)

变量与函数一起i绑定在内部。每个 lambda 函数将引用不同的变量,并且它将按预期工作。function_makerlambda