पायथन: मैं समानांतर में पायथन कार्यों को कैसे चला सकता हूं?


109

मैंने पहले शोध किया और अपने प्रश्न का उत्तर नहीं पा सका। मैं पायथन में समानांतर में कई कार्यों को चलाने की कोशिश कर रहा हूं।

मेरे पास कुछ इस तरह है:

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 को कॉल करना चाहता हूं और उन्हें उसी समय चलाना है। फ़ंक्शंस एक दूसरे के साथ या एक ही वस्तु पर बातचीत नहीं करते हैं। अभी मुझे func2 के शुरू होने से पहले खत्म होने के लिए func1 का इंतजार करना है। मैं नीचे की तरह कुछ कैसे करूँ:

process.py

from files import func1, func2

runBothFunc(func1(), func2())

मैं दोनों निर्देशिकाओं को एक ही समय में बहुत करीब बनाने में सक्षम होना चाहता हूं क्योंकि हर मिनट मैं गिन रहा हूं कि कितनी फाइलें बनाई जा रही हैं। अगर निर्देशिका नहीं है तो यह मेरी टाइमिंग को समाप्त कर देगा।


1
आप इसे फिर से आर्किटेक्ट करना चाह सकते हैं; यदि आप हर मिनट फ़ाइलों / फ़ोल्डरों की संख्या की गिनती कर रहे हैं, तो आप एक दौड़ की स्थिति बना रहे हैं। प्रत्येक फ़ंक्शन को एक काउंटर अपडेट करने के बारे में, या यह सुनिश्चित करने के लिए लॉकफ़ाइल का उपयोग करें कि आवधिक प्रक्रिया गिनती को अपडेट नहीं करती है जब तक कि दोनों फ़ंक्शन निष्पादित नहीं होते हैं?

जवाबों:


163

आप threadingया उपयोग कर सकते हैं multiprocessing।

सीपीथॉन की ख़ासियतों के कारण , 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
@ लैमर मैकएडरी: कृपया समझाएं कि "एक ही समय में" से आपका क्या मतलब है, शायद आपने जो किया उसका ठोस उदाहरण देते हुए, आप जो होने की उम्मीद कर रहे थे, और वास्तव में क्या हुआ था।
— एनपीई

4
@ लमार: आपके पास "बिल्कुल समान समय" की कोई गारंटी नहीं हो सकती है और यह सोचना कि आप सीधे सादे गलत हैं। आपके पास कितने cpus हैं, मशीन का भार, कंप्यूटर पर होने वाली कई चीजों के समय पर निर्भर करता है कि थ्रेड्स / प्रक्रिया शुरू होने के समय सभी पर प्रभाव पड़ेगा। साथ ही, चूंकि निर्माण के ठीक बाद प्रक्रियाएं शुरू की जाती हैं, इसलिए एक प्रक्रिया बनाने के ओवरहेड की गणना आपके द्वारा देखे गए समय अंतर में भी की जानी चाहिए।
— मार्टिन

1
क्या प्रत्येक फ़ंक्शन के परिणामों की सूची प्राप्त करना संभव है? मान लें कि प्रत्येक फ़ंक्शन एक अलग मान लौटाता है, क्या मूल्यों को किसी सूची में जोड़ा जा सकता है जिसे बाद में उपयोग किया जा सकता है? शायद परिणाम को वैश्विक सूची में जोड़ दिया जाए?
— पेलोस

1
यदि मेरे कार्य पैरामीटर लेते हैं और जब मैं अलग-अलग प्रक्रियाओं से कॉल करते समय पैरामीटर पास करता हूं, तो वे एक साथ नहीं चलते हैं। क्या आप मदद कर सकते हैं
— user2910372

18

यह रे के साथ सुरुचिपूर्ण ढंग से किया जा सकता है , एक प्रणाली जो आपको अपने पायथन कोड को आसानी से समानांतर और वितरित करने की अनुमति देती है।

अपने उदाहरण को समानांतर करने के लिए, आपको @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])

मल्टीप्रोसेसिंग मॉड्यूल पर रे का उपयोग करने के कई फायदे हैं । विशेष रूप से, एक ही कोड एक मशीन के साथ-साथ मशीनों के एक क्लस्टर पर चलेगा। रे के अधिक फायदे के लिए इस संबंधित पोस्ट को देखें ।


18

अपने कार्यों मुख्य रूप से कर रहे हैं मैं / हे काम (और कम CPU काम) और आपके पास अजगर 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!'),
])

यदि आपके कार्य मुख्य रूप से CPU काम कर रहे हैं (और I / O काम कम) और आपके पास पायथन 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!'),
])

यह एक अच्छा जवाब है। I / O के लिए परिणाम से कैसे पहचानें कि समवर्ती कार्यों का उपयोग करके। कौन सा कार्य पूरा हुआ? मूल रूप से लांबा कार्यों के बजाय अगर हमारे पास सामान्य कार्य हैं, तो परिणाम को फ़ंक्शन के लिए मैप कैसे किया जाए?
— त्रिकटनाइट

कोई बात नहीं, मुझे एक रास्ता मिल गया - इस run_cpu_tasks_in_parallel ([lambda: Print ('CPU task 1 रनिंग!') के बजाय, lambda: print ('CPU task 2 रनिंग!'),] इसे उपयोग करें - results_ run_io_tasks_in_parallel ([lambda:] {[is_something1 ': func1 ()), lambda: {' is_something2 ': func2 ()},])
— Tragaknight

5

यदि आप एक विंडोज़ उपयोगकर्ता हैं और अजगर 3 का उपयोग कर रहे हैं, तो यह पोस्ट आपको python. समानांतर में प्रोग्रामिंग करने में मदद करेगी। जब आप एक सामान्य मल्टीप्रोसेसिंग लाइब्रेरी की पूल प्रोग्रामिंग चलाते हैं, तो आपको अपने प्रोग्राम में मुख्य फ़ंक्शन के बारे में एक त्रुटि मिलेगी। यह इस तथ्य के कारण है कि विंडोज़ में कोई कांटा () कार्यक्षमता नहीं है। नीचे दी गई पोस्ट में वर्णित समस्या का समाधान दिया गया है।

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

चूँकि मैं अजगर 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]

मैं सोच रहा हूं कि यह पोस्ट कुछ विंडोज़ उपयोगकर्ताओं के लिए उपयोगी हो सकती है।


4

यह गारंटी देने का कोई तरीका नहीं है कि दो फ़ंक्शन एक-दूसरे के साथ सिंक करेंगे, जो ऐसा लगता है कि आप क्या करना चाहते हैं।

सबसे अच्छा आप यह कर सकते हैं कि फ़ंक्शन को कई चरणों में विभाजित करें, फिर दोनों का उपयोग करके महत्वपूर्ण सिंक्रनाइज़ेशन बिंदुओं पर समाप्त होने की प्रतीक्षा करें Process.join @ aix के उत्तर उल्लेखों का ।

यह बेहतर है time.sleep(10)क्योंकि आप सटीक समय की गारंटी नहीं दे सकते। स्पष्ट रूप से प्रतीक्षा के साथ, आप कह रहे हैं कि कार्यों को अगले चरण में जाने से पहले उस चरण को निष्पादित करना होगा, यह मानने के बजाय कि यह 10ms के भीतर किया जाएगा जो मशीन पर और क्या चल रहा है, इसके आधार पर गारंटी नहीं है।


1

लगता है कि आपके पास एक ही फ़ंक्शन है जिसे आपको दो अलग-अलग मापदंडों पर कॉल करने की आवश्यकता है। यह शान से concurrent.futuresऔर mapपायथन 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 Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.