2011-08-28 11 views
11

मैंने अभी एक एमएसडीएन आलेख पढ़ा है, "Synchronization and Multiprocessor Issues", जो मल्टीप्रोसेसर मशीनों पर मेमोरी कैश स्थिरता के मुद्दों को संबोधित करता है। यह वास्तव में मेरे लिए खुल रहा था, क्योंकि मैंने सोचा नहीं था कि उनके द्वारा प्रदान किए गए उदाहरण में दौड़ की स्थिति हो सकती है। यह आलेख बताता है कि मेरे कोड में लिखे गए क्रम में स्मृति में लिखना वास्तव में नहीं हो सकता है (अन्य सीपीयू के परिप्रेक्ष्य से)। यह मेरे लिए एक नई अवधारणा है!डेल्फी में कैश संगतता मुद्दों से बचने के लिए एक गंभीर खंड के साथ?

  1. चर की जरूरत है कि एक से अधिक CPU भर में स्थिरता कैश पर "अस्थिर" कीवर्ड का उपयोग करना:

    यह लेख 2 समाधान प्रदान करता है। यह एक सी/सी ++ कीवर्ड है, और डेल्फी में मेरे लिए उपलब्ध नहीं है।

  2. इंटरलॉक एक्सचेंज() और इंटरलॉक कॉम्पारे एक्सचेंज() का उपयोग करना। डेल्फी में ऐसा कुछ है जो मुझे करना था। यह बस थोड़ा गन्दा लगता है।

लेख में यह भी उल्लेख किया गया है कि "निम्नलिखित सिंक्रनाइज़ेशन फ़ंक्शन मेमोरी ऑर्डरिंग सुनिश्चित करने के लिए उपयुक्त बाधाओं का उपयोग करते हैं: • महत्वपूर्ण अनुभाग दर्ज करने या छोड़ने वाले कार्य"।

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

उत्तर

7

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

गंभीर वर्ग और म्यूटेक्स वास्तव में लागू किए गए हैं ताकि सुरक्षित चर के पढ़ने और लिखने को सभी धागे से सही ढंग से देखा जा सके।

मुझे लगता है कि महत्वपूर्ण वर्गों और म्यूटेक्स (ताले) के बारे में सोचने का सबसे अच्छा तरीका सीरियलाइजेशन लाने के लिए डिवाइस है। यही है, ऐसे ताले से संरक्षित कोड के ब्लॉक को क्रमशः निष्पादित किया जाता है, एक ओवरलैप के बिना एक के बाद। क्रमबद्धता स्मृति पहुंच पर भी लागू होती है। कैश समेकन या पढ़ने/लिखने के लिए कोई समस्या नहीं हो सकती है।

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

लॉक मुक्त एल्गोरिदम लॉक के आधार पर उन लोगों की तुलना में अधिक कुशल हो सकते हैं, लेकिन मुक्त एल्गोरिदम लॉक सही ढंग से लिखना बहुत कठिन हो सकता है। लॉक फ्री पर महत्वपूर्ण अनुभागों को प्राथमिकता दें जब तक प्रदर्शन प्रभाव स्पष्ट नहीं होते हैं।

एक और लेख पढ़ने योग्य मूल्य The "Double-Checked Locking is Broken" Declaration है।

+1

डेविड, अक्सर बूलियन, पूर्णांक इत्यादि परमाणु होने पर संदर्भित होते हैं (यदि सही ढंग से गठबंधन किया जाता है) और इस प्रकार थ्रेड सुरक्षित होता है। मुझे लगता है कि यहां स्वीकृत उत्तर http://stackoverflow.com/questions/510031/list-of-delphi-data-types-with-atomic-read-write-operations इसे परिप्रेक्ष्य में रखता है। उद्धरण "पढ़े धागे सुरक्षित हैं। लेखन धागे सुरक्षित नहीं हैं।" –

+0

@LU RD आपको सटीक होने की आवश्यकता है। थ्रेडसेफ का क्या मतलब है? और ठीक है "रीड थ्रेड सुरक्षित हैं, लिखते हैं धागे सुरक्षित नहीं हैं" मतलब क्या है? –

+0

यह केवल एक सामान्य चेतावनी थी कि यह मानने के लिए कि किसी भी थ्रेडिंग संदर्भों में उपयोग करने के लिए सुरक्षित तथाकथित परमाणु चर पर संचालन पर विचार नहीं कर सकता है। –

7

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

सीपीयू प्रति-कोर कैश संभावित मुद्दों में से एक है, जो गलत मान पढ़ने में अग्रणी होगा।रेस हालत में नेतृत्व करने वाला एक और मुद्दा एक ही समय में एक संसाधन को लिखने के लिए दो धागे हैं: यह जानना असंभव है कि बाद में कौन सा मूल्य संग्रहीत किया जाएगा।

चूंकि कोड डेटा को सुसंगत होने की अपेक्षा करता है, कुछ बहु-थ्रेड प्रोग्राम गलत तरीके से व्यवहार कर सकते हैं। बहु-थ्रेडिंग के साथ, आप सुनिश्चित नहीं हैं कि व्यक्तिगत निर्देशों के माध्यम से आपके द्वारा लिखे गए कोड को अपेक्षित रूप से निष्पादित किया जाता है, जब यह साझा चर के साथ संबंधित होता है।

InterlockedExchange/InterlockedIncrement काम करता है, एक ताला उपसर्ग के साथ निम्न स्तर के एएसएम opcodes (या XCHG EDX,[EAX] opcode की तरह, डिजाइन द्वारा बंद कर दिया) कर रहे हैं जो वास्तव में सभी सीपीयू कोर के लिए कैश जुटना के लिए बाध्य करेगा, और इसलिए एएसएम opcode निष्पादन धागा सुरक्षित बनाने के ।

उदाहरण के लिए, यहाँ कैसे एक स्ट्रिंग संदर्भ गिनती कार्यान्वित किया जाता है जब आप एक स्ट्रिंग मान निर्दिष्ट है (System.pas में _LStrAsg देखते हैं - यह our optimized version of the RTL for Delphi 7/2002 से है - के बाद से डेल्फी मूल कोड कॉपीराइट):

  MOV  ECX,[EDX-skew].StrRec.refCnt 
      INC  ECX { thread-unsafe increment ECX = reference count } 
      JG  @@1 { ECX=-1 -> literal string -> jump not taken } 
      ..... 
     @@1: LOCK INC [EDX-skew].StrRec.refCnt { ATOMIC increment of reference count } 
      MOV  ECX,[EAX] 
      ... 

पहले INC ECX और LOCK INC [EDX-skew].StrRec.refCnt के बीच एक अंतर है - न केवल पहली वृद्धि ईसीएक्स और संदर्भ गणना चर नहीं है, लेकिन पहला थ्रेड-सुरक्षित नहीं है, जबकि दूसरा लॉक द्वारा प्रीफ़िक्स किया गया है इसलिए थ्रेड-सुरक्षित होगा।

वैसे, यह LOCK उपसर्ग multi-thread scaling in the RTL की समस्या में से एक है - यह नए CPUs के साथ बेहतर है, लेकिन फिर भी सही नहीं है। ,

var GlobalVariable: string; 
    GlobalSection: TRTLCriticalSection; 

procedure TThreadOne.Execute; 
var LocalVariable: string; 
begin 
    ... 
    EnterCriticalSection(GlobalSection); 
    LocalVariable := GlobalVariable+'a'; { modify GlobalVariable } 
    GlobalVariable := LocalVariable; 
    LeaveCriticalSection(GlobalSection); 
    .... 
end; 

procedure TThreadTwp.Execute; 
var LocalVariable: string; 
begin 
    ... 
    EnterCriticalSection(GlobalSection); 
    LocalVariable := GlobalVariable; { thread-safe read GlobalVariable } 
    LeaveCriticalSection(GlobalSection); 
    .... 
end; 

एक स्थानीय चर का उपयोग करना महत्वपूर्ण अनुभाग कम करता है इसलिए अपने आवेदन ठीक से आंकने और पूरी शक्ति का उपयोग करेगा:

तो महत्वपूर्ण वर्गों का उपयोग कर एक कोड धागा सुरक्षित बनाने का सबसे आसान तरीका है आपके सीपीयू कोर का। EnterCriticalSection और LeaveCriticalSection के बीच, केवल एक धागा चल रहा होगा: अन्य धागे EnterCriticalSection कॉल में प्रतीक्षा करेंगे ... तो महत्वपूर्ण अनुभाग जितना छोटा होगा, उतना तेज़ आपका आवेदन होगा। कुछ गलत तरीके से डिज़ाइन किए गए बहु-थ्रेडेड अनुप्रयोग वास्तव में मोनो-थ्रेडेड ऐप्स से धीमे हो सकते हैं!

और यह न भूलें कि महत्वपूर्ण अनुभाग के अंदर आपका कोड अपवाद बढ़ा सकता है, तो आपको लॉक रिलीज की सुरक्षा के लिए हमेशा एक स्पष्ट try ... finally LeaveCriticalSection() end; ब्लॉक लिखना चाहिए, और अपने एप्लिकेशन के किसी भी मृत लॉक को रोकना चाहिए।

डेल्फी पूरी तरह से थ्रेड-सुरक्षित है यदि आप लॉक के साथ अपने साझा डेटा की रक्षा करते हैं, यानी एक गंभीर अनुभाग। सावधान रहें कि यहां तक ​​कि संदर्भ-गिनती चर (जैसे स्ट्रिंग्स) को संरक्षित किया जाना चाहिए, भले ही उनके आरटीएल कार्यों के अंदर एक लॉक हो: यह LOCK सही संदर्भ गिनती मानने और स्मृति रिसाव से बचने के लिए है, लेकिन यह थ्रेड-सुरक्षित नहीं होगा । जितनी जल्दी हो सके इसे बनाने के लिए, see this SO question

InterlockExchange और InterlockCompareExchange का उद्देश्य साझा सूचक परिवर्तक मान को बदलना है। आप पॉइंटर वैल्यू तक पहुंचने के लिए इसे महत्वपूर्ण अनुभाग के "हल्के" संस्करण के रूप में देख सकते हैं।

सभी मामलों में, काम कर रहे बहु थ्रेडेड कोड लिखना आसान नहीं है - यह हार्ड, as a Delphi expert just wrote in his blog भी है।

आपको या तो साझा किए गए डेटा के साथ सरल धागे लिखना चाहिए (थ्रेड शुरू होने से पहले डेटा की एक निजी प्रतिलिपि बनाएं, या केवल पढ़ने के लिए साझा डेटा का उपयोग करें - जो सार द्वारा थ्रेड-सुरक्षित है), या कुछ अच्छी तरह से कॉल करें डिजाइन और साबित पुस्तकालय - जैसे http://otl.17slon.com - जो आपको बहुत सारे डिबगिंग समय बचाएगा।

+0

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

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