कैसे / क्यों कार्यात्मक भाषाओं (विशेष रूप से एरलंग) को अच्छी तरह से मापते हैं?


92

मैं कुछ समय से कार्यात्मक प्रोग्रामिंग भाषाओं और सुविधाओं की बढ़ती दृश्यता देख रहा हूं। मैंने उनमें देखा और अपील का कारण नहीं देखा।

उसके बाद, हाल ही में मैंने केविन स्मिथ की "बेसिक ऑफ एरलांग " प्रस्तुति में कोडमेश में भाग लिया ।

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

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

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

कैसे कार्यात्मक प्रोग्रामिंग भाषाओं को स्वाभाविक रूप से थ्रेड-सुरक्षित किया जा सकता है, फिर भी पैमाने पर?


1
[उल्लेख नहीं किया गया]: एर्लैंग्स वीएम दूसरे स्तर पर अतुल्यकालिकता लेता है। जादू जादू (asm) के द्वारा यह सॉकेट जैसे सिंक संचालन की अनुमति देता है: ओएस धागे को रोकने के बिना ब्लॉक करने के लिए पढ़ा जाता है। यह आपको सिंक्रोनस कोड लिखने की अनुमति देता है जब अन्य भाषाएं आपको async-callback nests में मजबूर करेंगी। सिंगल थ्रेडेड माइक्रो-सर्विसेज वी.एस. के माइंड पिक्चर के साथ स्केलिंग ऐप लिखना बहुत आसान है, हर बार जब आप कोड बेस पर कुछ डील करते हैं तो बड़ी तस्वीर को ध्यान में रखते हैं।
वैन एस

@Vans एस दिलचस्प।
जिम एंडरसन

जवाबों:


97

एक कार्यात्मक भाषा (सामान्य रूप से) एक चर को बदलने पर निर्भर नहीं करती है। इस वजह से, हमें किसी वैरिएबल की "साझा स्थिति" की रक्षा करने की आवश्यकता नहीं है, क्योंकि मूल्य निश्चित है। यह बदले में हुप कूद के बहुमत से बचा जाता है कि पारंपरिक भाषाओं को प्रोसेसर या मशीनों में एक एल्गोरिथ्म को लागू करने के लिए गुजरना पड़ता है।

एर्लांग एक संदेश पासिंग सिस्टम में सेंध लगाकर पारंपरिक कार्यात्मक भाषाओं की तुलना में आगे ले जाता है, जो एक घटना आधारित प्रणाली पर काम करने की अनुमति देता है, जहां कोड का एक टुकड़ा केवल संदेश प्राप्त करने और संदेश भेजने की चिंता करता है, बड़ी तस्वीर के बारे में चिंता नहीं करता है।

इसका मतलब यह है कि प्रोग्रामर (नाममात्र) असंबद्ध है कि संदेश को किसी अन्य प्रोसेसर या मशीन पर हैंडल किया जाएगा: बस संदेश भेजना जारी रखने के लिए पर्याप्त है। यदि यह एक प्रतिक्रिया की परवाह करता है, तो यह एक और संदेश के रूप में इंतजार करेगा ।

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

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

संपादित करें: मुझे यह भी इंगित करना चाहिए कि एरलंग अतुल्यकालिक है। आप अपना संदेश भेजें और हो सकता है किसी दिन एक और संदेश वापस आ जाए। या नहीं।

ऑर्डर निष्पादन के बारे में स्पेंसर की बात भी महत्वपूर्ण है और अच्छी तरह से उत्तर दिया गया है।


मैं इसे समझता हूं, लेकिन यह नहीं देखता कि संदेश मॉडल कैसे कुशल है। मैं इसके विपरीत अनुमान लगाऊंगा। यह मेरे लिए एक वास्तविक आंख खोलने वाला है। कोई आश्चर्य नहीं कि कार्यात्मक प्रोग्रामिंग भाषाओं को इतना ध्यान दिया जा रहा है।
जिम एंडरसन

3
आप साझा कुछ भी नहीं प्रणाली में कई संगामिति क्षमता प्राप्त करते हैं। एक खराब कार्यान्वयन (उदाहरण के लिए उच्च संदेश पासिंग ओवरहेड), इसे टारपीडो कर सकता है, लेकिन एर्लैंग को यह सही लगता है और हल्के वजन को कम रखता है।
गोडेके

यह नोट करना महत्वपूर्ण है कि एरलांग के पास संदेश भेजने वाले शब्दार्थ हैं, लेकिन यह एक साझा स्मृति कार्यान्वयन है, इस प्रकार, इसमें शब्दार्थ वर्णित है, लेकिन अगर यह नहीं करना है तो यह सभी जगह पर सामान की नकल नहीं करता है।
आरोन मेनापा

1
@Godeke: "Erlang (अधिकांश कार्यात्मक भाषाओं की तरह) जब संभव हो तो किसी भी डेटा का एकल उदाहरण रखता है"। AFAIK, Erlang वास्तव में गहरी प्रतियां सब कुछ समवर्ती जीसी की कमी के कारण अपने हल्के प्रक्रियाओं के बीच पारित कर दिया।
जद

1
@JonHarrop लगभग सही है: जब कोई प्रक्रिया किसी अन्य प्रक्रिया को संदेश भेजती है, तो संदेश की प्रतिलिपि बनाई जाती है; बड़े बायनेरिज़ को छोड़कर, जो संदर्भ द्वारा पारित किए जाते हैं। उदाहरण के लिए jlouisramblings.blogspot.hu/2013/10/embrace-copying.html देखें क्यों यह एक अच्छी बात है।
hcs42

73

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

r = methodWithALotOfDiskProcessing();
x = r + 1;
y = methodWithALotOfNetworkProcessing();
w = x * y

एक पल के लिए विचार करें कि methodWithALotOfDiskProcessing () को पूरा होने में लगभग 2 सेकंड का समय लगता है और उस विधिWithALotOfNetworkProcessing () को पूरा होने में लगभग 1 सेकंड लगता है। एक प्रक्रियात्मक भाषा में इस कोड को चलने में लगभग 3 सेकंड का समय लगेगा क्योंकि लाइनों को क्रमिक रूप से निष्पादित किया जाएगा। हम एक विधि को पूरा करने के लिए समय बर्बाद कर रहे हैं जो एक संसाधन के लिए प्रतिस्पर्धा किए बिना दूसरे के साथ समवर्ती रूप से चल सकती है। एक कार्यात्मक भाषा में कोड की पंक्तियाँ निर्धारित नहीं होती हैं जब प्रोसेसर उन्हें प्रयास करेगा। एक कार्यात्मक भाषा निम्नलिखित की तरह कुछ करने की कोशिश करेगी:

Execute line 1 ... wait.
Execute line 2 ... wait for r value.
Execute line 3 ... wait.
Execute line 4 ... wait for x and y value.
Line 3 returned ... y value set, message line 4.
Line 1 returned ... r value set, message line 2.
Line 2 returned ... x value set, message line 4.
Line 4 returned ... done.

कितना मजेदार था वो? कोड के साथ आगे बढ़ने और केवल प्रतीक्षा करने से जहां आवश्यक हो, हमने प्रतीक्षा समय को दो सेकंड तक कम कर दिया है स्वचालित रूप से! : D हां हां, जबकि कोड समकालिक है, इसमें प्रक्रियात्मक भाषाओं की तुलना में एक अलग अर्थ है।

संपादित करें:

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


ठंडा! मैंने पूरी तरह से गलत समझा कि संदेश कैसे संभाले जा रहे हैं। धन्यवाद, आपका पोस्ट मदद करता है।
जिम एंडरसन

"एक कार्यात्मक भाषा निम्नलिखित की तरह कुछ करने की कोशिश करेगी" - मैं अन्य कार्यात्मक भाषाओं के बारे में निश्चित नहीं हूं, लेकिन एर्लैंग में प्रक्रियात्मक भाषाओं के मामले में उदाहरण बिल्कुल काम करेगा। आप स्पैनिंग प्रक्रियाओं द्वारा समानांतर में उन दो कार्यों को निष्पादित कर सकते हैं, जिससे उन्हें दो कार्यों को समान रूप से निष्पादित करने और अंत में उनके परिणाम प्राप्त करने में मदद मिलती है, लेकिन यह "ऐसा नहीं है" जबकि कोड सिंक्रोनाइज़ होता है इसका प्रक्रियात्मक भाषाओं की तुलना में एक अलग अर्थ है। " क्रिस का जवाब भी देखिए।
hcs42

16

यह संभावना है कि आप अनुक्रमिक के साथ तुल्यकालिक मिश्रण कर रहे हैं ।

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

उदाहरण के लिए आप एक ऐसी प्रक्रिया को शुरू कर सकते हैं जो एक पंक्ति में शब्दों की संख्या की गणना करती है। जैसा कि हम कई पंक्तियाँ रखते हैं, हम प्रत्येक पंक्ति के लिए इस तरह की एक प्रक्रिया करते हैं और उससे एक राशि की गणना करने के लिए उत्तर प्राप्त करते हैं।

इस तरह, हम प्रक्रियाएँ करते हैं जो "भारी" संगणना (यदि उपलब्ध हो तो अतिरिक्त कोर का उपयोग) और बाद में हम परिणाम एकत्र करते हैं।

-module(countwords).
-export([count_words_in_lines/1]).

count_words_in_lines(Lines) ->
    % For each line in lines run spawn_summarizer with the process id (pid)
    % and a line to work on as arguments.
    % This is a list comprehension and spawn_summarizer will return the pid
    % of the process that was created. So the variable Pids will hold a list
    % of process ids.
    Pids = [spawn_summarizer(self(), Line) || Line <- Lines], 
    % For each pid receive the answer. This will happen in the same order in
    % which the processes were created, because we saved [pid1, pid2, ...] in
    % the variable Pids and now we consume this list.
    Results = [receive_result(Pid) || Pid <- Pids],
    % Sum up the results.
    WordCount = lists:sum(Results),
    io:format("We've got ~p words, Sir!~n", [WordCount]).

spawn_summarizer(S, Line) ->
    % Create a anonymous function and save it in the variable F.
    F = fun() ->
        % Split line into words.
        ListOfWords = string:tokens(Line, " "),
        Length = length(ListOfWords),
        io:format("process ~p calculated ~p words~n", [self(), Length]),
        % Send a tuple containing our pid and Length to S.
        S ! {self(), Length}
    end,
    % There is no return in erlang, instead the last value in a function is
    % returned implicitly.
    % Spawn the anonymous function and return the pid of the new process.
    spawn(F).

% The Variable Pid gets bound in the function head.
% In erlang, you can only assign to a variable once.
receive_result(Pid) ->
    receive
        % Pattern-matching: the block behind "->" will execute only if we receive
        % a tuple that matches the one below. The variable Pid is already bound,
        % so we are waiting here for the answer of a specific process.
        % N is unbound so we accept any value.
        {Pid, N} ->
            io:format("Received \"~p\" from process ~p~n", [N, Pid]),
            N
    end.

और यह वह है जो ऐसा दिखता है, जब हम इसे शेल में चलाते हैं:

Eshell V5.6.5  (abort with ^G)
1> Lines = ["This is a string of text", "and this is another", "and yet another", "it's getting boring now"].
["This is a string of text","and this is another",
 "and yet another","it's getting boring now"]
2> c(countwords).
{ok,countwords}
3> countwords:count_words_in_lines(Lines).
process <0.39.0> calculated 6 words
process <0.40.0> calculated 4 words
process <0.41.0> calculated 3 words
process <0.42.0> calculated 4 words
Received "6" from process <0.39.0>
Received "4" from process <0.40.0>
Received "3" from process <0.41.0>
Received "4" from process <0.42.0>
We've got 17 words, Sir!
ok
4> 

13

Erlang को स्केल करने के लिए सक्षम करने वाली महत्वपूर्ण चीज़ संगामिति से संबंधित है।

एक ऑपरेटिंग सिस्टम दो तंत्रों द्वारा संगति प्रदान करता है:

  • ऑपरेटिंग सिस्टम की प्रक्रिया
  • ऑपरेटिंग सिस्टम थ्रेड्स

प्रक्रियाएं साझा नहीं करती हैं - एक प्रक्रिया डिज़ाइन द्वारा दूसरे को क्रैश नहीं कर सकती है।

थ्रेड्स साझा स्थिति - एक धागा डिज़ाइन द्वारा दूसरे को क्रैश कर सकता है - यही आपकी समस्या है।

Erlang के साथ - एक ऑपरेटिंग सिस्टम प्रक्रिया का उपयोग वर्चुअल मशीन द्वारा किया जाता है और VM ऑपरेटिंग सिस्टम थ्रेड्स का उपयोग करके नहीं बल्कि Erlang प्रक्रियाओं को प्रदान करके Erlang प्रोग्राम को समरूपता प्रदान करता है - जो कि Erlang अपने स्वयं के टाइमसीलर को लागू करता है।

ये एरलांग प्रक्रिया एक दूसरे को संदेश भेजकर बात करती है (एर्लांग वीएम द्वारा संचालित नहीं ऑपरेटिंग सिस्टम)। एरलैंग प्रक्रिया एक दूसरे को एक प्रक्रिया आईडी (पीआईडी) का उपयोग करके संबोधित करती है जिसमें तीन-भाग का पता होता है <<N3.N2.N1>>:

  • प्रक्रिया no N १
  • वीएम एन 2 पर
  • भौतिक मशीन N3

एक ही वीएम पर दो प्रक्रियाएं, एक ही मशीन पर अलग-अलग वीएम पर या दो मशीनें एक ही तरह से संवाद करती हैं - इसलिए आपका स्केलिंग उन भौतिक मशीनों की संख्या से स्वतंत्र है जिन्हें आप अपने आवेदन को (पहले सन्निकटन में) तैनात करते हैं।

Erlang केवल एक तुच्छ अर्थ में थ्रेडसेफ़ है - इसमें थ्रेड्स नहीं हैं। (जो भाषा है, एसएमपी / मल्टी-कोर वीएम एक ऑपरेटिंग सिस्टम थ्रेड प्रति कोर का उपयोग करता है)।


7

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


4

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

लगता है जैसे आप क्रिस और अनुक्रमिक मिलाया है जैसा कि क्रिस ने उल्लेख किया है।



-2

विशुद्ध रूप से कार्यात्मक भाषा में, मूल्यांकन का क्रम मायने नहीं रखता है - एक फ़ंक्शन अनुप्रयोग fn (arg1, .. argn) में, n तर्कों का मूल्यांकन समानांतर में किया जा सकता है। उच्च स्तरीय (स्वचालित) समानता की गारंटी देता है।

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

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