python 踩坑纪实 – 并行计算(multiprocessing)的全局变量问题

太长不看可以直接看文末加粗部分

问题描述

最近在项目中遇到了这样的问题,见代码

GAME_NAME = “”

def get_achieve_info():
        # ...
        global GAME_NAME
        # 更新全局变量 GAME_NAME
        GAME_NAME = soup.select(".profile_small_header_texture h1")[0].string.replace(":", "").replace("*", "")    
        # ...
def download(url, name, pic_path):
    print(GAME_NAME) # 并行计算的方法内输出全局变量
    if os.path.exists(pic_path):
        logger.warning(name + " exists, skipping")
        return
    req = request.Request(url, headers=HEAD)
    resp = request.urlopen(req)
    if resp.getcode() == 200:
        with open(pic_path, "wb") as f:
            f.write(resp.read())
        logger.warning("Downloaded " + name)
    else:
        logger.error("Get " + name + " returned code " + str(resp.getcode()))
        return


def download_pictures(pictures_q):
    print(GAME_NAME) # 非并行计算的方法内输出全局变量
    try:
        pool = multiprocessing.Pool(processes=multiprocessing.cpu_count() - 1)
        output = pool.starmap(download, pictures_q)
    except Exception as ex:
        logger.error("Download Pictures Failed!")
        logger.error("Error Message: " + str(ex))


def main():
# ...
logger.warning("Fetching Achievements...")
pictures_q, titles, descriptions = get_achieve_info()
logger.warning("Done.")
# ...
logger.warning("Downloading Pictures...")
download_pictures(pictures_q)
logger.warning("Done.")
# ...


if __name__ == "__main__":
main()

Windows 下的执行结果如下图

python 踩坑纪实 - 并行计算(multiprocessing)的全局变量问题

概括成一句话:并行计算时无法调用在其他方法内更新过的全局变量(全局变量为声明时的初始值)

原因

搜索后找到了答案 Python Multiprocess, Global Variable Not Updated

原因有两点:

  1. 操作系统
  2. __name__ == “__main__”

见如下测试代码

import multiprocessing as mtp

NAME = "NAME2"

def change():
    global NAME
    NAME = "NAME1"

def print_variables(element):
    print("NAME in print_carialbes: " + NAME)

def multip():
    str_list = ["1", "2", "3"]
    pool = mtp.Pool(processes=mtp.cpu_count()-2)
    output = pool.map(print_variables, str_list)

def main():
    print("NAME in main(before change): " + NAME)
    change()
    print("NAME in main(after change): " + NAME)
    multip()

if __name__ == "__main__":
    main()

声明一个全局变量 NAME,赋初始值为 “NAME2”,有一方法 change() 更新 NAME 的值为 “NAME1”, 主函数调用 change() 后执行并行计算,在并行计算方法内输出全局变量 NAME

Windows 下的执行结果

python 踩坑纪实 - 并行计算(multiprocessing)的全局变量问题

Linux 下的执行结果python 踩坑纪实 - 并行计算(multiprocessing)的全局变量问题

上述代码中再加入如下代码

if __name__ != "__main__": 
        NAME = "NAME3"

并且在并行计算方法 print_variables(element) 中输出 name 属性

def print_variables(element):
    print(__name__)
    print("NAME in print_carialbes: " + NAME)

Windows 下的执行结果

python 踩坑纪实 - 并行计算(multiprocessing)的全局变量问题

Linux 下的执行结果python 踩坑纪实 - 并行计算(multiprocessing)的全局变量问题

可以看到在 Windows 系统下,并行计算方法内输出的全局变量变成了 “NAME3”,也就是执行了 if name != “main 的代码,将全局变量更新为 “NAME3”

而 Linux 系统下则不存在全局变量没有更新的问题,全局变量 NAME 的值始终为更新后的 “NAME1”

所以,该问题发生的原因为

multiprocessing 模块在 Linux 和 Windows 下的实现方式不同

在 Linux 中可以使用 fork 创建子进程,而 Windows 下是使用 python 解释器新建子进程,再将进程需要序列化的资源传递给它,实现方式的不同导致子进程的 name 属性不同:

  • 在 Linux 中,子进程的 __name__ 与主进程的 __name__ 相同,都为 __main__,也就没有全局变量的更新问题
  • 在 Windows 中,子进程的 __name__ 属性不是 __main__ 而是 __mp_main__(或是其他不同于 __main__ 的值),因此复制给子进程的全局变量只能是初始声明时的值,它没有执行 if __name__ == “__main__” 内的代码,全局变量也就无法被更新

发表评论

电子邮件地址不会被公开。 必填项已用*标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据