2011-08-09 10 views
24

मैं कुछ ऐसी चीज पर काम कर रहा हूं जो स्मृति-मैप की गई फ़ाइलों (FileChannel.map() के माध्यम से) के साथ-साथ मेमोरी प्रत्यक्ष बाइटबफर से निर्मित ByteBuffers का उपयोग करता है। मैं समवर्ती और स्मृति मॉडल बाधाओं को समझने की कोशिश कर रहा हूं।जावा में सीधे मैप किए गए बाइटबफर पर एकाधिक थ्रेड लिख सकते हैं?

मैंने फाइलचैनेल, बाइटबफर, मैप्डबेट बफर आदि जैसी चीजों के लिए सभी प्रासंगिक जावाडोक (और स्रोत) पढ़ा है। ऐसा लगता है कि एक विशेष बाइटबफर (और प्रासंगिक उप-वर्गों) में फ़ील्ड का एक गुच्छा है और राज्य सुरक्षित नहीं है एक स्मृति मॉडल बिंदु से। इसलिए, यदि आप बफर में उस बफर का उपयोग करते हैं तो किसी विशेष बाइटबफर की स्थिति को संशोधित करते समय आपको सिंक्रनाइज़ करना होगा। आम चाल आदि एक ThreadLocal का उपयोग कर ByteBuffer रैप करने के लिए, डुप्लिकेट (जबकि सिंक्रनाइज़) एक नया उदाहरण एक ही मैप किया बाइट्स की ओर इशारा करते प्राप्त करने के लिए, शामिल

को देखते हुए इस परिदृश्य:

  1. प्रबंधक एक मैप की गई बाइट बफर है B_all पूरी फ़ाइल के लिए (कहें कि यह < 2 जीबी)
  2. प्रबंधक एक नया छोटा बाइटबफर B_1 बनाने के लिए B_all पर डुप्लिकेट(), स्थिति(), सीमा(), और टुकड़ा() कॉल करता है जो फ़ाइल का एक हिस्सा बनाता है और यह देता है थ्रेड टी 1
  3. मैनेजर एक ही सामान बनाने के लिए करता है B_2 ByteBuffer ही मैप किया बाइट्स की ओर इशारा करते हैं और इस टी 2 सूत्र में बाँधना

मेरा प्रश्न है देता है: B_2 को B_1 से T1 लिखने और टी 2 लिखने समवर्ती और एक दूसरे के परिवर्तन देखने के लिए गारंटी दी जा सकते हैं? क्या T3 उन बाइट्स को पढ़ने के लिए B_all का उपयोग कर सकता है और टी 1 और टी 2 दोनों में परिवर्तन देखने की गारंटी हो सकता है?

मुझे पता है कि एक मैप किए गए फ़ाइल में लिखना आवश्यक नहीं है प्रक्रिया पर जब तक आप बल() को डिस्क पर पृष्ठों को लिखने के लिए निर्देश देने के लिए बल() का उपयोग नहीं करते हैं। मुझे इसके बारे में परवाह नहीं है। इस सवाल के लिए मान लें कि यह जेवीएम एकमात्र प्रक्रिया है जो एक मैप की गई फाइल लिख रही है।

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

अद्यतन: मैंने कई परीक्षणों को समानांतर में एक ही फ़ाइल में लिखने के साथ कुछ परीक्षण किए हैं और अब तक ऐसा लगता है कि वे लिखने तुरंत अन्य धागे से दिखाई देते हैं। मुझे यकीन नहीं है कि मैं उस पर भरोसा कर सकता हूं।

+0

मै मैप्डबेट बफर (जावा 7) के लिए एपीआई पढ़ता हूं और वे चेतावनी देते हैं कि इसे केवल पढ़ने/लिखने के लिए इस्तेमाल किया जाना चाहिए, न कि मैनिप्लेशंस। – toto2

+0

हां, मेरा प्रश्न लगभग दो धागे लिख रहा है। मुझे नहीं पता कि "मैनिपुलेशन" से आपका क्या मतलब है। –

+0

मैपडबेट बफर का जावाडॉक: "मैप किए गए बाइट बफर का सभी या हिस्सा किसी भी समय पहुंच योग्य नहीं हो सकता है, उदाहरण के लिए यदि मैप किए गए फ़ाइल को छोटा कर दिया गया है। मैप किए गए बाइट बफर के एक पहुंचने योग्य क्षेत्र तक पहुंचने का प्रयास बफर की सामग्री को नहीं बदलेगा पहुंच के समय या कुछ समय बाद एक अनिर्दिष्ट अपवाद फेंक दिया जा सकता है। इसलिए यह दृढ़ता से अनुशंसा की जाती है कि इस कार्यक्रम द्वारा मैप किए गए फ़ाइल के ** हेरफेर ** से बचने के लिए उचित सावधानी बरतें, या समवर्ती रूप से फ़ाइल की सामग्री को पढ़ने या लिखने के अलावा प्रोग्राम चला रहा है। " – toto2

उत्तर

0

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

+0

हम स्मृति के बारे में बात कर रहे हैं जो डिस्क (ऑफ-हेप) के क्षेत्र में मैप किया गया है। मुझे यह मानने का कोई कारण नहीं है कि इसमें एक ही बाधा है। क्या आप उस प्रभाव का संदर्भ दे सकते हैं? –

1

मुझे लगता है कि प्रत्यक्ष स्मृति समान गारंटी या ढेर स्मृति के रूप में उनकी कमी प्रदान करती है। यदि आप एक बाइटबफर को संशोधित करते हैं जो अंतर्निहित सरणी या प्रत्यक्ष स्मृति पता साझा करता है, तो दूसरा बाइटबफर एक और धागा परिवर्तन देख सकता है, लेकिन ऐसा करने की गारंटी नहीं है।

मुझे संदेह है कि यदि आप सिंक्रनाइज़ या अस्थिरता का उपयोग करते हैं, तो भी यह काम करने की गारंटी नहीं है, हालांकि यह प्लेटफ़ॉर्म के आधार पर ऐसा कर सकता है।

धागे के बीच डेटा को बदलने के लिए एक आसान तरीका एक एक्सचेंजर

उदाहरण के आधार पर,

class FillAndEmpty { 
    final Exchanger<ByteBuffer> exchanger = new Exchanger<ByteBuffer>(); 
    ByteBuffer initialEmptyBuffer = ... a made-up type 
    ByteBuffer initialFullBuffer = ... 

    class FillingLoop implements Runnable { 
    public void run() { 
     ByteBuffer currentBuffer = initialEmptyBuffer; 
     try { 
     while (currentBuffer != null) { 
      addToBuffer(currentBuffer); 
      if (currentBuffer.remaining() == 0) 
      currentBuffer = exchanger.exchange(currentBuffer); 
     } 
     } catch (InterruptedException ex) { ... handle ... } 
    } 
    } 

    class EmptyingLoop implements Runnable { 
    public void run() { 
     ByteBuffer currentBuffer = initialFullBuffer; 
     try { 
     while (currentBuffer != null) { 
      takeFromBuffer(currentBuffer); 
      if (currentBuffer.remaining() == 0) 
      currentBuffer = exchanger.exchange(currentBuffer); 
     } 
     } catch (InterruptedException ex) { ... handle ...} 
    } 
    } 

    void start() { 
    new Thread(new FillingLoop()).start(); 
    new Thread(new EmptyingLoop()).start(); 
    } 
} 
+1

मुझे लगता है कि प्रत्यक्ष स्मृति में हीप मेमोरी के समान गारंटी नहीं है। यदि यह सच है, तो मैं ऐसा कुछ संदर्भ देखना चाहता हूं जो ऐसा कहता है। –

0

उपयोग करने के लिए नहीं, यह सामान्य जावा चर या सरणी तत्वों से अलग नहीं है।

+0

आप ऐसा क्यों कहते हैं? –

+0

यह अलग क्यों नहीं है? अंतर्निहित डेटा जावा चर या सरणी तत्व नहीं है, यह ऑपरेटिंग सिस्टम द्वारा प्रदान की गई मैप मेमोरी है। – EJP

1

बफर द्वारा मैप किए गए डिस्क के हिस्से तक विशेष पहुंच प्राप्त करने के लिए फ़ाइल लॉक का उपयोग कर एक संभावित उत्तर फ़ाइल लॉक का उपयोग कर रहा है। उदाहरण के लिए यह उदाहरण here के साथ समझाया गया है।

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

बेशक, अगर मैं जेवीएम/ओएस द्वारा स्थिरता की गारंटी देता हूं तो मैं फ़ाइल लॉकिंग या पृष्ठ सिंक्रनाइज़ेशन से बचना चाहता हूं।

+0

इसे Concurrency सूची पर पूछने का प्रयास करें। मैंने ऑन-हीप बाइट [] - [http://markmail.org/thread/2sldp7bz4k5aq5ei] और [http://markmail.org/thread/wpcu55p6psarvyho] के संबंध में कुछ समान पूछा था। लेकिन कोई विचार नहीं कि मैप किए गए मेमोरी समरूपता को कैसे संभालती है। –

15

जेवीएम के साथ मेमोरी मैपिंग CreateFileMapping (विंडोज) या mmap (posix) के आसपास सिर्फ एक पतली आवरण है। इस प्रकार, आपके पास ओएस के बफर कैश तक सीधे पहुंच है। इसका अर्थ यह है कि ये बफर वे हैं जो ओएस फ़ाइल को शामिल करने के लिए मानते हैं (और ओएस अंततः फ़ाइल को प्रतिबिंबित करने के लिए सिंक करेगा)।

इसलिए प्रक्रियाओं के बीच सिंक करने के लिए बल() को कॉल करने की आवश्यकता नहीं है। प्रक्रियाएं पहले ही सिंक हो चुकी हैं (ओएस के माध्यम से - एक ही पृष्ठ को पढ़ने/लिखने के लिए भी पढ़ा जाता है)। ओएस और ड्राइव नियंत्रक के बीच सिंचन को मजबूर करना (ड्राइव नियंत्रक और भौतिक platters के बीच कुछ देरी हो सकती है, लेकिन आपके पास इसके बारे में कुछ भी करने के लिए हार्डवेयर समर्थन नहीं है)।

भले ही, मेमोरी मैप की गई फ़ाइलें धागे और/या प्रक्रियाओं के बीच साझा स्मृति का स्वीकार्य रूप हैं। इस साझा स्मृति के बीच एकमात्र अंतर और, कहें, विंडोज़ में वर्चुअल मेमोरी का नामित ब्लॉक डिस्क पर अंतिम सिंक्रनाइज़ेशन है (वास्तव में mmap मैपिंग/dev/null द्वारा फ़ाइल चीज़ के बिना वर्चुअल मेमोरी करता है)।

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

अधिक जानकारी के लिए,

+0

उत्कृष्ट जवाब; शीट को वापस छील दिया और जेडी में सभी "व्यवहार अनिर्दिष्ट" भाषा के लिए विशिष्टता दी। धन्यवाद पॉल। –

+1

हालांकि यह काम करता है और बदलने की अपेक्षाकृत असंभव है, इसकी गारंटी नहीं है। बाइटबफर के लिए जावाडॉक कहता है: _ प्रत्यक्ष बाइट बफर दिया गया है, जावा आभासी मशीन सीधे आई/ओ संचालन करने के लिए ** ** सर्वश्रेष्ठ प्रयास करेगा। यही है, यह ** ** कोशिश करेगा ** बफर की सामग्री को अंतर्निहित ऑपरेटिंग सिस्टम के मूल I/O संचालन में से प्रत्येक के इनवोकेशन से पहले (या बाद) इंटरमीडिएट बफर (या उसके बाद) को कॉपी करने से बचने के लिए। "प्रयास" और " सबसे अच्छा प्रयास "मतलब कार्यान्वयन की आवश्यकता नहीं है/जिस तरह से आप चाहते हैं व्यवहार करने की गारंटी है। –

3

सबसे सस्ता बात आप एक अस्थिर चर का उपयोग कर रहा है क्या कर सकते हैं POSIX में mmap (या Windows है, जो लगभग एक ही तरह से बनाया गया था के लिए CreateFileMapping देखो।। एक धागा बाद मैप किए गए करने के लिए लिखता है क्षेत्र, इसे एक अस्थिर चर के लिए एक मान लिखना चाहिए। किसी भी पठन धागे को मैप किए गए बफर को पढ़ने से पहले अस्थिर चर को पढ़ना चाहिए। ऐसा करने से जावा मेमोरी मॉडल में "होता है-पहले" उत्पन्न होता है।

ध्यान दें कि आपके पास नहीं है गारंटी दें कि एक और प्रक्रिया कुछ नया लिखने के बीच में है। लेकिन अगर आप यह गारंटी देना चाहते हैं कि अन्य धागे आपके द्वारा लिखे गए कुछ भी देख सकें, अस्थिर लिखना (इसके बाद से इसे पढ़कर ई पढ़ने धागा) चाल करेंगे।

4

नहीं। जेवीएम मेमोरी मॉडल (जेएमएम) गारंटी नहीं देता है कि एकाधिक धागे उत्परिवर्तित (असीमित) डेटा एक दूसरे को बदल देगा।

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

के सवाल इसलिए इसके बारे में बाइट सरणियों अलग तरीके से व्यक्त करते हैं:

  1. एक प्रबंधक एक बाइट सरणी: byte[] B_all
  2. कि सरणी के लिए एक नया संदर्भ बनाई गई है: byte[] B_1 = B_all, और T1
  3. थ्रेड के लिए दिया
  4. उस सरणी का एक और संदर्भ बनाया गया है: byte[] B_2 = B_all, और थ्रेड T2

B_1 पर T1B_2 में T2 द्वारा देखा गया है?

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

ध्यान दें कि प्रैक्टिस में आप लिखते हैं या नहीं, ज्यादातर हार्डवेयर और कैश और रजिस्टरों के विभिन्न स्तरों में डेटा के संरेखण पर निर्भर करते हैं, और कैसे चल रहे थ्रेड मेमोरी पदानुक्रम में "दूर" हैं।

जेएसआर -133 जावा 5.0 के आसपास जावा मेमोरी मॉडल को सटीक रूप से परिभाषित करने का प्रयास था (और जहां तक ​​मुझे पता है कि यह अभी भी 2012 में लागू है)। यही वह जगह है जहां आप निश्चित (हालांकि घने) उत्तरों की तलाश करना चाहते हैं: http://www.cs.umd.edu/~pugh/java/memoryModel/jsr133.pdf (सेक्शन 2 सबसे प्रासंगिक है)। अधिक पठनीय सामान झामुमो वेब पेज पर पाया जा सकता है: http://www.cs.umd.edu/~pugh/java/memoryModel/

मेरा उत्तर के भाग जोर देते हुए है कि एक ByteBuffer डेटा तुल्यकालन के मामले में एक byte[] से अलग नहीं है। मुझे यह कहने वाले विशिष्ट दस्तावेज नहीं मिल रहे हैं, लेकिन मेरा सुझाव है कि java.nio.Buffer दस्तावेज़ के "थ्रेड सेफ्टी" सेक्शन में लागू होने पर सिंक्रनाइज़ेशन या अस्थिरता के बारे में कुछ उल्लेख किया जाएगा। चूंकि डॉक्टर इसका उल्लेख नहीं करता है, इसलिए हमें इस तरह के व्यवहार की उम्मीद नहीं करनी चाहिए।

संबंधित मुद्दे