code

배우 모델 : 왜 erlang이 특별할까요?

codestyles 2020. 10. 21. 08:07
반응형

배우 모델 : 왜 erlang이 특별할까요? 아니면 왜 다른 언어가 필요합니까?


나는 얼랭을 배우는 것을 조사해 왔고 그 결과 배우 모델에 대해 읽었습니다 (좋아요, 스키밍).

내가 이해하는 바에 따르면 액터 모델은 단순히 메시지 전달을 통해서만 서로 통신하는 함수 (erlang의 "프로세스"라고하는 경량 스레드 내에서 실행)의 집합입니다.

이것은 C ++ 또는 다른 언어로 구현하는 것은 매우 사소한 것 같습니다.

class BaseActor {
    std::queue<BaseMessage*> messages;
    CriticalSection messagecs;
    BaseMessage* Pop();
public:
    void Push(BaseMessage* message)
    {
        auto scopedlock = messagecs.AquireScopedLock();
        messagecs.push(message);
    }
    virtual void ActorFn() = 0;
    virtual ~BaseActor() {} = 0;
}

각 프로세스는 파생 된 BaseActor의 인스턴스입니다. 액터는 메시지 전달을 통해서만 서로 통신합니다. (즉, 밀기). 액터는 초기화시 중앙 맵에 등록하여 다른 액터가 찾을 수 있도록하고 중앙 함수가이를 통해 실행할 수 있도록합니다.

이제 저는 여기서 중요한 문제 하나를 놓치고 있거나, 즉, 항복 부족은 단일 액터가 과도한 시간을 부당하게 소비 할 수 있음을 의미합니다. 그러나 크로스 플랫폼 코 루틴이 C ++에서 이것을 어렵게 만드는 주요 원인입니까? (예를 들어 Windows에는 섬유가 있습니다.)

그래도 내가 놓친 다른 것이 있습니까? 아니면 모델이 정말 그렇게 분명합니까?

저는 여기서 불꽃 전쟁을 시작하려고하는 것이 아닙니다. 제가 놓친 것이 무엇인지 이해하고 싶습니다. 이것은 본질적으로 동시 코드에 대해 어느 정도 추론 할 수 있도록 이미 수행 한 작업이기 때문입니다.


C ++ 코드는 Erlang이 행위자 모델의 일부로 제공하는 모든 것 인 공정성, 격리, 오류 감지 또는 배포를 다루지 않습니다.

  • 어떤 배우도 다른 배우를 굶겨서는 안됩니다 (공정성)
  • 한 액터가 충돌하면 해당 액터에만 영향을 미칩니다 (격리).
  • 한 액터가 충돌하면 다른 액터가 해당 충돌을 감지하고 이에 대응할 수 있어야합니다 (오류 감지).
  • 액터는 마치 같은 머신에있는 것처럼 네트워크를 통해 통신 할 수 있어야합니다 (배포).

또한 빔 SMP 에뮬레이터는 액터의 JIT 스케줄링을 가져 와서 현재 사용률이 가장 낮은 코어로 이동하고 더 이상 필요하지 않은 경우 특정 코어에서 스레드를 최대 절전 모드로 전환합니다.

또한 Erlang으로 작성된 모든 라이브러리와 도구는 이것이 세계가 작동하는 방식이고 그에 따라 설계되는 방식이라고 가정 할 수 있습니다.

이러한 작업은 C ++에서 불가능하지는 않지만 Erlang이 거의 모든 주요 hw 및 os 구성에서 작동한다는 사실을 추가하면 점점 어려워집니다.

편집 : 방금 Ulf Wiger 가 erlang 스타일 동시성을 보는 것에 대한 설명을 찾았습니다 .


나는 내 자신을 인용하고 싶지 않지만 Virding의 첫 번째 프로그래밍 규칙에서

다른 언어로 된 충분히 복잡한 동시 프로그램에는 Erlang의 절반에 대한 임시 비공식적으로 지정된 버그가있는 느린 구현이 포함되어 있습니다.

Greenspun과 관련하여. Joe (Armstrong)도 비슷한 규칙을 가지고 있습니다.

문제는 배우를 구현하는 것이 아니라 그렇게 어렵지 않습니다. 문제는 프로세스, 통신, 가비지 수집, 언어 프리미티브, 오류 처리 등 모든 것이 함께 작동하도록하는 것입니다. 예를 들어 OS 스레드를 사용하면 확장이 심하므로 직접 수행해야합니다. 그것은 당신이 1k 개의 객체를 가질 수 있고 그것들을 만들고 사용하기가 무겁게하는 OO 언어를 "판매"하려고하는 것과 같습니다. 우리의 관점에서 동시성은 애플리케이션 구조화를위한 기본 추상화입니다.

여기서 멈출 것입니다.


이것은 실제로 훌륭한 질문이며 아직 설득력이없는 훌륭한 답변을 받았습니다.

이미 여기에있는 다른 훌륭한 답변에 음영과 강조를 추가하려면 내결함성 및 가동 시간을 달성하기 위해 Erlang 이 제거하는 사항 (C / C ++와 같은 기존의 범용 언어와 비교)을 고려하십시오 .

첫째, 자물쇠를 제거합니다. Joe Armstrong의 책은이 사고 실험을 설명합니다. 프로세스가 잠금을 획득 한 후 즉시 충돌한다고 가정합니다 (메모리 결함으로 인해 프로세스가 충돌하거나 전원이 시스템의 일부에 실패 함). 다음에 프로세스가 동일한 잠금을 기다릴 때 시스템은 방금 교착 상태가됩니다. 이는 샘플 코드의 AquireScopedLock () 호출에서와 같이 명백한 잠금 일 수 있습니다. 또는 malloc () 또는 free ()를 호출 할 때와 같이 메모리 관리자가 사용자를 대신하여 획득 한 암시 적 잠금 일 수 있습니다.

어쨌든 프로세스 충돌로 인해 전체 시스템의 진행이 중단되었습니다. 피니. 이야기의 끝. 시스템이 죽었습니다. C / C ++에서 사용하는 모든 라이브러리가 malloc을 호출하지 않고 잠금을 획득하지 않는다고 보장 할 수없는 경우 시스템은 내결함성이 없습니다. Erlang 시스템은 작업을 진행하기 위해 부하가 높을 때 마음대로 프로세스를 종료 할 수 있습니다. 따라서 처리량을 유지하려면 대규모 Erlang 프로세스를 강제 종료 할 수 있어야합니다 (모든 단일 실행 지점에서).

부분적인 해결 방법이 있습니다. 잠금 대신 모든 곳에서 임대를 사용하지만 사용하는 모든 라이브러리가이를 수행한다는 보장은 없습니다. 그리고 정확성에 대한 논리와 추론은 정말 빨리 털이 많아집니다. 또한리스는 시간 초과가 만료 된 후 느리게 복구되므로 전체 시스템이 실패시 정말 느려집니다.

둘째, Erlang은 정적 타이핑을 제거하여 핫 코드 스와핑을 활성화하고 동일한 코드의 두 버전을 동시에 실행할 수 있습니다. 즉, 시스템을 중지하지 않고 런타임에 코드를 업그레이드 할 수 있습니다. 이것이 시스템이 9 번 9 초 또는 32 밀리 초의 다운 타임을 유지하는 방법입니다. 단순히 제자리에서 업그레이드됩니다. 업그레이드하려면 C ++ 함수를 수동으로 다시 연결해야하며 두 버전을 동시에 실행하는 것은 지원되지 않습니다. 코드 업그레이드에는 시스템 중단 시간이 필요하며 한 번에 둘 이상의 코드 버전을 실행할 수없는 대규모 클러스터가있는 경우 전체 클러스터를 한 번에 중단해야합니다. 아야. 그리고 통신 세계에서는 용납 할 수 없습니다.

또한 Erlang은 공유 메모리와 공유 가비지 컬렉션을 제거합니다. 각 경량 프로세스는 독립적으로 가비지 수집됩니다. 이것은 첫 번째 요점의 간단한 확장이지만 진정한 내결함성을 위해서는 종속성 측면에서 연동되지 않는 프로세스가 필요하다는 점을 강조합니다. 즉, Java에 비해 GC 일시 중지는 큰 시스템에서 허용됩니다 (8GB GC를 완료하는 데 30 분을 일시 중지하는 대신 작음).


C ++ 용 실제 액터 라이브러리가 있습니다.

그리고 다른 언어에 대한 일부 라이브러리 목록 .


It is a lot less about the actor model and a lot more about how hard it is to properly write something analogous to OTP in C++. Also, different operating systems provide radically different debugging and system tooling, and Erlang's VM and several language constructs support a uniform way of figuring out just what all those processes are up to which would be very hard to do in a uniform way (or maybe do at all) across several platforms. (It is important to remember that Erlang/OTP predates the current buzz over the term "actor model", so in some cases these sort of discussions are comparing apples and pterodactyls; great ideas are prone to independent invention.)

All this means that while you certainly can write an "actor model" suite of programs in another language (I know, I have done this for a long time in Python, C and Guile without realizing it before I encountered Erlang, including a form of monitors and links, and before I'd ever heard the term "actor model"), understanding how the processes your code actually spawns and what is happening amongst them is extremely difficult. Erlang enforces rules that an OS simply can't without major kernel overhauls -- kernel overhauls that would probably not be beneficial overall. These rules manifest themselves as both general restrictions on the programmer (which can always be gotten around if you really need to) and basic promises the system guarantees for the programmer (which can be deliberately broken if you really need to also).

For example, it enforces that two processes cannot share state to protect you from side effects. This does not mean that every function must be "pure" in the sense that everything is referentially transparent (obviously not, though making as much of your program referentially transparent as practical is a clear design goal of most Erlang projects), but rather that two processes aren't constantly creating race conditions related to shared state or contention. (This is more what "side effects" means in the context of Erlang, by the way; knowing that may help you decipher some of the discussion questioning whether Erlang is "really functional or not" when compared with Haskell or toy "pure" languages.)

On the other hand, the Erlang runtime guarantees delivery of messages. This is something sorely missed in an environment where you must communicate purely over unmanaged ports, pipes, shared memory and common files which the OS kernel is the only one managing (and OS kernel management of these resources is necessarily extremely minimal compared to what the Erlang runtime provides). This doesn't meant that Erlang guarantees RPC (anyway, message passing is not RPC, nor is it method invocation!), it doesn't promise that your message is addressed correctly, and it doesn't promise that a process you're trying to send a message to exists or is alive, either. It just guarantees delivery if the thing your sending to happens to be valid at that moment.

Built on this promise is the promise that monitors and links are accurate. And based on that the Erlang runtime makes the entire concept of "network cluster" sort of melt away once you grasp what is going on with the system (and how to use erl_connect...). This permits you to hop over a set of tricky concurrency cases already, which gives one a big head start on coding for the successful case instead of getting mired in the swamp of defensive techniques required for naked concurrent programming.

So its not really about needing Erlang, the language, its about the runtime and OTP already existing, being expressed in a rather clean way, and implementing anything close to it in another language being extremely hard. OTP is just a hard act to follow. In the same vein, we don't really need C++, either, we could just stick to raw binary input, Brainfuck and consider Assembler our high level language. We also don't need trains or ships, as we all know how to walk and swim.

All that said, the VM's bytecode is well documented, and a number of alternative languages have emerged that compile to it or work with the Erlang runtime. If we break the question into a language/syntax part ("Do I have to understand Moon Runes to do concurrency?") and a platform part ("Is OTP the most mature way to do concurrency, and will it guide me around the trickiest, most common pitfalls to be found in a concurrent, distributed environment?") then the answer is ("no", "yes").


Casablanca is another new kid on the actor model block. A typical asynchronous accept looks like this:

PID replyTo;
NameQuery request;
accept_request().then([=](std::tuple<NameQuery,PID> request)
{
   if (std::get<0>(request) == FirstName)
       std::get<1>(request).send("Niklas");
   else
       std::get<1>(request).send("Gustafsson");
}

(Personally, I find that CAF does a better job at hiding the pattern matching behind a nice interface.)

참고URL : https://stackoverflow.com/questions/8107612/the-actor-model-why-is-erlang-special-or-why-do-you-need-another-language-for

반응형