วิธีการทำ .join () ของโมดูลมัลติโพรเซสเซอร์ของ Python คืออะไร


110

เรียนรู้เกี่ยวกับ Python Multiprocessing (จากบทความ PMOTW ) และชอบคำชี้แจงเกี่ยวกับjoin()วิธีการนี้

ในบทช่วยสอนเก่าตั้งแต่ปี 2008ระบุว่าหากไม่มีการp.join()เรียกในโค้ดด้านล่าง "กระบวนการย่อยจะไม่ได้ใช้งานและไม่ยุติกลายเป็นซอมบี้ที่คุณต้องฆ่าด้วยตนเอง"

from multiprocessing import Process

def say_hello(name='world'):
    print "Hello, %s" % name

p = Process(target=say_hello)
p.start()
p.join()

ฉันได้เพิ่มงานพิมพ์ของPIDa time.sleepเพื่อทดสอบและเท่าที่ฉันสามารถบอกได้กระบวนการนี้จะสิ้นสุดลงด้วยตัวเอง:

from multiprocessing import Process
import sys
import time

def say_hello(name='world'):
    print "Hello, %s" % name
    print 'Starting:', p.name, p.pid
    sys.stdout.flush()
    print 'Exiting :', p.name, p.pid
    sys.stdout.flush()
    time.sleep(20)

p = Process(target=say_hello)
p.start()
# no p.join()

ภายใน 20 วินาที:

936 ttys000    0:00.05 /Library/Frameworks/Python.framework/Versions/2.7/Reso
938 ttys000    0:00.00 /Library/Frameworks/Python.framework/Versions/2.7/Reso
947 ttys001    0:00.13 -bash

หลังจาก 20 วินาที:

947 ttys001    0:00.13 -bash

ลักษณะการทำงานจะเหมือนกันเมื่อp.join()เพิ่มกลับที่ท้ายไฟล์ งูหลามโมดูลของสัปดาห์มีคำอธิบายที่อ่านได้มากของโมดูล ; "ในการรอจนกว่ากระบวนการทำงานจะเสร็จสิ้นและออกจากระบบให้ใช้เมธอด join ()" แต่ดูเหมือนว่าอย่างน้อย OS X ก็ทำเช่นนั้นอยู่ดี

ฉันยังสงสัยเกี่ยวกับชื่อของวิธีการ เป็น.join()วิธีการเชื่อมโยงอะไรที่นี่? การเชื่อมต่อกับกระบวนการสิ้นสุดลงหรือไม่ หรือเพียงแค่ใช้ชื่อร่วมกับ.join()วิธีดั้งเดิมของ Python ?


2
เท่าที่ฉันรู้มันมีเธรดหลักและรอให้กระบวนการย่อยเสร็จสมบูรณ์จากนั้นรวมทรัพยากรในเธรดหลักกลับเข้ามาโดยส่วนใหญ่จะเป็นการออกที่สะอาด
abhishekgarg

อาที่สมเหตุสมผล ดังนั้นความจริงCPU, Memory resourcesจึงถูกแยกออกจากกระบวนการหลักจากนั้นjoinแก้ไขอีกครั้งหลังจากกระบวนการย่อยเสร็จสิ้นแล้ว?
MikeiLL

ใช่นั่นคือสิ่งที่ทำ ดังนั้นหากคุณไม่เข้าร่วมกับพวกเขาเมื่อกระบวนการย่อยเสร็จสิ้นมันก็อยู่ในฐานะกระบวนการที่หมดอายุหรือตาย
abhishekgarg

@abhishekgarg นั่นไม่เป็นความจริง กระบวนการย่อยจะเข้าร่วมโดยปริยายเมื่อกระบวนการหลักเสร็จสิ้น
dano

@dano ฉันกำลังเรียนรู้ python และฉันเพิ่งแบ่งปันสิ่งที่ฉันพบในการทดสอบของฉันในการทดสอบของฉันฉันมีกระบวนการหลักที่ไม่สิ้นสุดดังนั้นอาจเป็นเพราะเหตุใดฉันจึงเห็นกระบวนการย่อยเหล่านั้นเป็นสิ่งที่สิ้นหวัง
abhishekgarg

คำตอบ:


125

join()วิธีเมื่อใช้กับthreadingหรือmultiprocessingไม่ได้เกี่ยวข้องกับstr.join()- มันไม่จริงเชื่อมโยงเข้าด้วยกันอะไร แต่หมายความว่า "รอให้ [เธรด / กระบวนการ] นี้เสร็จสมบูรณ์" ชื่อjoinนี้ใช้เนื่องจากmultiprocessingAPI ของโมดูลมีลักษณะคล้ายกับthreadingAPI ของโมดูลและthreadingโมดูลใช้joinสำหรับThreadอ็อบเจ็กต์ การใช้คำjoinเพื่อหมายถึง "รอให้เธรดเสร็จสิ้น" เป็นเรื่องปกติในหลายภาษาโปรแกรมดังนั้น Python จึงนำมาใช้เช่นกัน

ตอนนี้เหตุผลที่คุณเห็นการหน่วงเวลา 20 วินาทีทั้งที่มีและไม่มีการโทรjoin()เป็นเพราะโดยค่าเริ่มต้นเมื่อกระบวนการหลักพร้อมที่จะออกมันจะเรียกอินสแตนซ์ที่join()กำลังทำงานอยู่ทั้งหมดโดยปริยาย multiprocessing.Processสิ่งนี้ไม่ได้ระบุไว้อย่างชัดเจนในmultiprocessingเอกสารเท่าที่ควร แต่มีการกล่าวถึงในส่วนแนวทางการเขียนโปรแกรม :

โปรดจำไว้ด้วยว่ากระบวนการที่ไม่ใช่ daemonic จะถูกรวมเข้าด้วยกันโดยอัตโนมัติ

คุณสามารถลบล้างพฤติกรรมนี้ได้โดยตั้งค่าdaemonแฟล็กบนProcessto Trueก่อนเริ่มกระบวนการ:

p = Process(target=say_hello)
p.daemon = True
p.start()
# Both parent and child will exit here, since the main process has completed.

หากคุณทำเช่นนั้นกระบวนการย่อยจะสิ้นสุดลงทันทีที่กระบวนการหลักเสร็จสิ้น :

ภูต

แฟล็ก daemon ของกระบวนการซึ่งเป็นค่าบูลีน ต้องตั้งค่านี้ก่อนที่จะเรียก start ()

ค่าเริ่มต้นจะสืบทอดมาจากกระบวนการสร้าง

เมื่อกระบวนการออกจากกระบวนการจะพยายามยุติกระบวนการชายด์ daemonic ทั้งหมด


6
ฉันเข้าใจว่าp.daemon=Trueมีไว้สำหรับ "การเริ่มต้นกระบวนการพื้นหลังที่ทำงานโดยไม่ปิดกั้นโปรแกรมหลักไม่ให้ออก" แต่ถ้า "กระบวนการ daemon ถูกยกเลิกโดยอัตโนมัติก่อนที่โปรแกรมหลักจะออก" มันใช้อะไรกันแน่?
MikeiLL

8
@MikeiLL โดยทั่วไปแล้วทุกสิ่งที่คุณต้องการให้ดำเนินการในพื้นหลังตราบเท่าที่กระบวนการหลักกำลังทำงานอยู่ แต่ไม่จำเป็นต้องทำความสะอาดอย่างสวยงามก่อนออกจากโปรแกรมหลัก บางทีกระบวนการของผู้ปฏิบัติงานที่อ่านข้อมูลจากซ็อกเก็ตหรืออุปกรณ์ฮาร์ดแวร์และฟีดข้อมูลนั้นกลับไปยังพาเรนต์ผ่านคิวหรือประมวลผลข้อมูลในพื้นหลังเพื่อจุดประสงค์บางอย่าง? โดยทั่วไปแล้วฉันจะบอกว่าการใช้daemonicกระบวนการย่อยนั้นไม่ปลอดภัยมากนักเนื่องจากกระบวนการนี้กำลังจะยุติลงโดยไม่อนุญาตให้ล้างทรัพยากรแบบเปิดใด ๆ ที่อาจมี .. (ต่อ)
dano

7
@MikeiLL แนวทางปฏิบัติที่ดีกว่าคือการส่งสัญญาณให้เด็กทำความสะอาดและออกก่อนออกจากกระบวนการหลัก คุณอาจคิดว่ามันสมเหตุสมผลที่จะปล่อยให้กระบวนการย่อยของ daemonic ทำงานเมื่อพาเรนต์ออก แต่โปรดทราบว่าmultiprocessingAPI ได้รับการออกแบบมาเพื่อเลียนแบบthreadingAPI ให้ใกล้เคียงที่สุด threading.ThreadวัตถุDaemonic จะสิ้นสุดลงทันทีที่เธรดหลักออกจากระบบดังนั้นmultiprocesing.Processวัตถุdaemonic จึงทำงานในลักษณะเดียวกัน
dano

38

หากไม่มีjoin()กระบวนการหลักสามารถดำเนินการให้เสร็จสิ้นก่อนที่กระบวนการย่อยจะทำ ฉันไม่แน่ใจว่าภายใต้สถานการณ์ใดที่นำไปสู่การเป็นซอมบี้

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

นิรุกติศาสตร์join()คือมันตรงกันข้ามforkซึ่งเป็นคำทั่วไปในระบบปฏิบัติการตระกูล Unix สำหรับการสร้างกระบวนการย่อย กระบวนการเดียว "แยก" เป็นหลายรายการจากนั้น "รวม" กลับเป็นกระบวนการเดียว


2
ใช้ชื่อjoin()เพราะjoin()เป็นสิ่งที่ใช้เพื่อรอให้threading.Threadออบเจ็กต์ดำเนินการเสร็จสิ้นและmultiprocessingAPI มีไว้เพื่อเลียนแบบthreadingAPI ให้มากที่สุด
dano

คำสั่งที่สองของคุณกล่าวถึงปัญหาที่ฉันกำลังเผชิญในโครงการปัจจุบัน
MikeiLL

ฉันเข้าใจส่วนที่เธรดหลักรอให้กระบวนการย่อยเสร็จสิ้น แต่การจัดเรียงแบบนั้นไม่ได้ทำให้วัตถุประสงค์ของการดำเนินการแบบอะซิงโครนัสผิดไปใช่หรือไม่ มันไม่ควรจะเสร็จสิ้นการดำเนินการโดยอิสระ (งานย่อยหรือกระบวนการ)?
Apurva Kunkulol

1
@ApurvaKunkulol ขึ้นอยู่กับว่าคุณใช้มันอย่างไร แต่join()จำเป็นในกรณีที่เธรดหลักต้องการผลลัพธ์ของการทำงานของเธรดย่อย ตัวอย่างเช่นหากคุณกำลังแสดงผลบางอย่างและกำหนด 1/4 ของภาพสุดท้ายให้กับแต่ละกระบวนการย่อย 4 กระบวนการและต้องการแสดงภาพทั้งหมดเมื่อเสร็จแล้ว
Russell Borogove

@RussellBorogove อ่า! ฉันเข้าใจแล้ว จากนั้นความหมายของ Asynchronous activity จะแตกต่างกันเล็กน้อยที่นี่ ต้องหมายถึงเฉพาะข้อเท็จจริงที่ว่ากระบวนการย่อยมีขึ้นเพื่อดำเนินงานพร้อมกันกับเธรดหลักในขณะที่เธรดหลักยังทำงานแทนการรอคอยกระบวนการย่อยอย่างเฉยเมย
Apurva Kunkulol

12

ฉันจะไม่อธิบายรายละเอียดว่าjoinทำอะไรแต่นี่คือนิรุกติศาสตร์และสัญชาตญาณที่อยู่เบื้องหลังซึ่งจะช่วยให้คุณจำความหมายได้ง่ายขึ้น

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

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

ชื่อ "fork" และ "join" ถูกใช้กับความหมายนี้ในการประมวลผลหลายขั้นตอนตั้งแต่ปีพ . . 2506


ดังนั้นในทางที่การใช้คำjoinนี้อาจนำหน้าจึงใช้ในการอ้างถึงการเรียงต่อกันซึ่งตรงข้ามกับวิธีอื่น ๆ
MikeiLL

1
ไม่น่าเป็นไปได้ที่การใช้ในการเชื่อมต่อที่ได้มาจากการใช้ในการประมวลผลหลายขั้นตอน ประสาทสัมผัสทั้งสองได้รับแยกจากความหมายภาษาอังกฤษธรรมดาของคำ
Russell Borogove

2

join()ใช้เพื่อรอให้กระบวนการของผู้ปฏิบัติงานออก หนึ่งจะต้องโทรclose()หรือก่อนที่จะใช้terminate()join()

เช่นเดียวกับที่ @Russell กล่าวถึงการเข้าร่วมก็เหมือนกับสิ่งที่ตรงกันข้ามกับfork (ซึ่ง Spawns กระบวนการย่อย)

สำหรับการเข้าร่วมเพื่อรันคุณต้องรันclose()ซึ่งจะป้องกันไม่ให้ส่งงานไปยังพูลอีกต่อไปและออกเมื่องานทั้งหมดเสร็จสมบูรณ์ อีกวิธีหนึ่งคือการเรียกใช้terminate()จะออกโดยการหยุดกระบวนการของผู้ปฏิบัติงานทั้งหมดทันที

"the child process will sit idle and not terminate, becoming a zombie you must manually kill" สิ่งนี้เป็นไปได้เมื่อกระบวนการหลัก (พาเรนต์) ออกไป แต่กระบวนการลูกยังคงทำงานอยู่และเมื่อดำเนินการเสร็จสิ้นแล้วจะไม่มีกระบวนการหลักที่จะคืนสถานะการออกเป็น


2

การjoin()โทรช่วยให้มั่นใจได้ว่าบรรทัดถัดมาของโค้ดของคุณจะไม่ถูกเรียกก่อนที่กระบวนการมัลติโพรเซสเซอร์ทั้งหมดจะเสร็จสมบูรณ์

ตัวอย่างเช่นหากไม่มีjoin()รหัสต่อไปนี้จะเรียกrestart_program()ก่อนที่กระบวนการจะเสร็จสิ้นซึ่งคล้ายกับอะซิงโครนัสและไม่ใช่สิ่งที่เราต้องการ (คุณสามารถลอง):

num_processes = 5

for i in range(num_processes):
    p = multiprocessing.Process(target=calculate_stuff, args=(i,))
    p.start()
    processes.append(p)
for p in processes:
    p.join() # call to ensure subsequent line (e.g. restart_program) 
             # is not called until all processes finish

restart_program()

0

ในการรอจนกว่ากระบวนการจะเสร็จสิ้นและออกจากระบบให้ใช้เมธอด join ()

และ

หมายเหตุเป็นสิ่งสำคัญที่จะต้องเข้าร่วม () กระบวนการหลังจากยุติกระบวนการเพื่อให้เวลาเครื่องจักรพื้นหลังอัปเดตสถานะของออบเจ็กต์เพื่อให้สอดคล้องกับการสิ้นสุด

นี่เป็นตัวอย่างที่ดีที่ช่วยให้ฉันเข้าใจ: ที่นี่

สิ่งหนึ่งที่ฉันสังเกตเห็นเป็นการส่วนตัวคือกระบวนการหลักของฉันหยุดชั่วคราวจนกว่าเด็กจะเสร็จสิ้นกระบวนการโดยใช้เมธอด join () ซึ่งเอาชนะจุดที่ฉันใช้multiprocessing.Process()ในตอนแรก

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.