Python中指针式传递的函数遇到的问题

来源:互联网 发布:uu黑历史知乎 编辑:程序博客网 时间:2024/05/01 01:03

最近在尝试通过 multiprocessing 把原来的计算核心改为通过多进程实现的并行程序,但碰到了一个非常奇怪的问题,经过一些时间的排查,终于找到了问题所在。虽然问题本身很简单,但也许也有一定典型性,尤其是对编程新手而言,因此在这里用最简单的模型重现一下。

首先从下面一段非常简单的程序开始:

def main():    x = [1, 2, 3, 4, 5]    foo(x)    print(x)def foo(x):    bar(x)    # x = bar2(x)    for i in range(len(x)):        x[i] = x[i]**2def bar(x):    for i in range(len(x)):        x[i] = x[i] - 1def bar2(x):    xx = x.copy()    for i in range(len(x)):        xx[i] = x[i] - 1    return xxif __name__ == '__main__':    main()

暂时忽略被注释掉的部分,这段程序实现的功能非常简单,main 中产生一个数组,调用 foo 函数,foo 函数又调用 bar 函数,后者使数组中的每一个元素减一,然后返回 foo 函数中,数组的每个元素再次求平方。

上面两个函数中都没有返回值,因为它接受的是数组,类似于 C 语言中的指针传递,直接操作的是原数组。类似的逻辑应该对很多编程语言都是类似的。

然而,如果要考虑通过多进程来将它并行化(比如用 foo 函数来处理非常大的数组),就需要再多一点考虑了。首先因为 Python 中 GIL 的限制,虽然也可以多线程,但速度甚至不如单线程的版本,要有效地实现纯 Python 的并行,(也许是)最佳也是最简单的方案是用多进程。但多进程中各个进程不能共享内存,也就无法像上面那样用指针传递的方式来建立操作数组的方式。

这种情况下,就需要把函数相应地改成显式地返回新数组的形式,也就是上面的 bar2 函数。然而,改写之后,试运行就可以发现,计算的结果不同了。比如x = [1, 2, 3, 4, 5]的输入,得到的结果应该是[0, 1, 4, 9, 16],但实际并非如此。更奇怪的是,监测 foo 函数,发现在函数内部的结果是正确的。这说明是 foo 函数没有正确地操作原输入数组。

想到这里,问题解决了一半。真正的原因是于x = bar2(x)这一行,等号左侧虽然同样用了 x 的变量名,但由于返回了一个新的数组,也就是说开辟了一块新的内存,使得函数后面操作的跟传入的并不是同一个数组,所以返回值看起来就像没变一样。

找到原因,解决的方法也就简单了,两个办法:

  1. 把所有的指针传递函数都改回显式返回数组的形式。
  2. x = bar2(x)改为x[:] = bar2(x),这样操作的就仍然是原来的数组了。

来源:我的博客

1 0
原创粉丝点击