Python: Bagaimana saya bisa menjalankan fungsi python secara paralel?

109

Saya meneliti terlebih dahulu dan tidak dapat menemukan jawaban atas pertanyaan saya. Saya mencoba menjalankan banyak fungsi secara paralel dengan Python.

Saya punya sesuatu seperti ini:

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)

Saya ingin memanggil func1 dan func2 dan menjalankannya pada waktu yang sama. Fungsi tidak berinteraksi satu sama lain atau pada objek yang sama. Sekarang saya harus menunggu func1 selesai sebelum func2 dimulai. Bagaimana saya melakukan sesuatu seperti di bawah ini:

process.py

from files import func1, func2

runBothFunc(func1(), func2())

Saya ingin dapat membuat kedua direktori pada waktu yang hampir bersamaan karena setiap menit saya menghitung berapa banyak file yang sedang dibuat. Jika direktori tidak ada di sana, itu akan membuang waktu saya.

lmcadory.dll
sumber
1
Anda mungkin ingin merancang ulang ini; jika Anda menghitung jumlah file / folder setiap menit, Anda membuat kondisi balapan. Bagaimana jika setiap fungsi memperbarui penghitung, atau menggunakan lockfile untuk memastikan bahwa proses periodik tidak memperbarui penghitungan hingga kedua fungsi selesai dijalankan?

Jawaban:

164

Anda bisa menggunakan threadingatau multiprocessing.

Karena kekhasan CPython , threadingtidak mungkin untuk mencapai paralelisme yang sebenarnya. Untuk alasan ini, multiprocessingumumnya taruhan yang lebih baik.

Berikut contoh lengkapnya:

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()

Mekanisme memulai / menggabungkan proses anak dapat dengan mudah diringkas menjadi fungsi di sepanjang baris Anda 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)
NPE
sumber
4
Saya menggunakan kode Anda tetapi fungsinya masih tidak dimulai pada saat yang bersamaan.
lmcadory
4
@ Lamar McAdory: Tolong jelaskan apa yang Anda maksud dengan "pada saat yang sama", mungkin memberikan contoh konkret tentang apa yang Anda lakukan, apa yang Anda harapkan akan terjadi, dan apa yang sebenarnya terjadi.
NPE
4
@ Lamar: Anda tidak akan pernah bisa memiliki jaminan untuk "waktu yang persis sama" dan berpikir Anda bisa benar-benar salah. Bergantung pada berapa banyak CPU yang Anda miliki, beban mesin, waktu dari banyak hal yang terjadi di komputer semuanya akan berpengaruh pada saat utas / proses dimulai. Selain itu, karena proses dimulai tepat setelah pembuatan, overhead pembuatan proses juga harus dihitung dalam perbedaan waktu yang Anda lihat.
Martin
1
apakah mungkin untuk mendapatkan daftar hasil dari setiap fungsi? katakanlah setiap fungsi mengembalikan nilai yang berbeda, dapatkah nilai ditambahkan ke beberapa daftar yang dapat digunakan nanti? mungkin menambahkan hasilnya ke daftar global?
pelos
1
Jika fungsi saya mengambil parameter dan ketika saya melewatkan parameter sambil memanggilnya dari proses terpisah, mereka tidak berjalan secara bersamaan. Bisakah Anda membantu
pengguna2910372
18

Ini dapat dilakukan dengan elegan dengan Ray , sebuah sistem yang memungkinkan Anda untuk dengan mudah memparalelkan dan mendistribusikan kode Python Anda.

Untuk memparalelkan contoh Anda, Anda perlu mendefinisikan fungsi Anda dengan @ray.remotedekorator, dan kemudian memanggilnya dengan .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)]) 

Jika Anda meneruskan argumen yang sama ke kedua fungsi dan argumennya besar, cara yang lebih efisien untuk melakukannya adalah dengan menggunakan ray.put(). Ini menghindari argumen besar untuk diserialkan dua kali dan untuk membuat dua salinan memori darinya:

largeData_id = ray.put(largeData)

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

Jika func1()dan func2()mengembalikan hasil, Anda perlu menulis ulang kode sebagai berikut:

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

Ada sejumlah keuntungan menggunakan Ray dibandingkan modul multiprosesing . Secara khusus, kode yang sama akan berjalan di satu mesin serta di sekumpulan mesin. Untuk lebih banyak keuntungan dari Ray lihat posting terkait ini .

Ion Stoica
sumber
18

Jika fungsi Anda terutama melakukan pekerjaan I / O (dan lebih sedikit pekerjaan CPU) dan Anda memiliki Python 3.2+, Anda dapat menggunakan 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!'),
])

Jika fungsi Anda terutama melakukan pekerjaan CPU (dan lebih sedikit pekerjaan I / O) dan Anda memiliki Python 2.6+, Anda dapat menggunakan modul multiprosesing :

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!'),
])
David Foster
sumber
Ini jawaban yang bagus. Bagaimana cara mengidentifikasi dari hasil untuk tugas terikat I / O menggunakan concurrent.futures yang mana yang diselesaikan? Pada dasarnya alih-alih fungsi lamba jika kita memiliki fungsi normal, bagaimana mengidentifikasi hasil yang dipetakan ke fungsi yang dipanggil?
Tragaknight
Nevermind saya menemukan cara - alih-alih run_cpu_tasks_in_parallel ini ([lambda: print ('CPU task 1 running!'), Lambda: print ('CPU task 2 running!'),]) Gunakan this - results = run_io_tasks_in_parallel ([lambda: {'is_something1': func1 ()}, lambda: {'is_something2': func2 ()},])
Tragaknight
5

Jika Anda adalah pengguna windows dan menggunakan python 3, maka posting ini akan membantu Anda untuk melakukan pemrograman paralel di python. Ketika Anda menjalankan pemrograman kumpulan perpustakaan multiprosesing biasa, Anda akan mendapatkan kesalahan mengenai fungsi utama dalam program Anda. Ini karena fakta bahwa windows tidak memiliki fungsionalitas fork (). Posting di bawah ini memberikan solusi untuk masalah yang disebutkan.

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

Karena saya menggunakan python 3, saya mengubah programnya sedikit seperti ini:

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)

Setelah fungsi ini, kode masalah di atas juga sedikit berubah seperti ini:

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])

Dan saya mendapatkan hasilnya sebagai:

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

Saya berpikir bahwa posting ini mungkin bermanfaat bagi beberapa pengguna windows.

Arun Sooraj
sumber
4

Tidak ada cara untuk menjamin bahwa dua fungsi akan dijalankan secara sinkron satu sama lain yang sepertinya ingin Anda lakukan.

Hal terbaik yang dapat Anda lakukan adalah membagi fungsi menjadi beberapa langkah, lalu tunggu keduanya selesai pada titik sinkronisasi kritis menggunakan Process.joinseperti jawaban @ aix yang menyebutkan.

Ini lebih baik daripada time.sleep(10)karena Anda tidak dapat menjamin pengaturan waktu yang tepat. Dengan menunggu secara eksplisit, Anda mengatakan bahwa fungsi harus dilakukan dengan mengeksekusi langkah itu sebelum pindah ke langkah berikutnya, alih-alih mengasumsikan itu akan dilakukan dalam 10 md yang tidak dijamin berdasarkan apa lagi yang terjadi di mesin.

Davy8
sumber
1

Sepertinya Anda memiliki satu fungsi yang perlu Anda panggil pada dua parameter berbeda. Ini dapat dilakukan dengan elegan menggunakan kombinasi concurrent.futuresdan mapdengan 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]

Sekarang, jika operasi Anda terikat IO, Anda dapat menggunakan ThreadPoolExecutorseperti ini:

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

Perhatikan bagaimana mapdigunakan di sini untuk mapfungsi Anda ke daftar argumen.

Sekarang, Jika fungsi Anda terikat dengan CPU, maka Anda dapat menggunakan ProcessPoolExecutor

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

Jika Anda tidak yakin, Anda dapat mencoba keduanya dan melihat mana yang memberi Anda hasil yang lebih baik.

Terakhir, jika Anda ingin mencetak hasil Anda, cukup lakukan ini:

with ThreadPoolExecutor() as executor:
  results = executor.map(sleep_secs, secs_list)
  for result in results:
    print(result)
BICube
sumber