ตัวอย่างง่ายๆของการใช้ Multiprocessing Queue, Pool และ Locking


92

ฉันพยายามอ่านเอกสารที่http://docs.python.org/dev/library/multiprocessing.htmlแต่ฉันยังคงดิ้นรนกับคิวการประมวลผลหลายขั้นตอนพูลและการล็อก และตอนนี้ฉันสามารถสร้างตัวอย่างด้านล่างได้

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

import multiprocessing
import time

data = (['a', '2'], ['b', '4'], ['c', '6'], ['d', '8'],
        ['e', '1'], ['f', '3'], ['g', '5'], ['h', '7']
)


def mp_handler(var1):
    for indata in var1:
        p = multiprocessing.Process(target=mp_worker, args=(indata[0], indata[1]))
        p.start()


def mp_worker(inputs, the_time):
    print " Processs %s\tWaiting %s seconds" % (inputs, the_time)
    time.sleep(int(the_time))
    print " Process %s\tDONE" % inputs

if __name__ == '__main__':
    mp_handler(data)

คำตอบ:


130

ทางออกที่ดีที่สุดสำหรับปัญหาของคุณคือใช้ไฟล์Pool. การใช้Queues และมีฟังก์ชัน "การป้อนคิว" แยกกันนั้นอาจจะมากเกินไป

นี่เป็นเวอร์ชันที่ปรับเปลี่ยนใหม่เล็กน้อยของโปรแกรมของคุณคราวนี้มีเพียง 2 กระบวนการที่รวมอยู่ในไฟล์Pool. ฉันเชื่อว่าเป็นวิธีที่ง่ายที่สุดโดยมีการเปลี่ยนแปลงโค้ดต้นฉบับเพียงเล็กน้อย:

import multiprocessing
import time

data = (
    ['a', '2'], ['b', '4'], ['c', '6'], ['d', '8'],
    ['e', '1'], ['f', '3'], ['g', '5'], ['h', '7']
)

def mp_worker((inputs, the_time)):
    print " Processs %s\tWaiting %s seconds" % (inputs, the_time)
    time.sleep(int(the_time))
    print " Process %s\tDONE" % inputs

def mp_handler():
    p = multiprocessing.Pool(2)
    p.map(mp_worker, data)

if __name__ == '__main__':
    mp_handler()

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

เอาท์พุต:

Processs a  Waiting 2 seconds
Processs b  Waiting 4 seconds
Process a   DONE
Processs c  Waiting 6 seconds
Process b   DONE
Processs d  Waiting 8 seconds
Process c   DONE
Processs e  Waiting 1 seconds
Process e   DONE
Processs f  Waiting 3 seconds
Process d   DONE
Processs g  Waiting 5 seconds
Process f   DONE
Processs h  Waiting 7 seconds
Process g   DONE
Process h   DONE

แก้ไขตามความคิดเห็น @Thales ด้านล่าง:

หากคุณต้องการ "ล็อกสำหรับขีด จำกัด พูลแต่ละรายการ" เพื่อให้กระบวนการของคุณทำงานเป็นคู่ควบคู่กัน:

รอ B รออยู่ | เสร็จแล้ว B เสร็จแล้ว | รอ C รอ D รอ | C เสร็จแล้ว D เสร็จแล้ว | ...

จากนั้นเปลี่ยนฟังก์ชันตัวจัดการเพื่อเรียกใช้พูล (จาก 2 กระบวนการ) สำหรับข้อมูลแต่ละคู่:

def mp_handler():
    subdata = zip(data[0::2], data[1::2])
    for task1, task2 in subdata:
        p = multiprocessing.Pool(2)
        p.map(mp_worker, (task1, task2))

ตอนนี้ผลลัพธ์ของคุณคือ:

 Processs a Waiting 2 seconds
 Processs b Waiting 4 seconds
 Process a  DONE
 Process b  DONE
 Processs c Waiting 6 seconds
 Processs d Waiting 8 seconds
 Process c  DONE
 Process d  DONE
 Processs e Waiting 1 seconds
 Processs f Waiting 3 seconds
 Process e  DONE
 Process f  DONE
 Processs g Waiting 5 seconds
 Processs h Waiting 7 seconds
 Process g  DONE
 Process h  DONE

ขอบคุณสำหรับตัวอย่างวิธีการที่ง่ายและตรงไปตรงมา แต่ฉันจะใช้การล็อกสำหรับขีด จำกัด แต่ละพูลได้อย่างไร ฉันหมายถึงถ้าคุณรันโค้ดฉันอยากเห็นบางอย่างเช่น "A waiting B waiting | A done, b done | C waiting, D waiting | C done, D done"
thclpr

2
กล่าวอีกนัยหนึ่งคุณไม่ต้องการให้ C เริ่มต้นจนกว่าทั้ง A และ B จะเสร็จสิ้น?
Velimir Mlaker

แน่นอนฉันสามารถทำได้โดยใช้การประมวลผลหลายกระบวนการ แต่ฉันคิดไม่ออกว่าจะทำอย่างไรโดยใช้พูล
thclpr

ขอบคุณมากทำงานได้ตามที่ตั้งใจไว้ แต่ในฟังก์ชัน mp_handler คุณกำลังอ้างอิงข้อมูลตัวแปรแทน var1 :)
thclpr

โอเคขอบคุณฉันลบvar1ทั้งหมดโดยอ้างถึงทั่วโลกdataแทน
Velimir Mlaker

8

สิ่งนี้อาจไม่เกี่ยวข้องกับคำถาม 100% แต่ในการค้นหาของฉันสำหรับตัวอย่างของการใช้การประมวลผลหลายขั้นตอนกับคิวสิ่งนี้จะปรากฏเป็นอันดับแรกใน Google

นี่คือคลาสตัวอย่างพื้นฐานที่คุณสามารถสร้างอินสแตนซ์และวางไอเท็มในคิวและสามารถรอจนกว่าคิวจะเสร็จสิ้น นั่นคือทั้งหมดที่ฉันต้องการ

from multiprocessing import JoinableQueue
from multiprocessing.context import Process


class Renderer:
    queue = None

    def __init__(self, nb_workers=2):
        self.queue = JoinableQueue()
        self.processes = [Process(target=self.upload) for i in range(nb_workers)]
        for p in self.processes:
            p.start()

    def render(self, item):
        self.queue.put(item)

    def upload(self):
        while True:
            item = self.queue.get()
            if item is None:
                break

            # process your item here

            self.queue.task_done()

    def terminate(self):
        """ wait until queue is empty and terminate processes """
        self.queue.join()
        for p in self.processes:
            p.terminate()

r = Renderer()
r.render(item1)
r.render(item2)
r.terminate()

2
คืออะไรitem1และitem2? เป็นงานหรือหน้าที่บางอย่างซึ่งจะดำเนินการในสองกระบวนการที่แตกต่างกันหรือไม่?
Zelphir Kaltstahl

2
ใช่เป็นงานหรืออินพุตพารามิเตอร์ที่ได้รับการประมวลผลแบบขนาน
linqu

8

นี่คือ goto ส่วนตัวของฉันสำหรับหัวข้อนี้:

Gist ที่นี่ (ยินดีต้อนรับการดึงคำขอ!): https://gist.github.com/thorsummoner/b5b1dfcff7e7fdd334ec

import multiprocessing
import sys

THREADS = 3

# Used to prevent multiple threads from mixing thier output
GLOBALLOCK = multiprocessing.Lock()


def func_worker(args):
    """This function will be called by each thread.
    This function can not be a class method.
    """
    # Expand list of args into named args.
    str1, str2 = args
    del args

    # Work
    # ...



    # Serial-only Portion
    GLOBALLOCK.acquire()
    print(str1)
    print(str2)
    GLOBALLOCK.release()


def main(argp=None):
    """Multiprocessing Spawn Example
    """
    # Create the number of threads you want
    pool = multiprocessing.Pool(THREADS)

    # Define two jobs, each with two args.
    func_args = [
        ('Hello', 'World',), 
        ('Goodbye', 'World',), 
    ]


    try:
        # Spawn up to 9999999 jobs, I think this is the maximum possible.
        # I do not know what happens if you exceed this.
        pool.map_async(func_worker, func_args).get(9999999)
    except KeyboardInterrupt:
        # Allow ^C to interrupt from any thread.
        sys.stdout.write('\033[0m')
        sys.stdout.write('User Interupt\n')
    pool.close()

if __name__ == '__main__':
    main()

1
ฉันไม่แน่ใจว่า. map_async () ดีกว่า. map () แต่อย่างใด
ThorSummoner

3
อาร์กิวเมนต์ถึงget()หมดเวลาไม่มีส่วนเกี่ยวข้องกับจำนวนงานที่เริ่มต้น
mata

@mata นั่นหมายถึงใช้ในวงสำรวจหรือไม่? .get(timeout=1)เหรอ? แล้วจะบอกว่า.get()รับรายการเสร็จได้ไหม
ThorSummoner

ใช่.get()รออย่างไม่มีกำหนดจนกว่าผลลัพธ์ทั้งหมดจะพร้อมใช้งานและส่งกลับรายการผลลัพธ์ คุณสามารถใช้การวนรอบการสำรวจเพื่อตรวจสอบผลสภาพอากาศที่พร้อมใช้งานหรือคุณสามารถส่งผ่านฟังก์ชันการโทรกลับในการmap_async()โทรซึ่งจะเรียกใช้สำหรับทุกผลลัพธ์เมื่อพร้อมใช้งาน
mata

2

สำหรับทุกคนที่ใช้โปรแกรมแก้ไขเช่น Komodo Edit (win10) sys.stdout.flush()ให้เพิ่มไปที่:

def mp_worker((inputs, the_time)):
    print " Process %s\tWaiting %s seconds" % (inputs, the_time)
    time.sleep(int(the_time))
    print " Process %s\tDONE" % inputs
    sys.stdout.flush()

หรือเป็นบรรทัดแรกเพื่อ:

    if __name__ == '__main__':
       sys.stdout.flush()

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


1

นี่คือตัวอย่างจากโค้ดของฉัน (สำหรับเธรดพูล แต่เพียงแค่เปลี่ยนชื่อคลาสแล้วคุณจะมีพูลกระบวนการ):

def execute_run(rp): 
   ... do something 

pool = ThreadPoolExecutor(6)
for mat in TESTED_MATERIAL:
    for en in TESTED_ENERGIES:
        for ecut in TESTED_E_CUT:
            rp = RunParams(
                simulations, DEST_DIR,
                PARTICLE, mat, 960, 0.125, ecut, en
            )
            pool.submit(execute_run, rp)
pool.join()

โดยทั่วไป:

  • pool = ThreadPoolExecutor(6) สร้างพูลสำหรับ 6 เธรด
  • จากนั้นคุณมีของสำหรับจำนวนมากที่เพิ่มงานให้กับพูล
  • pool.submit(execute_run, rp) เพิ่มงานลงในพูล arogument แรกคือฟังก์ชันที่เรียกในเธรด / กระบวนการส่วนที่เหลือของอาร์กิวเมนต์จะถูกส่งไปยังฟังก์ชันที่เรียกว่า
  • pool.join รอจนกว่างานทั้งหมดจะเสร็จสิ้น

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