วิธีแก้ปัญหาสำหรับการใช้ __name__=='__main__' ใน Python multiprocessing

ดังที่เราทุกคนรู้ดีว่าเราจำเป็นต้องปกป้อง main() เมื่อรันโค้ดด้วย multiprocessing ใน Python โดยใช้ if __name__ == '__main__'

ฉันเข้าใจว่านี่เป็นสิ่งจำเป็นในบางกรณีเพื่อให้สามารถเข้าถึงฟังก์ชันที่กำหนดไว้ใน main แต่ฉันไม่เข้าใจว่าทำไมจึงจำเป็นในกรณีนี้:

file2.py

import numpy as np
from multiprocessing import Pool
class Something(object):
    def get_image(self):
        return np.random.rand(64,64)

    def mp(self):
        image = self.get_image()
        p = Pool(2)
        res1 = p.apply_async(np.sum, (image,))
        res2 = p.apply_async(np.mean, (image,))
        print(res1.get())
        print(res2.get())
        p.close()
        p.join()

main.py

from file2 import Something
s = Something()
s.mp()

ฟังก์ชันหรือการนำเข้าทั้งหมดที่จำเป็นสำหรับ Something ในการทำงานเป็นส่วนหนึ่งของ file2.py เหตุใดกระบวนการย่อยจึงต้องรัน main.py อีกครั้ง

ฉันคิดว่าโซลูชัน __name__ ไม่ค่อยดีนักเพราะจะทำให้ฉันไม่สามารถแจกจ่ายรหัส file2.py ได้ เนื่องจากฉันไม่สามารถแน่ใจได้ว่าพวกเขากำลังปกป้องรหัสหลักของพวกเขา ไม่มีวิธีแก้ปัญหาสำหรับ Windows ใช่หรือไม่ แพ็คเกจจะแก้ไขได้อย่างไร (เนื่องจากฉันไม่เคยพบปัญหาใด ๆ ที่ไม่ปกป้องแพ็คเกจหลักของฉันด้วยแพ็คเกจใด ๆ - พวกมันไม่ได้ใช้การประมวลผลหลายตัวหรือเปล่า)

แก้ไข: ฉันรู้ว่านี่เป็นเพราะ fork() ไม่ได้ใช้งานใน Windows ฉันแค่ถามว่ามีแฮ็กเพื่อให้ล่ามเริ่มต้นที่ file2.py แทนที่จะเป็น main.py หรือไม่ เพราะฉันสามารถมั่นใจได้ว่า file2.py นั้นพึ่งพาตนเองได้


person skjerns    schedule 14.07.2017    source แหล่งที่มา
comment
การแฮ็ก if __name__ == '__main__' จำเป็นบน Windows เท่านั้น เนื่องจากแพลตฟอร์มนั้นไม่มี fork() หากคุณเลือกระบบปฏิบัติการ ใดๆ อื่น คุณจะไม่จำเป็นต้องใช้มัน   -  person Sven Marnach    schedule 14.07.2017
comment
OP เพียงเพื่อยืนยันว่าคุณ อยู่ บน windows ใช่ไหม   -  person cs95    schedule 14.07.2017
comment
หากฉันเข้าใจคุณถูกต้อง คุณกำลังเขียน file2.py เป็นไลบรารี และคุณต้องการรองรับโค้ดผู้ใช้ เช่น main.py (ซึ่งอาจถูกเขียนโดยบุคคลอื่นในอนาคต) ขออภัย ฉันไม่คิดว่ามีวิธีใดที่จะปกป้องผู้ใช้ของคุณจากข้อกำหนดของ multiprocessing คุณอาจจำเป็นต้องจัดทำเอกสารว่าโมดูลของคุณต้องการให้ใส่โค้ดสคริปต์ภายใน if __name__ == "__main__" บล็อก เพื่อไม่ให้มีการรันใดๆ หากโมดูลถูกนำเข้า   -  person Blckknght    schedule 15.07.2017
comment
@Blckknght ขอบคุณนั่นคือคำตอบที่ฉันกำลังมองหา! (แม้ว่าจะไม่ใช่คำตอบที่ฉันหวังไว้ ;))   -  person skjerns    schedule 18.07.2017


คำตอบ (4)


โมดูลหลักถูกนำเข้า (แต่ด้วย __name__ != '__main__' เนื่องจาก Windows พยายามจำลองพฤติกรรมที่คล้ายการฟอร์กบนระบบที่ไม่มีการฟอร์ก) multiprocessing ไม่มีทางรู้ได้เลยว่าคุณไม่ได้ทำอะไรที่สำคัญในโมดูลหลักของคุณ ดังนั้นการนำเข้าจึงเสร็จสิ้น "ในกรณีฉุกเฉิน" เพื่อสร้างสภาพแวดล้อมที่คล้ายกับสภาพแวดล้อมในกระบวนการหลักของคุณ หากไม่ทำเช่นนี้ สิ่งต่างๆ ทุกประเภทที่เกิดขึ้นจากผลข้างเคียงใน main (เช่น การนำเข้า การเรียกการกำหนดค่าที่มีผลข้างเคียงอย่างต่อเนื่อง ฯลฯ) อาจไม่ได้รับการดำเนินการอย่างถูกต้องในกระบวนการลูก

ดังนั้น หากพวกเขาไม่ได้ปกป้อง __main__ ของพวกเขา โค้ดจะไม่ปลอดภัยสำหรับการประมวลผลหลายรายการ (และไม่ปลอดภัยต่อหน่วย ปลอดภัยในการนำเข้า ฯลฯ) กระดาษห่อป้องกัน if __name__ == '__main__': ควรเป็นส่วนหนึ่งของโมดูลหลักที่ถูกต้องทั้งหมด ดำเนินการต่อและแจกจ่ายพร้อมหมายเหตุเกี่ยวกับการกำหนดให้มีการป้องกันโมดูลหลักที่ปลอดภัยสำหรับการประมวลผลหลายตัว

person ShadowRanger    schedule 14.07.2017

เมื่อใช้วิธีการเริ่มต้น "วางไข่" กระบวนการใหม่คือล่าม Python ที่เริ่มต้นตั้งแต่ต้น เป็นไปไม่ได้ที่ล่าม Python ใหม่ในกระบวนการย่อยจะทราบว่าโมดูลใดที่ต้องนำเข้า ดังนั้นพวกเขาจึงนำเข้าโมดูลหลักอีกครั้ง ซึ่งจะนำเข้าอย่างอื่นทั้งหมดตามลำดับ ซึ่งหมายความว่าจะต้องสามารถนำเข้าโมดูลหลักได้โดยไม่มีผลข้างเคียงใดๆ

หากคุณอยู่บนแพลตฟอร์มที่แตกต่างจาก Windows คุณสามารถใช้วิธีเริ่มต้น "fork" แทนได้ และคุณจะไม่มีปัญหานี้

ที่กล่าวว่าเกิดอะไรขึ้นกับการใช้ if __name__ == "__main__": มีประโยชน์เพิ่มเติมมากมาย เช่น เครื่องมือเอกสารจะสามารถประมวลผลโมดูลหลักของคุณได้ และการทดสอบหน่วยก็ง่ายกว่า ฯลฯ ดังนั้นคุณควรใช้มันในทุกกรณี

person Sven Marnach    schedule 14.07.2017

จำเป็นต้องใช้ if __name__ == '__main__' บน windows เนื่องจาก windows ไม่มีตัวเลือก "fork" สำหรับกระบวนการ

ตัวอย่างเช่น ใน linux คุณสามารถ fork กระบวนการได้ ดังนั้นกระบวนการหลักจะถูกคัดลอกและการคัดลอกจะกลายเป็นกระบวนการย่อย (และจะสามารถเข้าถึงโค้ดที่นำเข้าไว้แล้วที่คุณโหลดไว้ในกระบวนการหลัก)

เนื่องจากคุณไม่สามารถแยกใน windows ได้ python จึงนำเข้าโค้ดทั้งหมดที่นำเข้าโดยกระบวนการหลักในกระบวนการลูก สิ่งนี้จะสร้างเอฟเฟกต์ที่คล้ายกัน แต่ถ้าคุณไม่ทำตามเคล็ดลับ __name__ การนำเข้านี้จะดำเนินการโค้ดของคุณอีกครั้งในกระบวนการลูก (และสิ่งนี้จะทำให้สร้างเป็นลูกของตัวเอง เป็นต้น)

ดังนั้นแม้ในตัวอย่างของคุณ main.py จะถูกนำเข้าอีกครั้ง (เนื่องจากไฟล์ทั้งหมดถูกนำเข้าอีกครั้ง) python ไม่สามารถเดาได้ว่าสคริปต์ python เฉพาะใดที่กระบวนการลูกควรนำเข้า

โปรดทราบว่ายังมีข้อจำกัดอื่นๆ ที่คุณควรทราบ เช่น การใช้ globals คุณสามารถอ่านได้ที่นี่ https://docs.python.org/2/library/multiprocessing.html#windows

person DorElias    schedule 14.07.2017

ตามที่คนอื่นกล่าวถึงวิธีการ spawn() บน Windows จะนำเข้ารหัสอีกครั้งสำหรับแต่ละอินสแตนซ์ของล่าม การนำเข้านี้จะรันโค้ดของคุณอีกครั้งในกระบวนการลูก (และจะทำให้สร้างเป็นลูกของตัวเอง และอื่นๆ)

วิธีแก้ปัญหาคือการดึงสคริปต์ที่มีการประมวลผลหลายตัวลงในไฟล์แยกต่างหาก จากนั้นใช้กระบวนการย่อยเพื่อเรียกใช้งานจากสคริปต์หลัก

ฉันส่งตัวแปรไปยังสคริปต์โดยเลือกตัวแปรเหล่านั้นในไดเร็กทอรีชั่วคราว และฉันส่งไดเร็กทอรีชั่วคราวไปยังกระบวนการย่อยด้วย argparse

จากนั้นฉันจะเลือกผลลัพธ์ลงในไดเร็กทอรีชั่วคราว ซึ่งเป็นที่ที่สคริปต์หลักดึงข้อมูลเหล่านั้น

นี่คือตัวอย่างฟังก์ชัน file_hasher() ที่ฉันเขียน:

main_program.py

import os, pickle, shutil, subprocess, sys, tempfile

def file_hasher(filenames):
    try:
        subprocess_directory = tempfile.mkdtemp()
        input_arguments_file = os.path.join(subprocess_directory, 'input_arguments.dat')
        with open(input_arguments_file, 'wb') as func_inputs:
            pickle.dump(filenames, func_inputs)
        current_path = os.path.dirname(os.path.realpath(__file__))
        file_hasher = os.path.join(current_path, 'file_hasher.py')
        python_interpreter = sys.executable
        proc = subprocess.call([python_interpreter, file_hasher, subprocess_directory],
                               timeout=60, 
                              )
        output_file = os.path.join(subprocess_directory, 'function_outputs.dat')
        with open(output_file, 'rb') as func_outputs:
            hashlist = pickle.load(func_outputs)
    finally:
        shutil.rmtree(subprocess_directory)
    return hashlist

file_hasher.py

#! /usr/bin/env python
import argparse, hashlib, os, pickle
from multiprocessing import Pool

def file_hasher(input_file):
    with open(input_file, 'rb') as f:
        data = f.read()
        md5_hash = hashlib.md5(data)
    hashval = md5_hash.hexdigest()
    return hashval

if __name__=='__main__':
    argument_parser = argparse.ArgumentParser()
    argument_parser.add_argument('subprocess_directory', type=str)
    subprocess_directory = argument_parser.parse_args().subprocess_directory

    arguments_file = os.path.join(subprocess_directory, 'input_arguments.dat')
    with open(arguments_file, 'rb') as func_inputs:
        filenames = pickle.load(func_inputs)

    hashlist = []
    p = Pool()
    for r in p.imap(file_hasher, filenames):
        hashlist.append(r)

    output_file = os.path.join(subprocess_directory, 'function_outputs.dat')
    with open(output_file, 'wb') as func_outputs:
        pickle.dump(hashlist, func_outputs)

มันคงมีวิธีที่ดีกว่านี้...

person Chris Hubley    schedule 07.12.2018
comment
ไม่มีปัญหา. ฉันมีปัญหาเดียวกันและเป็นสิ่งที่ดีที่สุดที่ฉันสามารถทำได้ มันไม่สวยงาม แต่ช่วยให้คุณสามารถเรียกใช้สคริปต์หลายตัวประมวลผลจากภายในโมดูลข้ามหลายแพลตฟอร์ม อย่างไรก็ตาม มีค่าใช้จ่ายเล็กน้อย ดังนั้นหากคุณรู้ว่าโค้ดของคุณจะไม่มีวันทำงานบน Windows ฉันคิดว่าเป็นการดีกว่าที่จะละเลยการป้องกัน if name__=='__main' - person Chris Hubley; 12.12.2018