İşlevsel diller (özellikle Erlang) nasıl / neden iyi ölçeklenir?


92

Bir süredir işlevsel programlama dillerinin ve özelliklerinin artan görünürlüğünü izliyorum. Onlara baktım ve itirazın nedenini görmedim.

Daha sonra yakın zamanda Codemash'ta Kevin Smith'in " Erlang'ın Temelleri" sunumuna katıldım .

Sunumdan keyif aldım ve işlevsel programlamanın birçok özelliğinin iş parçacığı / eşzamanlılık sorunlarından kaçınmayı çok daha kolaylaştırdığını öğrendim. Durum ve değişkenlik eksikliğinin birden fazla iş parçacığının aynı verileri değiştirmesini imkansız hale getirdiğini anlıyorum, ancak Kevin dedi (doğru anladıysam) tüm iletişim mesajlar aracılığıyla gerçekleşir ve mesajlar eşzamanlı olarak işlenir (yine eşzamanlılık sorunlarından kaçınarak).

Ancak Erlang'ın oldukça ölçeklenebilir uygulamalarda kullanıldığını okudum (Ericsson'un onu ilk başta yaratmasının nedeni). Her şey eşzamanlı olarak işlenmiş bir mesaj olarak işlenirse, saniyede binlerce isteği işlemek nasıl verimli olabilir? Eşzamansız işlemeye doğru ilerlememizin nedeni bu değil mi - böylece aynı anda birden fazla işlem parçacığı çalıştırmanın avantajlarından yararlanıp ölçeklenebilirlik elde edebilir miyiz? Görünüşe göre bu mimari, daha güvenli olmakla birlikte, ölçeklenebilirlik açısından geriye doğru bir adımdır. Neyi kaçırıyorum?

Erlang'ın yaratıcılarının eşzamanlılık sorunlarını önlemek için kasıtlı olarak iş parçacığı oluşturmayı desteklemekten kaçındığını anlıyorum, ancak ölçeklenebilirliği sağlamak için çoklu iş parçacığı oluşturmanın gerekli olduğunu düşündüm.

İşlevsel programlama dilleri, doğası gereği iş parçacığı açısından güvenli, ancak yine de ölçeklenebilir mi?


1
[Belirtilmedi]: Erlangs'ın sanal makinesi eşzamansızlığı başka bir düzeye taşıyor. Voodoo büyüsü (asm) sayesinde, bir os iş parçacığını durdurmadan socket: read to block gibi senkronizasyon işlemlerine izin verir. Bu, diğer diller sizi eşzamansız geri arama yuvalarına zorladığında eşzamanlı kod yazmanıza olanak tanır. Tek iş parçacıklı mikro hizmetlerin zihin resmi ile bir ölçeklendirme uygulaması yazmak VS kod tabanına her bir şey yapıştırdığınızda büyük resmi göz önünde bulundurmak çok daha kolaydır.
Vans S

@Vans S İlginç.
Jim Anderson

Yanıtlar:


99

İşlevsel bir dil (genel olarak) mutasyona dayanmaz bir değişken. Bu nedenle, bir değişkenin "paylaşılan durumunu" korumak zorunda değiliz çünkü değer sabittir. Bu da geleneksel dillerin işlemciler veya makineler arasında bir algoritma uygulamak için geçmesi gereken çember atlamasının çoğunu önler.

Erlang, bir kod parçasının yalnızca mesaj alma ve mesaj gönderme konusunda endişelenip daha büyük bir resim için endişelenmediği olay tabanlı bir sistemde her şeyin çalışmasına izin veren bir mesaj geçirme sisteminde pişirerek bunu geleneksel işlevsel dillerden daha ileri götürür.

Bunun anlamı, programcının (sözde) mesajın başka bir işlemci veya makinede ele alınacağından endişe duymamasıdır: basitçe mesajı göndermek mesajın devam etmesi için yeterince iyidir. Bir yanıtı önemsiyorsa, başka bir mesaj olarak bekleyecektir .

Bunun sonucu, her bir kod parçacığının diğer tüm parçacıklardan bağımsız olmasıdır. Paylaşılan kod yok, paylaşılan durum yok ve birçok donanım parçası arasında dağıtılabilen (veya dağıtılamayan) bir mesaj sisteminden gelen tüm etkileşimler.

Bunu geleneksel bir sistemle karşılaştırın: "korumalı" değişkenlerin ve kod çalıştırmanın etrafına muteksleri ve semaforları yerleştirmeliyiz. Yığın aracılığıyla bir işlev çağrısında sıkı bağlamamız var (dönüşün gerçekleşmesini bekliyoruz). Tüm bunlar, Erlang gibi paylaşılmayan hiçbir sistemde daha az sorun teşkil eden darboğazlar yaratır.

DÜZENLEME: Erlang'ın asenkron olduğunu da belirtmeliyim. Mesajınızı gönderirsiniz ve belki / bir gün başka bir mesaj geri gelir. Ya da değil.

Spencer'ın sıra dışı yürütme hakkındaki görüşü de önemlidir ve iyi yanıtlanmıştır.


Bunu anlıyorum ama mesaj modelinin ne kadar verimli olduğunu anlamıyorum. Tam tersini tahmin ediyorum. Bu benim için gerçek bir göz açıcı. İşlevsel programlama dillerinin bu kadar dikkat çekmesine şaşmamalı.
Jim Anderson

3
Hiçbir şey paylaşılmayan bir sistemde çok fazla eşzamanlılık potansiyeli kazanırsınız . Kötü bir uygulama (örneğin tepeden geçen yüksek mesaj) bunu baltalayabilir, ancak Erlang bunu doğru yapıyor ve her şeyi hafif tutuyor gibi görünüyor.
Godeke

Erlang mesaj iletme semantiğine sahipken, paylaşılan bir bellek uygulamasına sahip olduğunu, bu nedenle, açıklanan anlamsallığa sahip olduğunu, ancak gerekmiyorsa her ikisinin de her şeyi kopyaladığını unutmamak önemlidir.
Aaron Maenpaa

1
@Godeke: "Erlang (çoğu işlevsel dil gibi) mümkün olduğunda herhangi bir verinin tek bir örneğini tutar". AFAIK, Erlang aslında eşzamanlı GC eksikliğinden dolayı hafif süreçleri arasında geçen her şeyi derinlemesine kopyalar.
JD

1
@JonHarrop neredeyse haklı: bir işlem başka bir işleme mesaj gönderdiğinde mesaj kopyalanır; referansla iletilen büyük ikili dosyalar dışında. Bunun neden iyi bir şey olduğu için örneğin jlouisramblings.blogspot.hu/2013/10/embrace-copying.html adresine bakın .
hcs42

74

Mesaj kuyruğu sistemi harikadır çünkü hakkında okuduğunuz eşzamanlı kısım olan "ateşle ve sonucu bekle" efekti yaratır. Bunu inanılmaz derecede harika yapan şey, satırların sırayla çalıştırılmasına gerek olmadığı anlamına gelmesidir. Aşağıdaki kodu göz önünde bulundurun:

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

Bir an için methodWithALotOfDiskProcessing () 'in tamamlanmasının yaklaşık 2 saniye sürdüğünü ve bu methodWithALotOfNetworkProcessing ()' in tamamlanmasının yaklaşık 1 saniye sürdüğünü düşünün. Prosedürel bir dilde, bu kodun çalışması yaklaşık 3 saniye sürer çünkü satırlar sıralı olarak çalıştırılır. Tek bir kaynak için rekabet etmeden diğeriyle aynı anda çalışabilecek bir yöntemin tamamlanmasını bekleyerek zaman kaybediyoruz. İşlevsel bir dilde kod satırları, işlemcinin bunları ne zaman deneyeceğini belirlemez. İşlevsel bir dil aşağıdaki gibi bir şeyi deneyecektir:

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.

Ne kadar serin? Kodla devam ederek ve yalnızca gerektiği yerde bekleyerek, bekleme süresini otomatik olarak iki saniyeye düşürdük! : D Yani evet, kod eşzamanlı iken, prosedürel dillerden farklı bir anlama sahip olma eğilimindedir.

DÜZENLE:

Bu kavramı Godeke'nin gönderisiyle bağlantılı olarak kavradığınızda , birden çok işlemciden, sunucu çiftliklerinden, yedek veri depolarından ve başka neyi bilenlerden yararlanmanın ne kadar basit hale geldiğini hayal etmek kolaydır .


Güzel! Mesajların nasıl işlendiğini tamamen yanlış anladım. Teşekkürler, gönderiniz yardımcı oluyor.
Jim Anderson

"İşlevsel bir dil aşağıdaki gibi bir şeyi deneyecektir" - Diğer işlevsel dillerden emin değilim, ancak Erlang'da örnek prosedürel dillerdeki gibi tam olarak çalışacaktır. Sen olabilir onları uyumsuz iki görevi yürütmek ettiğini ve sonunda onların sonuçlar elde yumurtlama süreçler tarafından paralel olarak bu iki görevleri yerine ancak kod senkron iken bu usul dilde farklı bir anlama sahip olma eğilimindedir" gibi değil. " Ayrıca Chris'in cevabına bakın.
hcs42

16

Muhtemelen sıralı ile eşzamanlı karışımı karıştırıyorsunuz .

Erlang dilinde bir işlevin gövdesi sırayla işleniyor. Yani Spencer'ın bu "otomatik büyü etkisi" hakkında söyledikleri erlang için geçerli değil. Yine de bu davranışı erlang ile modelleyebilirsiniz.

Örneğin, bir satırdaki kelimelerin sayısını hesaplayan bir işlem başlatabilirsiniz. Birkaç satırımız olduğu için, her satır için böyle bir işlem oluşturuyoruz ve ondan bir toplam hesaplamak için yanıtları alıyoruz.

Bu şekilde, "ağır" hesaplamaları yapan (varsa ek çekirdekler kullanan) süreçler oluştururuz ve daha sonra sonuçları toplarız.

-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.

Ve bunu kabukta çalıştırdığımızda göründüğü gibi:

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'ın ölçeklenmesini sağlayan en önemli şey eşzamanlılıkla ilgilidir.

Bir işletim sistemi iki mekanizma ile eşzamanlılık sağlar:

  • işletim sistemi işlemleri
  • işletim sistemi konuları

Süreçler durumu paylaşmaz - bir süreç tasarım gereği diğerini çökertemez.

İş parçacığı paylaşım durumu - bir iş parçacığı diğerini tasarım gereği çökebilir - bu sizin sorununuz.

Erlang ile - bir işletim sistemi süreci sanal makine tarafından kullanılır ve VM, işletim sistemi parçacıkları kullanarak değil, Erlang süreçleri sağlayarak Erlang programına eşzamanlılık sağlar - yani Erlang kendi zamanlayıcıyı uygular.

Bu Erlang süreci, mesajlar göndererek birbirleriyle konuşur (işletim sistemi değil Erlang VM tarafından ele alınır). Erlang işlemleri, üç bölümlü adrese sahip bir işlem kimliği (PID) kullanarak birbirini ele alır <<N3.N2.N1>>:

  • işlem no N1 açık
  • VM N2 açık
  • fiziksel makine N3

Aynı VM'de, aynı makinedeki farklı VM'lerde veya iki makinede aynı şekilde iletişim kuran iki işlem - bu nedenle ölçeklendirmeniz, uygulamanızı dağıttığınız fiziksel makinelerin sayısından bağımsızdır (ilk yaklaşımda).

Erlang sadece önemsiz bir anlamda iş parçacığı güvenlidir - dişleri yoktur. (Dil, yani SMP / çok çekirdekli VM, çekirdek başına bir işletim sistemi iş parçacığı kullanır).


7

Erlang'ın nasıl çalıştığını yanlış anlamış olabilirsiniz. Erlang çalışma zamanı, bir CPU'daki bağlam değiştirmeyi en aza indirir, ancak birden fazla CPU varsa, o zaman tümü mesajları işlemek için kullanılır. Diğer dillerde olduğu gibi "ileti dizisine" sahip değilsiniz, ancak aynı anda işlenen çok sayıda iletiniz olabilir.


4

Erlang mesajları tamamen eşzamansızdır, eğer mesajınıza eşzamanlı bir cevap istiyorsanız bunun için açıkça kodlamanız gerekir. Muhtemelen söylenen şey, bir işlem mesaj kutusundaki mesajların sıralı olarak işlenmesiydi. Bir işleme gönderilen herhangi bir mesaj, bu işlemin mesaj kutusuna oturur ve süreç, bu kutudan bir mesaj seçer ve onu işler ve uygun gördüğü sırayla bir sonrakine geçer. Bu çok sıralı bir eylemdir ve alma bloğu tam olarak bunu yapar.

Görünüşe göre, Chris'in bahsettiği gibi eşzamanlı ve sıralıyı karıştırmışsınız.



-2

Tamamen işlevsel bir dilde, değerlendirme sırası önemli değildir - fn (arg1, .. argn) işlev uygulamasında n bağımsız değişken paralel olarak değerlendirilebilir. Bu, yüksek düzeyde (otomatik) paralelliği garanti eder.

Erlang, bir işlemin aynı sanal makinede veya farklı bir işlemcide çalışabileceği bir süreç modeli kullanır - bunu söylemenin bir yolu yoktur. Bu yalnızca mesajlar süreçler arasında kopyalandığı için mümkündür, paylaşılan (değiştirilebilir) durum yoktur. Çok işlemcili paralellik, çoklu iş parçacığından çok daha ileri gider, çünkü iş parçacıkları paylaşılan belleğe bağlıdır, bu, 8 çekirdekli bir CPU üzerinde paralel olarak çalışan yalnızca 8 iş parçacığı olabilirken, çoklu işlem binlerce paralel işleme ölçeklenebilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.