python multithreading รอจนกว่าเธรดทั้งหมดจะเสร็จสิ้น


119

สิ่งนี้อาจถูกถามในบริบทที่คล้ายกัน แต่ฉันไม่สามารถหาคำตอบได้หลังจากค้นหาประมาณ 20 นาทีดังนั้นฉันจะถาม

ฉันได้เขียนสคริปต์ Python (สมมติว่า scriptA.py) และสคริปต์ (สมมติว่า scriptB.py)

ใน scriptB ฉันต้องการเรียก scriptA หลาย ๆ ครั้งด้วยอาร์กิวเมนต์ที่แตกต่างกันแต่ละครั้งจะใช้เวลาประมาณหนึ่งชั่วโมงในการรัน (มันเป็นสคริปต์ขนาดใหญ่ทำสิ่งต่างๆมากมาย .. ไม่ต้องกังวลกับมัน) และฉันต้องการที่จะสามารถเรียกใช้ scriptA พร้อมอาร์กิวเมนต์ที่แตกต่างกันทั้งหมดพร้อมกัน แต่ฉันต้องรอจนกว่าจะเสร็จสิ้นทั้งหมดก่อนที่จะดำเนินการต่อ รหัสของฉัน:

import subprocess

#setup
do_setup()

#run scriptA
subprocess.call(scriptA + argumentsA)
subprocess.call(scriptA + argumentsB)
subprocess.call(scriptA + argumentsC)

#finish
do_finish()

ฉันต้องการเรียกใช้ทั้งหมดsubprocess.call()ในเวลาเดียวกันจากนั้นรอจนกว่าทุกอย่างจะเสร็จสิ้นฉันจะทำอย่างไร

ฉันพยายามใช้เธรดดังตัวอย่างที่นี่ :

from threading import Thread
import subprocess

def call_script(args)
    subprocess.call(args)

#run scriptA   
t1 = Thread(target=call_script, args=(scriptA + argumentsA))
t2 = Thread(target=call_script, args=(scriptA + argumentsB))
t3 = Thread(target=call_script, args=(scriptA + argumentsC))
t1.start()
t2.start()
t3.start()

แต่ฉันไม่คิดว่านี่จะถูกต้อง

ฉันจะรู้ได้อย่างไรว่าพวกเขาวิ่งเสร็จแล้วก่อนที่จะไปที่ฉันdo_finish()?

คำตอบ:


150

คุณต้องใช้วิธีการเข้าร่วมของThreadวัตถุในตอนท้ายของสคริปต์

t1 = Thread(target=call_script, args=(scriptA + argumentsA))
t2 = Thread(target=call_script, args=(scriptA + argumentsB))
t3 = Thread(target=call_script, args=(scriptA + argumentsC))

t1.start()
t2.start()
t3.start()

t1.join()
t2.join()
t3.join()

ดังนั้นหัวข้อหลักจะรอจนกว่าt1, t2และt3เสร็จสิ้นการดำเนินการ


5
อืม - มีปัญหาในการทำความเข้าใจบางสิ่งบางอย่างเคยชินกับการรัน t1 ครั้งแรกรอจนกว่าจะเสร็จสิ้นจากนั้นไปที่ t2..etc ฯลฯ ? จะทำให้ทุกอย่างเกิดขึ้นพร้อมกันได้อย่างไร ฉันไม่เห็นว่ามันจะเรียกใช้พวกเขาในเวลาเดียวกันได้อย่างไร?
Inbar Rose

25
การเรียกไปยังjoinบล็อกจนกว่าเธรดจะเสร็จสิ้นการดำเนินการ คุณจะต้องรอทุกหัวข้อต่อไป หากt1เสร็จสิ้นก่อนคุณจะเริ่มรอt2(ซึ่งอาจจะเสร็จแล้วและคุณจะดำเนินการรอทันทีt3) หากt1ใช้เวลาดำเนินการนานที่สุดเมื่อคุณกลับจากทั้งสองt1และt2จะกลับทันทีโดยไม่มีการปิดกั้น
Maksim Skurydzin

1
คุณไม่เข้าใจคำถามของฉัน - หากฉันคัดลอกรหัสด้านบนไปยังรหัสของฉัน - จะใช้ได้หรือไม่? หรือฉันขาดอะไรไป?
Inbar Rose

2
โอเคเข้าใจแล้ว. ตอนนี้ฉันเข้าใจแล้วสับสนเล็กน้อยเกี่ยวกับเรื่องนี้ แต่ฉันคิดว่าฉันเข้าใจjoinประเภทของการแนบกระบวนการปัจจุบันเข้ากับเธรดและรอจนกว่าจะเสร็จสิ้นและถ้า t2 เสร็จสิ้นก่อน t1 เมื่อ t1 เสร็จแล้วจะตรวจสอบว่า t2 เสร็จเรียบร้อยแล้ว นั่นคือจากนั้นตรวจสอบ t3..etc..etc .. จากนั้นเมื่อทำเสร็จแล้วเท่านั้นจึงจะดำเนินการต่อ น่ากลัว
Inbar Rose

3
พูดว่า t1 ใช้เวลานานที่สุด แต่ t2 มีข้อยกเว้น แล้วจะเกิดอะไรขึ้น? คุณสามารถจับข้อยกเว้นนั้นหรือตรวจสอบว่า t2 เสร็จเรียบร้อยหรือไม่?
Ciprian Tomoiagă

174

ใส่เธรดในรายการจากนั้นใช้วิธีเข้าร่วม

 threads = []

 t = Thread(...)
 threads.append(t)

 ...repeat as often as necessary...

 # Start all threads
 for x in threads:
     x.start()

 # Wait for all of them to finish
 for x in threads:
     x.join()

1
ใช่ว่าจะได้ผล แต่เข้าใจยากกว่า คุณควรพยายามหาจุดสมดุลระหว่างโค้ดขนาดกะทัดรัดและ "ความสามารถในการอ่าน" ข้อควรจำ: รหัสเขียนครั้งเดียว แต่อ่านหลายครั้ง ดังนั้นจึงสำคัญกว่าที่จะเข้าใจได้ง่าย
Aaron Digulla

2
"รูปแบบโรงงาน" ไม่ใช่สิ่งที่ฉันสามารถอธิบายได้ในประโยคเดียว Google และค้นหา stackoverflow.com มีตัวอย่างและคำอธิบายมากมาย สรุป: คุณเขียนโค้ดซึ่งสร้างสิ่งที่ซับซ้อนสำหรับคุณ เหมือนโรงงานจริง: คุณสั่งซื้อและรับสินค้าสำเร็จรูปคืน
Aaron Digulla

18
ฉันไม่ชอบแนวคิดในการใช้ความเข้าใจในรายการเนื่องจากเป็นผลข้างเคียงและไม่ได้ทำอะไรที่เป็นประโยชน์กับรายการผลลัพธ์ ห่วงง่าย ๆ จะสะอาดกว่าแม้ว่ามันจะกระจายสองแถวก็ตาม ...
Ioan Alexandru Cucu

1
@Aaron DIgull ฉันเข้าใจสิ่งนั้นสิ่งที่ฉันหมายถึงคือฉันจะทำfor x in threads: x.join()แทนที่จะใช้การรวบรวมรายการ
Ioan Alexandru Cucu

1
@IoanAlexandruCucu: ฉันยังสงสัยอยู่ว่ามีวิธีแก้ปัญหาที่อ่านง่ายและมีประสิทธิภาพ
มากกว่านี้ไหม

29

ใน Python3 เนื่องจาก Python 3.2 มีวิธีการใหม่ในการเข้าถึงผลลัพธ์เดียวกันโดยส่วนตัวแล้วฉันชอบการสร้างเธรด / start / join แบบดั้งเดิมแพ็คเกจconcurrent.futures: https://docs.python.org/3/library/concurrent.futures .html

การใช้ThreadPoolExecutorรหัสจะเป็น:

from concurrent.futures.thread import ThreadPoolExecutor
import time

def call_script(ordinal, arg):
    print('Thread', ordinal, 'argument:', arg)
    time.sleep(2)
    print('Thread', ordinal, 'Finished')

args = ['argumentsA', 'argumentsB', 'argumentsC']

with ThreadPoolExecutor(max_workers=2) as executor:
    ordinal = 1
    for arg in args:
        executor.submit(call_script, ordinal, arg)
        ordinal += 1
print('All tasks has been finished')

ผลลัพธ์ของรหัสก่อนหน้ามีลักษณะดังนี้:

Thread 1 argument: argumentsA
Thread 2 argument: argumentsB
Thread 1 Finished
Thread 2 Finished
Thread 3 argument: argumentsC
Thread 3 Finished
All tasks has been finished

ข้อดีอย่างหนึ่งคือคุณสามารถควบคุมการตั้งค่าปริมาณงานสูงสุดพร้อมกันได้


แต่คุณจะรู้ได้อย่างไรว่าเมื่อเธรดทั้งหมดในเธรดพูลเสร็จสิ้นแล้ว?
Prime By Design

1
ดังที่คุณเห็นในตัวอย่างรหัสหลังwithคำสั่งจะถูกดำเนินการเมื่องานทั้งหมดเสร็จสิ้น
Roberto

ไม่ได้ผล ลองทำเธรดที่ยาวมาก ๆ คำสั่งพิมพ์ของคุณจะดำเนินการก่อนที่เธรดจะเสร็จสิ้น
Pranalee

@Pranalee รหัสนั้นใช้งานได้ฉันได้อัปเดตรหัสเพื่อเพิ่มบรรทัดผลลัพธ์ คุณไม่สามารถเห็น "งานทั้งหมด ... " ก่อนที่เธรดทั้งหมดจะเสร็จสิ้นนั่นคือวิธีการwithทำงานของคำสั่งโดยการออกแบบในกรณีนี้ อย่างไรก็ตามคุณสามารถเปิดคำถามใหม่ใน SO และโพสต์รหัสของคุณได้ตลอดเวลาเพื่อให้เราสามารถช่วยคุณค้นหาว่าเกิดอะไรขึ้นในกรณีของคุณ
Roberto

@PrimeByDesign คุณสามารถใช้concurrent.futures.waitฟังก์ชันได้คุณสามารถดูตัวอย่างจริงได้ที่นี่ เอกสารทางการ: docs.python.org/3/library/…
Alexander Fortin

28

ฉันชอบใช้ความเข้าใจในรายการตามรายการอินพุต:

inputs = [scriptA + argumentsA, scriptA + argumentsB, ...]
threads = [Thread(target=call_script, args=(i)) for i in inputs]
[t.start() for t in threads]
[t.join() for t in threads]

คำตอบที่ตรวจสอบแล้วอธิบายได้ดี แต่คำตอบนี้สั้นกว่าและไม่ต้องการคำซ้ำที่น่าเกลียด เป็นเพียงคำตอบที่ดี :)
tleb

ความเข้าใจในรายการสำหรับผลข้างเคียงมักจะคิดค่าเสื่อมราคา * แต่ในกรณีการใช้งานนี้ดูเหมือนจะเป็นความคิดที่ดี * stackoverflow.com/questions/5753597/…
Vinayak Kaniyarakkal

3
@VinayakKaniyarakkal for t in threads:t.start()ไม่ดีกว่าเหรอ
SmartManoj

5

คุณสามารถมีคลาสเช่นด้านล่างซึ่งคุณสามารถเพิ่ม 'n' จำนวนฟังก์ชันหรือ console_scripts ที่คุณต้องการดำเนินการด้วยความหลงใหลแบบคู่ขนานและเริ่มการดำเนินการและรอให้งานทั้งหมดเสร็จสิ้น ..

from multiprocessing import Process

class ProcessParallel(object):
    """
    To Process the  functions parallely

    """    
    def __init__(self, *jobs):
        """
        """
        self.jobs = jobs
        self.processes = []

    def fork_processes(self):
        """
        Creates the process objects for given function deligates
        """
        for job in self.jobs:
            proc  = Process(target=job)
            self.processes.append(proc)

    def start_all(self):
        """
        Starts the functions process all together.
        """
        for proc in self.processes:
            proc.start()

    def join_all(self):
        """
        Waits untill all the functions executed.
        """
        for proc in self.processes:
            proc.join()


def two_sum(a=2, b=2):
    return a + b

def multiply(a=2, b=2):
    return a * b


#How to run:
if __name__ == '__main__':
    #note: two_sum, multiply can be replace with any python console scripts which
    #you wanted to run parallel..
    procs =  ProcessParallel(two_sum, multiply)
    #Add all the process in list
    procs.fork_processes()
    #starts  process execution 
    procs.start_all()
    #wait until all the process got executed
    procs.join_all()

นี่คือการประมวลผลหลายขั้นตอน คำถามเกี่ยวกับdocs.python.org/3/library/threading.html
Rustam A.

3

จากthreading เอกสารประกอบโมดูล

มีวัตถุ "เธรดหลัก"; สิ่งนี้สอดคล้องกับเธรดเริ่มต้นของการควบคุมในโปรแกรม Python ไม่ใช่เธรด daemon

มีความเป็นไปได้ที่ "วัตถุด้ายหลอก" ถูกสร้างขึ้น นี่คือเธรดออบเจ็กต์ที่สอดคล้องกับ“ เธรดเอเลี่ยน” ซึ่งเป็นเธรดของการควบคุมที่เริ่มต้นนอกโมดูลเธรดเช่นโดยตรงจากโค้ด C วัตถุด้ายแบบจำลองมีฟังก์ชันการทำงานที่ จำกัด พวกมันมักจะถูกมองว่ามีชีวิตและไร้เดียงสาและไม่สามารถjoin()แก้ไขได้ พวกเขาจะไม่ถูกลบเนื่องจากเป็นไปไม่ได้ที่จะตรวจพบการสิ้นสุดของเธรดเอเลี่ยน

ดังนั้นหากต้องการจับสองกรณีนี้เมื่อคุณไม่สนใจที่จะเก็บรายชื่อเธรดที่คุณสร้างไว้:

import threading as thrd


def alter_data(data, index):
    data[index] *= 2


data = [0, 2, 6, 20]

for i, value in enumerate(data):
    thrd.Thread(target=alter_data, args=[data, i]).start()

for thread in thrd.enumerate():
    if thread.daemon:
        continue
    try:
        thread.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err.args[0]:
            # catchs main thread
            continue
        else:
            raise

ครั้นแล้ว:

>>> print(data)
[0, 4, 12, 40]

2

อาจจะมีบางอย่างเช่น

for t in threading.enumerate():
    if t.daemon:
        t.join()

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

1

ฉันเพิ่งเจอปัญหาเดียวกันที่ฉันต้องรอเธรดทั้งหมดที่สร้างขึ้นโดยใช้ for loop ฉันเพิ่งลองใช้โค้ดต่อไปนี้มันอาจไม่ใช่วิธีแก้ปัญหาที่สมบูรณ์แบบ แต่ฉันคิดว่ามันจะเป็นวิธีง่ายๆ ทดสอบ:

for t in threading.enumerate():
    try:
        t.join()
    except RuntimeError as err:
        if 'cannot join current thread' in err:
            continue
        else:
            raise
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.