기능적 언어 (특히 Erlang)는 어떻게 / 왜 잘 확장됩니까?


92

나는 한동안 함수형 프로그래밍 언어와 기능의 증가하는 가시성을 지켜 보았다. 나는 그들을 조사했지만 항소 이유를 보지 못했습니다.

그런 다음 최근에 Codemash 에서 Kevin Smith의 "Basics of Erlang"프레젠테이션에 참석했습니다 .

프레젠테이션을 즐겼고 함수형 프로그래밍의 많은 속성이 스레딩 / 동시성 문제를 훨씬 더 쉽게 피할 수 있다는 것을 배웠습니다. 나는 상태와 가변성이 부족하여 여러 스레드가 동일한 데이터를 변경할 수 없다는 것을 이해하지만 Kevin은 모든 통신이 메시지를 통해 이루어지고 메시지가 동 기적으로 처리된다고 말했습니다 (동시성 문제를 방지 함).

하지만 Erlang이 확장 성이 뛰어난 응용 프로그램에서 사용된다는 것을 읽었습니다 (Ericsson이 처음에 만든 이유). 모든 것이 동 기적으로 처리되는 메시지로 처리되는 경우 초당 수천 개의 요청을 효율적으로 처리 할 수있는 방법은 무엇입니까? 이것이 우리가 비동기 처리로 이동하기 시작한 이유가 아닙니까? 동시에 여러 작업 스레드를 실행하고 확장 성을 확보 할 수 있습니다. 이 아키텍처는 더 안전하지만 확장 성 측면에서 한 걸음 뒤로 물러 난 것처럼 보입니다. 내가 무엇을 놓치고 있습니까?

나는 Erlang의 제작자가 동시성 문제를 피하기 위해 의도적으로 스레딩을 지원하지 않았 음을 이해하지만 확장 성을 달성하려면 멀티 스레딩이 필요하다고 생각했습니다.

함수형 프로그래밍 언어가 본질적으로 스레드로부터 안전하면서도 확장 가능한 방법은 무엇입니까?


1
[언급되지 않음] : Erlangs의 VM은 비동기 성을 다른 수준으로 끌어 올립니다. voodoo magic (asm)에 의해 socket : read와 같은 동기화 작업이 OS 스레드를 중지하지 않고 차단할 수 있습니다. 이를 통해 다른 언어로 인해 비동기 콜백 중첩이 발생하는 경우 동기 코드를 작성할 수 있습니다. 코드 기반에 무언가를 붙일 때마다 큰 그림을 염두에두고 단일 스레드 마이크로 서비스의 마음 그림을 사용하여 확장 앱을 작성하는 것이 훨씬 쉽습니다.
Vans S

@Vans S 흥미 롭습니다.
Jim Anderson

답변:


99

기능적 언어는 (일반적으로) 변수 변경에 의존하지 않습니다 . 이 때문에 값이 고정되어 있기 때문에 변수의 "공유 상태"를 보호 할 필요가 없습니다. 이는 결국 기존 언어가 프로세서 또는 시스템에서 알고리즘을 구현하기 위해 거쳐야하는 후프 점프의 대부분을 방지합니다.

Erlang은 코드가 메시지 수신 및 메시지 전송에만 신경을 쓰는 이벤트 기반 시스템에서 모든 것이 작동 할 수 있도록하는 메시지 전달 시스템을 적용하여 기존의 기능적 언어보다 더 큰 의미를 갖습니다.

이것이 의미하는 바는 프로그래머가 메시지가 다른 프로세서 나 기계에서 처리 될 것이라는 것을 (명목상) 염려하지 않는다는 것입니다. 단순히 메시지를 보내는 것만으로도 계속할 수 있습니다. 응답에 관심이 있다면 다른 메시지 로 기다립니다 .

이것의 최종 결과는 각 스 니펫이 다른 모든 스 니펫과 독립적이라는 것입니다. 공유 코드, 공유 상태 및 모든 상호 작용이 여러 하드웨어간에 분산 될 수있는 (또는 그렇지 않은) 메시지 시스템에서 발생합니다.

이것을 전통적인 시스템과 대조하십시오. "보호 된"변수와 코드 실행 주위에 뮤텍스와 세마포어를 배치해야합니다. 스택을 통한 함수 호출에 긴밀한 바인딩이 있습니다 (반환이 발생하기를 기다림). 이 모든 것이 Erlang과 같은 비공유 시스템에서 문제가되지 않는 병목 현상을 만듭니다.

편집 : 또한 Erlang이 비동기적임을 지적해야합니다. 메시지를 보내고 언젠가 다른 메시지가 다시 도착합니다. 아님.

비 순차적 실행에 대한 Spencer의 요점도 중요하고 잘 대답됩니다.


나는 이것을 이해하지만 메시지 모델이 어떻게 효율적인지 보지 못합니다. 나는 그 반대를 추측 할 것이다. 이것은 저에게 정말 놀라운 일입니다. 함수형 프로그래밍 언어가 많은 관심을 받고있는 것은 당연합니다.
Jim Anderson

3
비공유 시스템에서 많은 동시성 잠재력 을 얻습니다 . 잘못된 구현 (예 : 높은 메시지 전달 오버 헤드)은이를 어둡게 할 수 있지만 Erlang은이를 올바르게 처리하고 모든 것을 가볍게 유지하는 것 같습니다.
Godeke

Erlang은 메시지 전달 의미 체계를 가지고 있지만 공유 메모리 구현이 있으므로 의미 체계가 설명되어 있지만 필요하지 않은 경우 모든 곳에서 물건을 복사하지는 않습니다.
Aaron Maenpaa

1
@Godeke : "Erlang (대부분의 기능 언어와 마찬가지로)은 가능하면 모든 데이터의 단일 인스턴스를 유지합니다." AFAIK, Erlang은 동시 GC 부족으로 인해 경량 프로세스간에 전달되는 모든 것을 실제로 딥 복사합니다.
JD 2011

1
@JonHarrop이 거의 맞습니다. 프로세스가 다른 프로세스에 메시지를 보낼 때 메시지가 복사됩니다. 참조로 전달되는 큰 바이너리는 제외됩니다. 이것이 좋은 이유는 jlouisramblings.blogspot.hu/2013/10/embrace-copying.html 을 참조하십시오 .
hcs42

74

메시지 큐 시스템은 당신이 읽고있는 동기적인 부분 인 "fire-and-wait-for-result"효과를 효과적으로 생성하기 때문에 멋집니다. 이것을 엄청나게 멋지게 만드는 것은 라인이 순차적으로 실행될 필요가 없다는 것을 의미합니다. 다음 코드를 고려하십시오.

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

methodWithALotOfDiskProcessing ()은 완료하는 데 약 2 초가 걸리고 methodWithALotOfNetworkProcessing ()은 완료하는 데 약 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.

얼마나 멋진가요? 코드를 진행하고 필요한 경우에만 대기함으로써 대기 시간을 자동으로 2 초로 단축했습니다! : D 그렇습니다. 코드는 동기식이지만 절차 언어와는 다른 의미를 갖는 경향이 있습니다.

편집하다:

Godeke의 게시물과 함께이 개념을 이해하면 여러 프로세서, 서버 팜, 중복 데이터 저장소를 활용하는 것이 얼마나 간단 해지고 다른 것을 누가 알고 있는지 쉽게 상상할 수 있습니다.


멋있는! 나는 메시지가 어떻게 처리되고 있는지 완전히 오해했다. 감사합니다. 귀하의 게시물이 도움이됩니다.
Jim Anderson

"기능적 언어는 다음과 같은 것을 시도합니다."-다른 기능적 언어에 대해서는 잘 모르겠지만 Erlang에서 예제는 절차 적 언어의 경우와 똑같이 작동합니다. 당신은 할 수 있습니다 그들을 비동기 적으로이 작업을 실행시키는, 그리고 마지막에 결과를 얻을 수, 산란 프로세스에 의해 병렬로 두 작업을 수행하지만, 코드가 동기하면서 절차 적 언어와는 다른 의미를 가지고하는 경향이있다 "처럼 아니에요. " Chris의 답변도 참조하십시오.
hcs42 2015

16

그것은 당신이 혼합있어 가능성이 높습니다 동기연속 .

erlang의 함수 본문은 순차적으로 처리됩니다. 그래서 스펜서가이 "자동 마법 효과"에 대해 말한 것은 얼랭에게는 사실이 아닙니다. 하지만 erlang으로이 동작을 모델링 할 수 있습니다.

예를 들어 한 줄의 단어 수를 계산하는 프로세스를 생성 할 수 있습니다. 여러 줄이 있으므로 각 줄에 대해 이러한 프로세스 하나를 생성하고 그로부터 합계를 계산하기위한 답변을받습니다.

그런 식으로 "대량"계산 (사용 가능한 경우 추가 코어 사용)을 수행하는 프로세스를 생성하고 나중에 결과를 수집합니다.

-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은 자체 타임 라이 서를 구현합니다.

이러한 Erlang 프로세스는 메시지를 전송하여 서로 통신합니다 (운영 체제가 아닌 Erlang VM에서 처리). Erlang 프로세스는 세 부분으로 된 주소가있는 프로세스 ID (PID)를 사용하여 서로 주소를 지정합니다 <<N3.N2.N1>>.

  • N1 없음 처리
  • VM N2 켜기
  • 물리적 시스템 N3

동일한 VM의 두 프로세스, 같은 머신의 서로 다른 VM 또는 두 대의 머신이 동일한 방식으로 통신합니다. 따라서 확장은 애플리케이션을 배포하는 물리적 머신의 수와 무관합니다 (첫 번째 근사치).

Erlang은 사소한 의미에서 스레드 세이프 일뿐입니다. 스레드가 없습니다. (즉, SMP / 멀티 코어 VM은 코어 당 하나의 운영 체제 스레드를 사용합니다.)


7

얼랭이 어떻게 작동하는지 오해 할 수 있습니다. Erlang 런타임은 CPU에서 컨텍스트 전환을 최소화하지만 사용 가능한 CPU가 여러 개인 경우 모두 메시지를 처리하는 데 사용됩니다. 다른 언어로하는 것과 같은 의미에서 "스레드"는 없지만 동시에 처리되는 많은 메시지를 가질 수 있습니다.


4

Erlang 메시지는 순전히 비동기식이므로 메시지에 대한 동기식 응답을 원하면 명시 적으로 코딩해야합니다. 아마도 프로세스 메시지 상자의 메시지는 순차적으로 처리된다는 것입니다. 프로세스에 전송 된 모든 메시지는 해당 프로세스 메시지 상자에 저장되고 프로세스는 해당 상자에서 하나의 메시지를 선택하여 처리 한 다음 적합하다고 판단되는 순서대로 다음 메시지로 이동합니다. 이것은 매우 순차적 인 동작이며 수신 블록이 정확히 수행합니다.

chris가 언급했듯이 동기식과 순차를 섞은 것 같습니다.



-2

순전히 기능적인 언어에서는 평가 순서가 중요하지 않습니다. 함수 응용 프로그램 fn (arg1, .. argn)에서 n 개의 인수를 병렬로 평가할 수 있습니다. 이는 높은 수준의 (자동) 병렬 처리를 보장합니다.

Erlang은 프로세스가 동일한 가상 머신 또는 다른 프로세서에서 실행될 수있는 프로세스 모델을 사용합니다. 이는 메시지가 프로세스간에 복사되고 공유 (변경 가능) 상태가 없기 때문에 가능합니다. 멀티 프로세서 병렬 처리는 멀티 스레딩보다 훨씬 더 큽니다. 스레드는 공유 메모리에 의존하기 때문에 8 코어 CPU에서 병렬로 실행되는 스레드는 8 개 뿐인 반면 멀티 프로세싱은 수천 개의 병렬 프로세스로 확장 할 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.