Мертвий простий приклад використання багатопроцесорної черги, пулу та блокування


91

Я намагався прочитати документацію за адресою 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)

Відповіді:


129

Найкраще рішення для вашої проблеми - використовувати 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 нижче:

Якщо вам потрібен "блокування для кожного ліміту пулу", щоб ваші процеси працювали в тандемних парах, ала:

Чекає В чекає | Готово, Б зроблено | 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

Дякуємо за простий і прямий приклад, як це зробити, але як я можу застосувати блокування для кожного ліміту пулу? Я маю на увазі, якщо ви виконуєте код, я хотів би бачити щось на кшталт "Чекає B чекає | A готово, b зроблено | C чекає, D чекає | C зроблено, D зроблено"
thclpr

2
Іншими словами, ви не хочете, щоб C починався, поки не закінчаться і A, і B?
Велімір Млакер

Точно, я можу це зробити за допомогою багатопроцесорної обробки.
Процес,

Щиро дякую, працюйте за призначенням, але на функції mp_handler ви посилаєтесь на дані змінної замість var1 :)
thclpr

Добре, дякую, я var1взагалі видалив , dataнатомість маючи на увазі глобальний .
Велімір Млакер

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

Ось мій особистий перехід до цієї теми:

Додайте сюди, (вітайте запити на витягування!): 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 03.03.17

Так, .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 потоків
  • Тоді у вас є купа for, які додають завдання до пулу
  • pool.submit(execute_run, rp) додає завдання до пулу, спочатку arogument - це функція, що викликається в потоці / процесі, решта аргументів передається у викликану функцію.
  • pool.join чекає, поки всі завдання будуть виконані.

2
Зверніть увагу, що ви використовуєте concurrent.futures, але OP запитує про multiprocessingта Python 2.7.
Тім Петерс 02
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.