Використання багатопроцесорної обробки. Обробка з максимальною кількістю одночасних процесів


78

У мене є Pythonкод:

from multiprocessing import Process

def f(name):
    print 'hello', name

if __name__ == '__main__':
    for i in range(0, MAX_PROCESSES):
        p = Process(target=f, args=(i,))
        p.start()

який працює добре. Однак MAX_PROCESSESє змінною і може мати будь-яке значення між 1і 512. Оскільки я запускаю цей код лише на машині з 8ядрами, мені потрібно з’ясувати, чи можна обмежити кількість процесів, дозволених для запуску одночасно. Я вивчав multiprocessing.Queue, але це не схоже на те, що мені потрібно - або, можливо, я неправильно трактую документи.

Чи є спосіб обмежити кількість одночасних multiprocessing.Processзапусків?


для i в діапазоні (0, хв (MAX_PROCESSES, 8)):
Яків

@Jacob Я все ще хочу, щоб усі MAX_PROCESSES працювали. Наведений вище код скорочений для простоти, але основна функція викликається до 512 разів (отже, цикл). Тож мені цікаво, чи існує спосіб черги процесів.
Бретт

2
отже, ви хочете налаштування майстра / працівника, і ви хочете обмежити кількість робітників?
Яків

@Jacob Так, це може бути кращим способом сформулювати це.
Бретт

Відповіді:


105

Можливо, найбільш розумним буде використання, multiprocessing.Poolяке створює пул робочих процесів на основі максимальної кількості ядер, доступних у вашій системі, а потім, в основному, подає завдання, коли ядра стають доступними.

Приклад зі стандартних документів ( http://docs.python.org/2/library/multiprocessing.html#using-a-pool-of-workers ) показує, що ви також можете вручну встановити кількість ядер:

from multiprocessing import Pool

def f(x):
    return x*x

if __name__ == '__main__':
    pool = Pool(processes=4)              # start 4 worker processes
    result = pool.apply_async(f, [10])    # evaluate "f(10)" asynchronously
    print result.get(timeout=1)           # prints "100" unless your computer is *very* slow
    print pool.map(f, range(10))          # prints "[0, 1, 4,..., 81]"

І також зручно знати, що існує multiprocessing.cpu_count()метод підрахунку кількості ядер у даній системі, якщо це потрібно у вашому коді.

Редагувати: Ось декілька проектів коду, які, здається, працюють для вашого конкретного випадку:

import multiprocessing

def f(name):
    print 'hello', name

if __name__ == '__main__':
    pool = multiprocessing.Pool() #use all available cores, otherwise specify the number you want as an argument
    for i in xrange(0, 512):
        pool.apply_async(f, args=(i,))
    pool.close()
    pool.join()

Гаразд, я склав версію, яка, здається, чудово працює для вашого конкретного випадку та додана до публікації вище.
тредді

50
multiprocessing.cpu_count()-1 or 1може бути корисною евристикою для вирішення, скільки процесів виконувати паралельно: -1 дозволяє уникнути блокування системи, монополізуючи всі ядра, але якщо доступний лише один процесор, то це orдає витончений запас для одноядерного запуску.
andybuckley

Що робити, якщо моя функція має важку роботу і мало обробки? Використання 10 потоків на 4-ядерній машині будь-яким чином вплине на програму?
Абхідемон,

3
зверніть увагу, що multiprocessing.cpu_count()це не кількість ядер, а кількість потоків (у сенсі гіперпотоків).
Grismar

1
Я зміг скоротити важкий внутрішній час обробки нічних запланованих завдань у своєму додатку з ~ 20 хвилин до ~ 8 хвилин, використовуючи те, що ви зазначили вище. Дякую @treddy!
Фергюс

11

Я думаю, що ви шукаєте Semaphore, він заблокує основний процес після відліку до 0. Приклад коду:

from multiprocessing import Process
from multiprocessing import Semaphore
import time

def f(name, sema):
    print('process {} starting doing business'.format(name))
    # simulate a time-consuming task by sleeping
    time.sleep(5)
    # `release` will add 1 to `sema`, allowing other 
    # processes blocked on it to continue
    sema.release()

if __name__ == '__main__':
    concurrency = 20
    total_task_num = 1000
    sema = Semaphore(concurrency)
    all_processes = []
    for i in range(total_task_num):
        # once 20 processes are running, the following `acquire` call
        # will block the main process since `sema` has been reduced
        # to 0. This loop will continue only after one or more 
        # previously created processes complete.
        sema.acquire()
        p = Process(target=f, args=(i, sema))
        all_processes.append(p)
        p.start()

    # inside main process, wait for all processes to finish
    for p in all_processes:
        p.join()

Наступний код є більш структурованим, оскільки він отримує та випускає semaв тій самій функції. Однак він споживає занадто багато ресурсів, якщо total_task_numдуже великий:

from multiprocessing import Process
from multiprocessing import Semaphore
import time

def f(name, sema):
    print('process {} starting doing business'.format(name))
    # `sema` is acquired and released in the same
    # block of code here, making code more readable,
    # but may lead to problem.
    sema.acquire()
    time.sleep(5)
    sema.release()

if __name__ == '__main__':
    concurrency = 20
    total_task_num = 1000
    sema = Semaphore(concurrency)
    all_processes = []
    for i in range(total_task_num):
        p = Process(target=f, args=(i, sema))
        all_processes.append(p)
        # the following line won't block after 20 processes
        # have been created and running, instead it will carry 
        # on until all 1000 processes are created.
        p.start()

    # inside main process, wait for all processes to finish
    for p in all_processes:
        p.join()

Наведений вище код буде створювати total_task_numпроцеси, але працюватимуть лише concurrencyпроцеси, тоді як інші процеси будуть заблоковані, споживаючи цінні системні ресурси.


Це чудово! Також вирішує проблему з PicklingError для речей, які пітон не може замаринувати
YotamWIS Constantini

Я не впевнений, що це щось я роблю неправильно, але мій sema.release () ніколи не виникає при використанні першого блоку коду з випуском у функції f, але набуває в основному. У когось коли-небудь була така проблема? Дурна помилка?
user1983682

4

загальніше, це також може виглядати так:

import multiprocessing
def chunks(l, n):
    for i in range(0, len(l), n):
        yield l[i:i + n]

numberOfThreads = 4


if __name__ == '__main__':
    jobs = []
    for i, param in enumerate(params):
        p = multiprocessing.Process(target=f, args=(i,param))
        jobs.append(p)
    for i in chunks(jobs,numberOfThreads):
        for j in i:
            j.start()
        for j in i:
            j.join()

Звичайно, такий спосіб є досить жорстоким (оскільки він чекає кожного процесу у смітті, поки не продовжиться з наступним шматком). Тим не менше, він добре працює приблизно приблизно однаковий час роботи викликів функції.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.