मल्टीप्रोसेसिंग: प्रक्रियाओं के बीच एक बड़ी रीड-ओनली ऑब्जेक्ट साझा करना?


107

क्या कार्यक्रम में पहले बनाई गई मल्टीप्रोसेसिंग शेयर ऑब्जेक्ट्स के माध्यम से बच्चे की प्रक्रियाओं को जन्म दिया जाता है ?

मेरे पास निम्न सेटअप है:

do_some_processing(filename):
    for line in file(filename):
        if line.split(',')[0] in big_lookup_object:
            # something here

if __name__ == '__main__':
    big_lookup_object = marshal.load('file.bin')
    pool = Pool(processes=4)
    print pool.map(do_some_processing, glob.glob('*.data'))

मैं किसी बड़ी वस्तु को मेमोरी में लोड कर रहा हूं, फिर श्रमिकों का एक पूल बना रहा हूं जिसे उस बड़ी वस्तु का उपयोग करने की आवश्यकता है। बड़े ऑब्जेक्ट को केवल पढ़ने के लिए एक्सेस किया जाता है, मुझे प्रक्रियाओं के बीच इसके संशोधनों को पारित करने की आवश्यकता नहीं है।

मेरा प्रश्न है: क्या बड़ी वस्तु को साझा मेमोरी में लोड किया जाता है, जैसे कि अगर मैं यूनिक्स / सी में एक प्रक्रिया को जन्म देता हूं, या प्रत्येक प्रक्रिया बड़ी वस्तु की अपनी कॉपी को लोड करती है?

अपडेट: आगे स्पष्ट करने के लिए - big_lookup_object एक साझा लुकअप ऑब्जेक्ट है। मुझे इसे अलग करने और इसे अलग से संसाधित करने की आवश्यकता नहीं है। मुझे इसकी एक प्रति रखने की आवश्यकता है। जिस काम के लिए मुझे विभाजित करने की आवश्यकता है वह बहुत सारी अन्य बड़ी फ़ाइलों को पढ़ रहा है और लुकिंग ऑब्जेक्ट के विरुद्ध उन बड़ी फ़ाइलों में आइटम देख रहा है।

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


लिखित रूप में आपका कोड marshal.loadमाता-पिता और प्रत्येक बच्चे के लिए (प्रत्येक प्रक्रिया मॉड्यूल आयात करता है) कॉल करेगा ।
JFS

तुम सही हो, सही किया गया।
परांद

"स्थानीय इन-मेमोरी" के लिए और यदि आप कॉपी करने से बचना चाहते हैं तो उपयोगी हो सकता है docs.python.org/library/…
jfs

शेयर नं। स्पॉन्डेड प्रक्रियाएं (उदाहरण के लिए कांटा या निष्पादन) कॉलिंग प्रक्रिया का एक सटीक डुप्लिकेट है ... लेकिन विभिन्न मेमोरी में। एक प्रक्रिया के लिए दूसरे से बात करने के लिए, आपको कुछ साझा मेमोरी स्थान पर इंटरप्रोसेस संचार या आईपीसी पढ़ने / लिखने की आवश्यकता होती है ।
रॉन

जवाबों:


50

"क्या बच्चे की प्रक्रिया कार्यक्रम में पहले से बनाई गई मल्टीप्रोसेसिंग शेयर ऑब्जेक्ट्स के माध्यम से पैदा हुई है?"

नहीं (3.8 से पहले अजगर), और हाँ में 3.8 ( https://docs.python.org/3/library/multiprocessing.sared_memory.html#module-multiprocessing.saring_memory )

प्रक्रियाओं में स्वतंत्र मेमोरी स्पेस होता है।

समाधान 1

बहुत सारे श्रमिकों के साथ एक बड़ी संरचना का सबसे अच्छा उपयोग करने के लिए, यह करें।

  1. प्रत्येक कार्यकर्ता को "फ़िल्टर" के रूप में लिखें - स्टड से मध्यवर्ती परिणाम पढ़ता है, काम करता है, स्टडआउट पर मध्यवर्ती परिणाम लिखता है।

  2. सभी श्रमिकों को पाइपलाइन के रूप में कनेक्ट करें:

    process1 <source | process2 | process3 | ... | processn >result

प्रत्येक प्रक्रिया पढ़ती है, काम करती है और लिखती है।

यह उल्लेखनीय रूप से कुशल है क्योंकि सभी प्रक्रियाएं समवर्ती रूप से चल रही हैं। प्रक्रियाओं के बीच साझा बफ़र्स के माध्यम से लिखता है और पढ़ता है।


समाधान २

कुछ मामलों में, आपके पास एक अधिक जटिल संरचना है - अक्सर एक "फैन-आउट" संरचना। इस मामले में आपके पास कई बच्चों के साथ माता-पिता हैं।

  1. जनक स्रोत डेटा खोलता है। माता-पिता कई बच्चों की तलाश करते हैं।

  2. माता-पिता स्रोत को पढ़ते हैं, स्रोत के कुछ हिस्सों को प्रत्येक चल रहे बच्चे तक पहुंचाते हैं।

  3. जब माता-पिता अंत तक पहुंचते हैं, तो पाइप बंद करें। बच्चे को फ़ाइल का अंत मिलता है और सामान्य रूप से खत्म होता है।

बच्चे के हिस्से लिखने में सुखद होते हैं क्योंकि प्रत्येक बच्चा बस पढ़ता है sys.stdin

माता-पिता के पास सभी बच्चों को जगाने और पाइप को ठीक से बनाए रखने में थोड़ा सा फूटवर्क है, लेकिन यह बहुत बुरा नहीं है।

फैन-इन विपरीत संरचना है। स्वतंत्र रूप से चलने वाली प्रक्रियाओं की एक संख्या को उनके इनपुट को एक सामान्य प्रक्रिया में अंतर करने की आवश्यकता होती है। कलेक्टर को लिखना इतना आसान नहीं है, क्योंकि इसे कई स्रोतों से पढ़ना पड़ता है।

कई नामित पाइपों से पढ़ना अक्सर selectमॉड्यूल का उपयोग करके यह देखने के लिए किया जाता है कि किस पाइप में लंबित इनपुट है।


समाधान 3

साझा लुकअप डेटाबेस की परिभाषा है।

समाधान 3 ए - एक डेटाबेस लोड करें। कार्यकर्ता डेटाबेस में डेटा को प्रोसेस करने दें।

समाधान 3B - WSGI अनुप्रयोगों को प्रदान करने के लिए werkzeug (या समान) का उपयोग करके एक बहुत ही सरल सर्वर बनाएं जो HTTP GET का जवाब देता है ताकि कार्यकर्ता सर्वर को क्वेरी कर सकें।


समाधान ४

साझा किए गए फ़ाइल सिस्टम ऑब्जेक्ट। यूनिक्स ओएस साझा मेमोरी ऑब्जेक्ट प्रदान करता है। ये केवल फाइलें हैं जो मेमोरी में मैप की जाती हैं ताकि स्वैपिंग आई / ओ को अधिक कन्वेंशन बफ़र्ड रीड की बजाय किया जाए।

आप इसे पायथन संदर्भ से कई तरीकों से कर सकते हैं

  1. एक स्टार्टअप प्रोग्राम लिखें जो (1) आपकी मूल विशाल वस्तु को छोटी वस्तुओं में तोड़ता है, और (2) प्रत्येक छोटी वस्तु के साथ श्रमिकों को शुरू करता है। छोटे ऑब्जेक्ट्स को फ़ाइल पढ़ने के समय का एक छोटा सा हिस्सा बचाने के लिए पायथन ऑब्जेक्ट्स को चुना जा सकता है।

  2. एक स्टार्टअप प्रोग्राम लिखें, जो (1) आपकी मूल विशाल वस्तु को पढ़ता है और एक पेज-संरचित, बाइट-कोडेड फ़ाइल को लिखता है, seekजो यह सुनिश्चित करने के लिए ऑपरेशन का उपयोग करता है कि अलग-अलग वर्गों को सरल खोज के साथ ढूंढना आसान है। यह वही है जो एक डेटाबेस इंजन करता है - डेटा को पृष्ठों में तोड़ता है, प्रत्येक पृष्ठ को एक के माध्यम से पता लगाना आसान बनाता है seek

    इस बड़े पृष्ठ संरचित फ़ाइल को एक्सेस करने वाले स्पॉन कर्मी। प्रत्येक कार्यकर्ता संबंधित हिस्सों की तलाश कर सकता है और वहां अपना काम कर सकता है।


मेरी प्रक्रिया वास्तव में फिट नहीं हैं; वे सभी समान हैं, बस डेटा के विभिन्न टुकड़ों को संसाधित कर रहे हैं।
परंड

उन्हें अक्सर फ़िल्टर के रूप में संरचित किया जा सकता है। वे अपने डेटा के टुकड़े को पढ़ते हैं, अपना काम करते हैं, और बाद के प्रसंस्करण के लिए अपना परिणाम लिखते हैं।
लोट

मुझे आपका समाधान पसंद है, लेकिन अवरुद्ध I / O के साथ क्या होता है? यदि माता-पिता अपने बच्चों में से / से पढ़ने / लिखने को रोकते हैं तो क्या होगा? चयन आपको सूचित करता है कि आप लिख सकते हैं, लेकिन यह नहीं कहता कि कितना। पढ़ने के लिए एक ही।
21:39 पर क्रिस्टियन सियुपिटु

ये अलग-अलग प्रक्रियाएं हैं - माता-पिता और बच्चे एक-दूसरे के साथ हस्तक्षेप नहीं करते हैं। एक पाइप के एक छोर पर उत्पादित प्रत्येक बाइट तुरंत भस्म होने के दूसरे छोर पर उपलब्ध है - एक पाइप एक साझा बफर है। इस संदर्भ में आपके प्रश्न का क्या अर्थ है, यह निश्चित नहीं है।
एस.लॉट

मैं सत्यापित कर सकता हूं कि S.Lott ने क्या कहा। मुझे एक ही फ़ाइल पर किए गए समान संचालन की आवश्यकता थी। तो पहले कार्यकर्ता ने प्रत्येक पंक्ति पर संख्या 2% == 0 के साथ अपना कार्य किया और इसे एक फ़ाइल में सहेजा, और अन्य पंक्तियों को अगली पाइप की गई प्रक्रिया (जो कि एक ही स्क्रिप्ट थी) को भेज दिया। रनटाइम आधे से नीचे चला गया। यह थोड़ा हैकरी है, लेकिन ओवरहेड मल्टीप्रोसेसिंग मॉड्यूल में मैप / पूप की तुलना में बहुत हल्का है।
विंस

36

क्या कार्यक्रम में पहले बनाई गई मल्टीप्रोसेसिंग शेयर ऑब्जेक्ट्स के माध्यम से बच्चे की प्रक्रियाओं को जन्म दिया जाता है ?

निर्भर करता है। वैश्विक रीड-ओनली वेरिएबल्स के लिए इसे अक्सर माना जा सकता है (इसके अलावा खपत की गई मेमोरी के अलावा) इसे नहीं करना चाहिए।

मल्टीप्रोसेसिंग के दस्तावेज़ कहते हैं:

Better to inherit than pickle/unpickle

विंडोज़ पर मल्टीप्रोसेसिंग से कई प्रकारों को अचार बनाने की आवश्यकता होती है ताकि बच्चे की प्रक्रियाएं उनका उपयोग कर सकें। हालांकि, आम तौर पर पाइप या कतारों का उपयोग करके साझा वस्तुओं को अन्य प्रक्रियाओं में भेजने से बचना चाहिए। इसके बजाय आपको कार्यक्रम की व्यवस्था करनी चाहिए ताकि एक प्रक्रिया जिसे अन्य जगहों पर साझा किए गए संसाधन तक पहुंच की आवश्यकता हो, उसे पूर्वजों की प्रक्रिया से विरासत में मिल सके।

Explicitly pass resources to child processes

यूनिक्स पर एक बच्चे की प्रक्रिया एक वैश्विक संसाधन का उपयोग करके मूल प्रक्रिया में निर्मित साझा संसाधन का उपयोग कर सकती है। हालांकि, बच्चे की प्रक्रिया के लिए निर्माता के तर्क के रूप में ऑब्जेक्ट को पास करना बेहतर है।

कोड (संभावित) को विंडोज के साथ संगत करने के अलावा यह भी सुनिश्चित करता है कि जब तक बच्चे की प्रक्रिया अभी भी जीवित है तब तक ऑब्जेक्ट मूल प्रक्रिया में एकत्र नहीं किया जाएगा। यह महत्वपूर्ण हो सकता है यदि कुछ संसाधन को मुक्त किया जाता है जब ऑब्जेक्ट को मूल प्रक्रिया में एकत्र किया जाता है।

Global variables

इस बात को ध्यान में रखें कि यदि बाल प्रक्रिया में चलाया जाने वाला कोड वैश्विक चर को एक्सेस करने की कोशिश करता है, तो वह मान (यदि कोई हो) उस समय मूल प्रक्रिया में मान के समान नहीं हो सकता है, जिसे Process.start () कहा जाता था। ।

उदाहरण

विंडोज पर (सिंगल सीपीयू):

#!/usr/bin/env python
import os, sys, time
from multiprocessing import Pool

x = 23000 # replace `23` due to small integers share representation
z = []    # integers are immutable, let's try mutable object

def printx(y):
    global x
    if y == 3:
       x = -x
    z.append(y)
    print os.getpid(), x, id(x), z, id(z) 
    print y
    if len(sys.argv) == 2 and sys.argv[1] == "sleep":
       time.sleep(.1) # should make more apparant the effect

if __name__ == '__main__':
    pool = Pool(processes=4)
    pool.map(printx, (1,2,3,4))

के साथ sleep:

$ python26 test_share.py sleep
2504 23000 11639492 [1] 10774408
1
2564 23000 11639492 [2] 10774408
2
2504 -23000 11639384 [1, 3] 10774408
3
4084 23000 11639492 [4] 10774408
4

इसके बिना sleep:

$ python26 test_share.py
1148 23000 11639492 [1] 10774408
1
1148 23000 11639492 [1, 2] 10774408
2
1148 -23000 11639324 [1, 2, 3] 10774408
3
1148 -23000 11639324 [1, 2, 3, 4] 10774408
4

6
है ना? कैसे z प्रक्रियाओं के बीच साझा किया जा रहा है ??
जुबेर

4
@cbare: अच्छा सवाल! z वास्तव में साझा नहीं किया गया है, क्योंकि नींद शो के साथ आउटपुट है। नींद के बिना आउटपुट से पता चलता है कि एक एकल प्रक्रिया (पीआईडी ​​= 1148) सभी काम संभालती है; अंतिम उदाहरण में हम जो देखते हैं वह इस एकल प्रक्रिया के लिए z का मान है।
एरिक ओ लेबिगोट

यह उत्तर दिखाता है कि zसाझा नहीं किया गया है। इस प्रकार इस प्रश्न का उत्तर दिया जाता है: "नहीं, कम से कम विंडोज के तहत, बच्चों के बीच एक अभिभावक चर साझा नहीं किया जाता है"।
एरिक ओ लेबिगॉट

@ ईओएल: तकनीकी रूप से आप सही हैं लेकिन व्यवहार में यदि डेटा आसानी से ( zमामले के विपरीत ) साझा किया जा सकता है।
JFS

बस स्पष्ट करने के लिए, कथन को ध्यान में रखें कि यदि एक बच्चे की प्रक्रिया में कोड रन एक वैश्विक चर का उपयोग करने की कोशिश करता है ... 2.7 डॉक्स में विंडोज के तहत चलने वाले पायथन को संदर्भित करता है।
user1071847

28

एस.लॉट सही है। पाइथन के मल्टीप्रोसेसिंग शॉर्टकट आपको प्रभावी रूप से मेमोरी का एक अलग, डुप्लिकेट किया हुआ हिस्सा देते हैं।

अधिकांश * nix सिस्टम पर, निम्न-स्तरीय कॉल का उपयोग करते हुए os.fork(), वास्तव में, आपको कॉपी-ऑन-राइट मेमोरी देते हैं, जो कि आप जो सोच रहे हैं वह हो सकता है। AFAIK, सिद्धांत रूप में, कार्यक्रमों के सबसे सरल में, आप उस डेटा को बिना डुप्लिकेट किए पढ़ सकते हैं।

हालांकि, चीजें पायथन दुभाषिया में काफी सरल नहीं हैं। ऑब्जेक्ट डेटा और मेटा-डेटा को एक ही मेमोरी सेगमेंट में संग्रहीत किया जाता है, इसलिए भले ही ऑब्जेक्ट कभी भी न बदले, उस ऑब्जेक्ट के लिए रेफ़रेंस काउंटर की तरह कुछ बढ़ाए जाने से मेमोरी राइट हो जाएगा, और इसलिए एक कॉपी। लगभग किसी भी पायथन कार्यक्रम जो "प्रिंट 'हैलो' से अधिक कर रहा है, संदर्भ गणना में वृद्धि का कारण होगा, इसलिए आपको संभवतः कॉपी-ऑन-राइट के लाभ का एहसास नहीं होगा।

यहां तक ​​कि अगर किसी ने पायथन में साझा-मेमोरी समाधान को हैक करने का प्रबंधन किया, तो प्रक्रियाओं के दौरान कचरा संग्रह को समन्वित करने की कोशिश करना संभवतः बहुत दर्दनाक होगा।


3
केवल उस गिनती में मेमोरी क्षेत्र की प्रतिलिपि बनाई जाएगी, जरूरी नहीं कि बड़े रीड-ओनली डेटा ही हों, है न?
बजे kawing-chiu

7

यदि आप यूनिक्स के तहत चल रहे हैं, तो वे एक ही वस्तु को साझा कर सकते हैं, कांटा कैसे काम करता है (यानी, बच्चे की प्रक्रियाओं में अलग-अलग मेमोरी होती है, लेकिन यह कॉपी-ऑन-राइट है, इसलिए इसे तब तक साझा किया जा सकता है जब तक कोई इसे संशोधित नहीं करता)। मैंने निम्नलिखित कोशिश की:

import multiprocessing

x = 23

def printx(y):
    print x, id(x)
    print y

if __name__ == '__main__':
    pool = multiprocessing.Pool(processes=4)
    pool.map(printx, (1,2,3,4))

और निम्न आउटपुट मिला:

$ ./mtest.py
२३ २२ ९९ ५६५६
1
२३ २२ ९९ ५६५६
2
२३ २२ ९९ ५६५६
3
२३ २२ ९९ ५६५६
4

बेशक यह साबित नहीं करता है कि एक प्रतिलिपि नहीं बनाई गई है, लेकिन आपको यह सत्यापित करने में सक्षम होना चाहिए कि आपकी स्थिति में psयह देखने के लिए कि प्रत्येक उपप्रकार कितना वास्तविक मेमोरी का उपयोग कर रहा है।


2
कचरा उठाने वाले के बारे में क्या? जब यह चलता है तो क्या होता है? क्या मेमोरी लेआउट नहीं बदलता है?
क्रिस्टियन सियुपिटु

यह एक वैध चिंता का विषय है। क्या यह प्रभावित करेगा परांद इस पर निर्भर करेगा कि वह इस सब का उपयोग कैसे कर रहा है और यह कोड कितना विश्वसनीय है। अगर यह उसके लिए काम नहीं कर रहा था, तो मैं और अधिक नियंत्रण के लिए mmap मॉड्यूल का उपयोग करने की सलाह दूंगा (यह मानते हुए कि वह इस मूल दृष्टिकोण के साथ रहना चाहता है)।
जैकब गैब्रिएल्सन

मैंने आपके उदाहरण के लिए एक अपडेट पोस्ट किया है: stackoverflow.com/questions/659865/…
jfs

@JacobGabrielson: प्रतिलिपि बनाई गई है। मूल प्रश्न यह है कि क्या कॉपी बनाई गई है।
अभिनवकुलकर्णी

3

विभिन्न प्रक्रियाओं में अलग-अलग पते की जगह होती है। जैसे दुभाषिया के विभिन्न उदाहरणों को चलाना। यही IPC (इंटरप्रोसेस कम्युनिकेशन) है।

आप इस उद्देश्य के लिए या तो कतारों या पाइपों का उपयोग कर सकते हैं। यदि आप किसी नेटवर्क पर प्रक्रियाओं को बाद में वितरित करना चाहते हैं तो आप tcp पर rpc का उपयोग कर सकते हैं।

http://docs.python.org/dev/library/multiprocessing.html#exchanging-objects-between-processes


2
मुझे नहीं लगता कि इसके लिए आईपीसी उचित होगा; यह रीड-ओनली डेटा है जिसकी हर किसी को पहुँच की आवश्यकता है। कोई अर्थ नहीं यह प्रक्रियाओं के बीच गुजर रहा है; सबसे खराब प्रत्येक अपनी प्रति पढ़ सकता है। मैं प्रत्येक प्रक्रिया में एक अलग प्रतिलिपि नहीं होने से स्मृति को बचाने का प्रयास कर रहा हूं।
परंड

आपके पास अन्य गुलाम प्रक्रियाओं पर काम करने के लिए डेटा के टुकड़े सौंपने की एक मास्टर प्रक्रिया हो सकती है। या तो दास डेटा के लिए पूछ सकते हैं या यह डेटा को धक्का दे सकता है। इस तरह हर प्रक्रिया में पूरी वस्तु की एक प्रति नहीं होगी।
वासिल

1
@Vasil: क्या होगा यदि प्रत्येक प्रक्रिया को पूरे डेटा सेट की आवश्यकता है, और बस इस पर एक अलग ऑपरेशन चल रहा है?
विल

1

सीधे तौर पर मल्टीप्रोसेसिंग प्रति से संबंधित नहीं है, लेकिन आपके उदाहरण से, ऐसा लगता है कि आप बस शेल्व मॉड्यूल या ऐसा कुछ उपयोग कर सकते हैं । क्या "big_lookup_object" वास्तव में पूरी तरह से मेमोरी में होना है?


अच्छी बात यह है कि, मैंने ऑन-डिस्क बनाम मेमोरी के प्रदर्शन की सीधे तुलना नहीं की है। मैंने मान लिया था कि एक बड़ा अंतर होगा, लेकिन मैंने वास्तव में परीक्षण नहीं किया है।
परांद

1

नहीं, लेकिन आप अपने डेटा को एक बच्चे की प्रक्रिया के रूप में लोड कर सकते हैं और इसे अन्य बच्चों के साथ अपने डेटा को साझा करने की अनुमति दे सकते हैं। निचे देखो।

import time
import multiprocessing

def load_data( queue_load, n_processes )

    ... load data here into some_variable

    """
    Store multiple copies of the data into
    the data queue. There needs to be enough
    copies available for each process to access. 
    """

    for i in range(n_processes):
        queue_load.put(some_variable)


def work_with_data( queue_data, queue_load ):

    # Wait for load_data() to complete
    while queue_load.empty():
        time.sleep(1)

    some_variable = queue_load.get()

    """
    ! Tuples can also be used here
    if you have multiple data files
    you wish to keep seperate.  
    a,b = queue_load.get()
    """

    ... do some stuff, resulting in new_data

    # store it in the queue
    queue_data.put(new_data)


def start_multiprocess():

    n_processes = 5

    processes = []
    stored_data = []

    # Create two Queues
    queue_load = multiprocessing.Queue()
    queue_data = multiprocessing.Queue()

    for i in range(n_processes):

        if i == 0:
            # Your big data file will be loaded here...
            p = multiprocessing.Process(target = load_data,
            args=(queue_load, n_processes))

            processes.append(p)
            p.start()   

        # ... and then it will be used here with each process
        p = multiprocessing.Process(target = work_with_data,
        args=(queue_data, queue_load))

        processes.append(p)
        p.start()

    for i in range(n_processes)
        new_data = queue_data.get()
        stored_data.append(new_data)    

    for p in processes:
        p.join()
    print(processes)    

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.