Python: Як я можу паралельно виконувати функції python?


109

Я дослідив першим і не зміг знайти відповіді на своє запитання. Я намагаюся паралельно виконувати кілька функцій у Python.

У мене є щось подібне:

files.py

import common #common is a util class that handles all the IO stuff

dir1 = 'C:\folder1'
dir2 = 'C:\folder2'
filename = 'test.txt'
addFiles = [25, 5, 15, 35, 45, 25, 5, 15, 35, 45]

def func1():
   c = common.Common()
   for i in range(len(addFiles)):
       c.createFiles(addFiles[i], filename, dir1)
       c.getFiles(dir1)
       time.sleep(10)
       c.removeFiles(addFiles[i], dir1)
       c.getFiles(dir1)

def func2():
   c = common.Common()
   for i in range(len(addFiles)):
       c.createFiles(addFiles[i], filename, dir2)
       c.getFiles(dir2)
       time.sleep(10)
       c.removeFiles(addFiles[i], dir2)
       c.getFiles(dir2)

Я хочу зателефонувати func1 та func2, щоб вони одночасно працювали. Функції не взаємодіють одна з одною або на одному об'єкті. Зараз я повинен зачекати, коли func1 закінчиться, перш ніж func2 почати. Як зробити щось на кшталт нижче:

process.py

from files import func1, func2

runBothFunc(func1(), func2())

Я хочу мати можливість створювати обидва каталоги досить близько до того ж часу, оскільки щохвилини я рахую, скільки файлів створюється. Якщо каталогі там немає, це скине мої терміни.


1
Ви можете реконструювати це; якщо ви підраховуєте кількість файлів / папок щохвилини, ви створюєте умову гонки. А як щодо того, щоб кожна функція оновлювала лічильник або використовувала файл блокування, щоб переконатися, що періодичний процес не оновлює кількість, поки обидві функції не завершить виконання?

Відповіді:


163

Ви можете використовувати threadingабо multiprocessing.

З - за особливості CPython , threadingнавряд чи досягнуть справжнього паралелізму. З цієї причини, multiprocessingяк правило, краща ставка.

Ось повний приклад:

from multiprocessing import Process

def func1():
  print 'func1: starting'
  for i in xrange(10000000): pass
  print 'func1: finishing'

def func2():
  print 'func2: starting'
  for i in xrange(10000000): pass
  print 'func2: finishing'

if __name__ == '__main__':
  p1 = Process(target=func1)
  p1.start()
  p2 = Process(target=func2)
  p2.start()
  p1.join()
  p2.join()

Механіку запуску / приєднання дочірніх процесів можна легко включити у функцію, що відповідає вашим runBothFunc:

def runInParallel(*fns):
  proc = []
  for fn in fns:
    p = Process(target=fn)
    p.start()
    proc.append(p)
  for p in proc:
    p.join()

runInParallel(func1, func2)

4
Я використовував ваш код, але функції все одно не запускалися одночасно.
lmcadory

4
@Lamar McAdory: Будь ласка, поясніть, що саме ви маєте на увазі під "одночасно", можливо, наведіть конкретний приклад того, що ви зробили, що ви очікували, що відбудеться, і що насправді сталося.
NPE

4
@Lamar: Ви ніколи не можете мати жодної гарантії "точно в той самий час", і думати, що ви можете, це просто неправильно. Залежно від того, скільки у вас є процесора, навантаження машини, терміни багатьох речей, що відбуваються на комп'ютері, все впливатиме на час запуску ниток / процесу. Крім того, оскільки процеси запускаються відразу після створення, накладні витрати на створення процесу також повинні бути розраховані в різниці у часі, яку ви бачите.
Мартін

1
чи можливо отримати список результатів кожної функції? скажімо, кожна функція повертає інше значення, чи можна додавати значення до якогось списку, який можна використовувати пізніше? можливо, додавання результату до глобального списку?
pelos

1
Якщо мої функції приймають параметри і коли я передаю параметри під час виклику їх з окремих процесів, вони не запускаються одночасно. Чи можете ви допомогти
user2910372

18

Це можна зробити елегантно за допомогою системи Ray , яка дозволяє легко паралелізувати та розподілити код Python.

Щоб паралелізувати свій приклад, вам потрібно визначити свої функції з @ray.remoteдекоратором, а потім викликати їх .remote.

import ray

ray.init()

dir1 = 'C:\\folder1'
dir2 = 'C:\\folder2'
filename = 'test.txt'
addFiles = [25, 5, 15, 35, 45, 25, 5, 15, 35, 45]

# Define the functions. 
# You need to pass every global variable used by the function as an argument.
# This is needed because each remote function runs in a different process,
# and thus it does not have access to the global variables defined in 
# the current process.
@ray.remote
def func1(filename, addFiles, dir):
    # func1() code here...

@ray.remote
def func2(filename, addFiles, dir):
    # func2() code here...

# Start two tasks in the background and wait for them to finish.
ray.get([func1.remote(filename, addFiles, dir1), func2.remote(filename, addFiles, dir2)]) 

Якщо ви передаєте один і той же аргумент обом функціям і аргумент великий, більш ефективним способом зробити це є використання ray.put(). Це дозволяє уникнути великого аргументу, який слід двічі серіалізувати та створити дві його копії:

largeData_id = ray.put(largeData)

ray.get([func1(largeData_id), func2(largeData_id)])

Якщо func1()і func2()повертати результати, вам потрібно переписати код наступним чином:

ret_id1 = func1.remote(filename, addFiles, dir1)
ret_id2 = func1.remote(filename, addFiles, dir2)
ret1, ret2 = ray.get([ret_id1, ret_id2])

Існує ряд переваг використання Ray над багатопроцесорним модулем. Зокрема, той самий код працюватиме як на одній машині, так і на кластері машин. Більше переваг Рея дивіться у цьому пов’язаному дописі .


18

Якщо ваші функції в основному виконують роботу введення-виводу (і менше роботи процесора) і у вас Python 3.2+, ви можете використовувати ThreadPoolExecutor :

from concurrent.futures import ThreadPoolExecutor

def run_io_tasks_in_parallel(tasks):
    with ThreadPoolExecutor() as executor:
        running_tasks = [executor.submit(task) for task in tasks]
        for running_task in running_tasks:
            running_task.result()

run_io_tasks_in_parallel([
    lambda: print('IO task 1 running!'),
    lambda: print('IO task 2 running!'),
])

Якщо ваші функції в основному виконують роботу процесора (і менше роботи вводу / виводу) і у вас Python 2.6+, ви можете використовувати багатопроцесорний модуль:

from multiprocessing import Process

def run_cpu_tasks_in_parallel(tasks):
    running_tasks = [Process(target=task) for task in tasks]
    for running_task in running_tasks:
        running_task.start()
    for running_task in running_tasks:
        running_task.join()

run_cpu_tasks_in_parallel([
    lambda: print('CPU task 1 running!'),
    lambda: print('CPU task 2 running!'),
])

Це хороша відповідь. Як визначити з результату для завдань, пов'язаних з входом / виводом, використовуючи concurrent.futures, які виконано? В основному замість функцій lamba, якщо ми маємо нормальні функції, як визначити результат, відображений на функцію, що називається?
Tragaknight

Не забудьте, я знайшов спосіб - замість цього run_cpu_tasks_in_parallel ([lambda: print ('CPU завдання 1 працює!'), Lambda: print ('CPU завдання 2 працює!'),]) Використовуйте це - results = run_io_tasks_in_parallel ([lambda: {'is_something1': func1 ()}, лямбда: {'is_something2': func2 ()},])
Tragaknight

5

Якщо ви користувач Windows та використовуєте python 3, то ця публікація допоможе вам паралельно програмувати в python.При запуску звичайного багатопроцесорного програмування пулу бібліотеки ви отримаєте помилку щодо основної функції у вашій програмі. Це пояснюється тим, що Windows не має функціоналу fork (). Наведений нижче пост дає вирішення згаданої проблеми.

http://python.6.x6.nabble.com/Multiprocessing-Pool-woes-td5047050.html

Оскільки я використовував python 3, я трохи змінив програму так:

from types import FunctionType
import marshal

def _applicable(*args, **kwargs):
  name = kwargs['__pw_name']
  code = marshal.loads(kwargs['__pw_code'])
  gbls = globals() #gbls = marshal.loads(kwargs['__pw_gbls'])
  defs = marshal.loads(kwargs['__pw_defs'])
  clsr = marshal.loads(kwargs['__pw_clsr'])
  fdct = marshal.loads(kwargs['__pw_fdct'])
  func = FunctionType(code, gbls, name, defs, clsr)
  func.fdct = fdct
  del kwargs['__pw_name']
  del kwargs['__pw_code']
  del kwargs['__pw_defs']
  del kwargs['__pw_clsr']
  del kwargs['__pw_fdct']
  return func(*args, **kwargs)

def make_applicable(f, *args, **kwargs):
  if not isinstance(f, FunctionType): raise ValueError('argument must be a function')
  kwargs['__pw_name'] = f.__name__  # edited
  kwargs['__pw_code'] = marshal.dumps(f.__code__)   # edited
  kwargs['__pw_defs'] = marshal.dumps(f.__defaults__)  # edited
  kwargs['__pw_clsr'] = marshal.dumps(f.__closure__)  # edited
  kwargs['__pw_fdct'] = marshal.dumps(f.__dict__)   # edited
  return _applicable, args, kwargs

def _mappable(x):
  x,name,code,defs,clsr,fdct = x
  code = marshal.loads(code)
  gbls = globals() #gbls = marshal.loads(gbls)
  defs = marshal.loads(defs)
  clsr = marshal.loads(clsr)
  fdct = marshal.loads(fdct)
  func = FunctionType(code, gbls, name, defs, clsr)
  func.fdct = fdct
  return func(x)

def make_mappable(f, iterable):
  if not isinstance(f, FunctionType): raise ValueError('argument must be a function')
  name = f.__name__    # edited
  code = marshal.dumps(f.__code__)   # edited
  defs = marshal.dumps(f.__defaults__)  # edited
  clsr = marshal.dumps(f.__closure__)  # edited
  fdct = marshal.dumps(f.__dict__)  # edited
  return _mappable, ((i,name,code,defs,clsr,fdct) for i in iterable)

Після цієї функції вищезазначений код проблеми також трохи змінюється так:

from multiprocessing import Pool
from poolable import make_applicable, make_mappable

def cube(x):
  return x**3

if __name__ == "__main__":
  pool    = Pool(processes=2)
  results = [pool.apply_async(*make_applicable(cube,x)) for x in range(1,7)]
  print([result.get(timeout=10) for result in results])

І результат я отримав як:

[1, 8, 27, 64, 125, 216]

Я думаю, що ця публікація може бути корисною для деяких користувачів Windows.


4

Ні в якому разі не можна гарантувати, що дві функції будуть виконуватися синхронно між собою, що, здається, саме те, що ви хочете зробити.

Найкраще, що ви можете зробити, це розділити функцію на кілька етапів, а потім дочекатися закінчення обох критичних точок синхронізації, використовуючи Process.joinзгадки відповіді @ aix.

Це краще, ніж time.sleep(10)тому, що ви не можете гарантувати точні терміни. Явно чекаючи, ви говорите, що функції потрібно виконати, виконуючи цей крок, перш ніж перейти до наступного, замість того, щоб припустити, що він буде виконаний протягом 10 мс, що не гарантується з урахуванням того, що ще відбувається на машині.


1

Здається, у вас є одна функція, яку потрібно викликати за двома різними параметрами. Це можна елегантно зробити за допомогою комбінації concurrent.futuresта mapз Python 3.2+

import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

def sleep_secs(seconds):
  time.sleep(seconds)
  print(f'{seconds} has been processed')

secs_list = [2,4, 6, 8, 10, 12]

Тепер, якщо ваша операція пов'язана з IO, ви можете використовувати ThreadPoolExecutorяк таке:

with ThreadPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)

Зверніть увагу, як mapтут використовуєтьсяmap ваша функція до списку аргументів.

Тепер, якщо ваша функція пов'язана з процесором, ви можете використовувати її ProcessPoolExecutor

with ProcessPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)

Якщо ви не впевнені, ви можете просто спробувати обидва і побачити, який з них дає кращі результати.

Нарешті, якщо ви хочете роздрукувати свої результати, ви можете просто зробити це:

with ThreadPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)
  for result in results:
    print(result)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.