어떤 언어로든 직관적인 동시성 프로그래밍

Intuitive concurrent programming in any language

lf-lang.org ▲ 126 댓글 35 mpweiher

요약

Lingua Franca는 여러 언어로 쓴 코드를 리액터로 묶어 실행 결과가 늘 같은 동시성 프로그램을 만들게 합니다.

UC 버클리에서 시작해 킬 대학교, 드레스덴 공과대학교 등이 함께 개발해 온 오픈소스 조정 언어(여러 코드 조각의 실행 순서와 통신을 정하는 언어)의 공식 홈페이지입니다. 수십 년의 연구를 바탕으로 스레드, 발행-구독, 액터 모델보다 동작을 더 잘 재현할 수 있다고 소개합니다.

왜 중요한가

  • 개발자가 스레드와 동기화를 직접 다루지 않아도 경쟁 상태(실행 순서에 따라 결과가 달라지는 버그)가 구조적으로 생기지 않는다고 주장합니다.
  • 시간 개념이 언어 핵심에 들어 있어, 임베디드처럼 타이밍이 중요한 시스템을 겨냥합니다.
  • 새 언어로 로직을 다시 쓰지 않고, 기존 언어 코드는 그대로 둔 채 조정만 맡기는 방식입니다.

핵심 내용

  • 프로그램은 입력·출력 포트를 가진 리액터(반응형 구성 요소)를 정의하고 서로 연결해 만듭니다.
  • 리액터 안의 로직은 C, C++, Python, TypeScript, Rust 가운데 하나로 씁니다.
  • 가위바위보 예제에서 한쪽이 상대 선택을 본 뒤 자기 선택을 내면 컴파일러가 인과 관계 고리로 보고 거부합니다.
  • 언어 안에서 "동시에"의 뜻이 정확히 정해져 있어, 두 선수가 공정하게 동시에 내는 게임을 간단히 만들 수 있다고 합니다.
  • 단일 시스템용 프로그램을 거의 고치지 않고 분산 시스템으로 옮길 수 있다고 설명합니다.

HN 반응

  • 여러 이용자가 무엇을 푸는 도구인지 알기 어렵고, "어떤 언어로든"이라면서 다섯 언어만 지원한다고 지적했습니다.
  • Erlang과 비교해서는 지연이 생겨도 논리 시간으로 메시지 순서를 지키는 결정성이 장점이라는 설명과, 구조가 고정돼야만 결정적이라는 반론이 나왔습니다.

댓글

30개 표시 · 전체 35개
  1. samuell HN

    코드 예시를 보니 흐름 기반 프로그래밍(flow-based programming)에서 "포트" 개념을 가져온 것 같은데, 저는 이게 마음에 듭니다. 어쩌면 가장 강력한 장점 중 하나일 수도 있고요.

        player2.reveal -> player1.observe
        player1.reveal -> player2.observe
    

    즉 컴포넌트 사이뿐 아니라 개별 입력과 출력 사이에도 데이터 흐름 연결을 만들 수 있다는 뜻입니다. 컴포넌트 단위 연결만 되면 너무 제한적이고, 그렇지 않으면 그 경로를 처리하느라 보통 문법이 잔뜩 더 필요해집니다.

    예전에 워크플로 시스템 맥락에서 이 얘기를 쓴 적이 있습니다( https://livingsystems.cc/posts/workflows-dataflow-not-task-deps/ ).


    11:12 CET 수정: 제가 염두에 둔 건 "이름 붙은 포트(named ports)"라는 표현이 더 정확하다는 걸 깨달았습니다. 아래 댓글에서 더 자세히 적었습니다.

  2. mpweiher HN

    포트는 소프트웨어 아키텍처 문헌(과 그에 해당하는 시스템들)에서 컴포넌트의 일반적인 특징입니다.

    여기에 더해 커넥터도 구조를 갖게 됩니다.

    Objective-Smalltalk는 다형적(polymorphic) 커넥터를 써서, 어느 수준의 세분성(granularity)으로 작업할지를 상황에 맞게, 대부분 자동으로 고릅니다.

    https://dl.acm.org/doi/10.1145/3689492.3690052


    그건 그렇고, 정말 멋진 글이네요! 데이터플로는 정말로 유용한 추상화라서 곳곳에서 불쑥불쑥 나타나는데, 우리 언어들은 이걸 범용 메커니즘으로는 잘 지원하지 못합니다.

    대신 프로그래밍의 한 분야에 한해서 데이터플로 비슷한 특화 도구를 쓰는 경우가 많습니다. 이점이 워낙 커서 임피던스 불일치를 감수할 만하기 때문입니다.

  3. samuell HN

    맞는 말씀 같습니다. 미리 검색해 봤어야 했네요. 찾아보니 FBP(흐름 기반 프로그래밍)를 만든 사람이 1975년의 Weinberg라는 사람을 인용하고 있더군요(FBP 자체는 69~70년경에 나왔다고 하니, 어느 정도 평행하게 이루어진 발견일 수도 있겠습니다).

    그렇다면 우리 의사코드가 IP를 받아 오고 내보내는 "IN"과 "OUT"은 무엇일까요? 이것들은 연결의 이름이 아닙니다. 연결의 이름이라면 컴포넌트의 코드가 네트워크의 한 지점에 묶여 버리기 때문입니다. 이것들은 "포트"라고 불리는 것입니다. "Portae"는 라틴어로 "문"을 뜻하며, 포트는 (항구처럼) 사물이나 사람이 드나드는 벽이나 경계의 특정 지점이라고 생각할 수 있습니다. Weinberg(1975)는 포트를 이렇게 설명합니다.

    "... 입력과 출력이 흐르는 경계 위의 특별한 장소. 입력과 출력이라는 위험한 과정은 오직 포트가 있는 곳 안에서만 일어날 수 있으며, 이런 과정을 그곳에 국한함으로써 입출력이라는 특수한 문제에 특별한 메커니즘을 적용할 수 있다."

    https://www.jpaulmorrison.com/fbp/concepts_book.shtml

    그래도 데이터 흐름 지향 라이브러리와 DSL에서 포트가 주류가 되기까지 이렇게 오래 걸렸다는 게 놀랍습니다.

    J.P. Morrison도 특히 이름 붙은(named) 포트가 유용하다고 강조했던 것 같은데, 제가 처음 댓글에서 강조했어야 할 부분도 아마 그쪽이었을 겁니다.

    요즘은 나아진 것 같지만, 많은 도구가 그 부분을 건너뛰었고, 그래서 지금은 병렬 스트림 여러 개를 라우팅하기 위해서만 특수 문법이 잔뜩 필요합니다.


    좋게 봐 주셔서 감사합니다!

  4. samuell HN

    Zotero에서 대충 훑어봤는데 Objective-Smalltalk 논문도 정말 흥미로워 보입니다! 다음 주에 제대로 파고들어 봐야겠습니다.

  5. chrisweekly HN

    it's(it is) -> its(그것의)

    깐깐하게 굴려는 건 아닙니다. 하지만 두 단어는 서로 다르고, (특히 우리 커뮤니티가 프로그래밍 언어를 다루며 논리와 정밀함에 얼마나 능한지를 생각하면) 이 "it's / its" 오류가 이렇게 자주 반복되는 게 이상합니다. 다른 분들이 올바르게 쓰는 데 도움이 되기를 바라며 짚어 봅니다.

  6. anonymous908213 HN

    이 오류가 자주 반복되는 건 전혀 이상한 일이 아니에요. 영어의 "설계" 결함이거든요. 영어는 규칙이라는 게 있다 해도 그 규칙을 스스로 어깁니다. 어포스트로피는 소유격과 줄임형 둘 다에 쓰이잖아요. 예를 들면 "That's John's ball"(저건 존의 공이야)처럼요. 사실 소유격 "it's"도 역사적으로는 어포스트로피를 붙여 썼어요. 그 어포스트로피가 빠져서 소유격 "its"가 된 건 완전히 임의적인 예외 규칙이고, 어느 쪽에서 뺄지를 정해 주는 논리가 없으니 순간적으로 기억이 안 나면 따져서 구분할 수도 없어서 쉽게 헷갈리는 거죠.

  7. chrisweekly HN

    답변 감사합니다. 흥미로운 지적이지만 저는 생각이 다릅니다. 줄임형은 항상 어포스트로피를 쓰지만 소유격은 가끔만 씁니다 [they're / their, you're / your, he's / his, she's / her, it's / its, ...]. "it"의 경우에는 어포스트로피 하나만으로 두 단어가 구별됩니다.

  8. anonymous908213 HN

    줄임형은 항상 어포스트로피를 쓰지만

    정말 그렇게 말씀하실 거예요(gonna)? 다시 생각해 볼 마음 없으세요(wanna)? 그건 좀 아닌 것 같은데요(kinda sorta). 이 말은 해야겠어요(gotta). 특히 곧 할로윈(Halloween)이 다가오는 마당에는요.

    "it"의 경우 어포스트로피 하나만으로 두 단어가 구별됩니다.

    그건 그렇죠. 그런데 그게 어느 쪽에서 어포스트로피를 뺄지까지 정해 주지는 않잖아요. 영어는 줄임형 쪽에서 빼 버릴 수도 있었다고요(coulda).

  9. nagaiaida HN

    "its"는 "hers"와 정확히 같은 구조 아닌가요?

  10. RHSeeger HN

    맞아요, "논리와 정밀함"과는 거의 정반대죠.

  11. samuell HN

    네, 이론적으로는 알고 있는데 영어가 모국어가 아니다 보니 가끔 헷갈리네요. 그래도 이런 흔한 실수를 바로잡아 가는 건 분명 좋은 일입니다.

  12. loofbanger-meis HN

    틀리셨어요. It's는 둘 다 될 수 있습니다. 둘 다 맞아요.

    저는 깐깐하게 굴려는 겁니다. 당신이 올바르게 쓰도록 돕고 싶어서 짚는 거예요.

  13. globnomulous HN

    진심인지 아닌지 모르겠네요. "It's"는 소유격이 아닙니다. 소유격으로 쓰면 틀린 거예요.

  14. chrisweekly HN

    틀렸습니다.

  15. XCabbage HN

    처음 봐서는 이게 어떤 문제를 풀려는 건지 잘 이해가 안 됩니다. 평범한 파이썬으로 짠 동시성 예제 프로그램을 먼저 보여 주고, 동시성 오류를 피하려면 어떤 귀찮은 관리 작업을 해야 하고 어떤 함정에 쉽게 빠지는지 설명한 다음, 같은 것을 Lingua Franca로 구현한 예제와 그것이 어떻게 그 관리 작업과 함정을 없애 주는지 설명이 이어지면 좋겠습니다.

  16. tgv HN

    플레이그라운드에서 직접 돌려 볼 수 있는 파이썬 예제들입니다: https://github.com/lf-lang/playground-lingua-franca/tree/main/examples/Python

  17. stefanha HN

    이 예제들이 무엇을 보여 주려는 건지 설명해 주실 수 있나요? Audio와 Webserver 예제를 봤는데, 파이썬에서 직접 하는 편이 더 쉬운 일을 LF 문법으로 적어 놓은 것 같았습니다. LF의 장점이 뭔지, 애초에 왜 이렇게 해야 하는지 모르겠습니다.

  18. phyzome HN

    한 줄 소개를 다듬을 필요가 있어요.

    "모든 언어에서"라고 해 놓고 페이지에는 "We currently support C, C++, Python, TypeScript, and Rust."(현재 C, C++, Python, TypeScript, Rust를 지원합니다)라고 적혀 있고요.

    "Lingua Franca는 조율(coordination) 언어입니다"라고 하면서 "Not a New Programming Language"(새로운 프로그래밍 언어가 아닙니다)라고도 하죠.

    무슨 말을 하려는지는 알 것 같은데, 설명이 훨씬 더 나아져야 해요.

  19. Garlef HN

    랜딩 페이지의 메인 예제가 좀 직관적이지 않은 것 같아요.

    가위바위보인데 그래프에서는 reveal(공개)이 observe(관찰)에 대한 반응으로 나오죠? 그것도 루프 안에서요? 그리고 어딘가에 마법 같은 1초짜리 min-delay(최소 지연)가 있고요. 왜 0.1초가 아니라 1초일까요? 이게 쌓이면 플레이어끼리 동기가 어긋날 수도 있나요? (분산 시스템의 문제를 풀겠다는 언어가 "그냥 기다리면 되지, ㅋㅋ" 하는 건 좀 이상하네요.)

    전체적으로 이 예제는 게임과 플레이어를 분리하고, 반복 루프도 따로 빼 주면 더 좋았을 것 같아요.

    예를 들면 두 플레이어가 모두 호스트에게 reveal하고, 그다음 결과(와 각자의 선택)를 돌려받게 하는 거죠. 그러면 양쪽 플레이어 모두 상대의 선택이 자신의 관찰과 무관하다는 걸 확신할 수 있습니다.

    (이 예제에서 선택이 관찰과 독립적으로 이루어진다는 점에서 "올바른" 예제라는 건 알아요. 다만 이 시나리오에 얽힌 조율 문제 일부를 코드 구조로 더 깔끔하게 풀어냈다면 예제가 더 나아졌을 거라고 생각해요.)

    그래도 전체적으로 이런 다언어 메타 언어라는 아이디어는 꽤 마음에 듭니다!

  20. tete HN

    랜딩 페이지의 메인 예제가 좀 직관적이지 않은 것 같아요.

    그래도 지금의 Go 랜딩 페이지보다는 백만 배 낫습니다. Go의 열렬한 팬인 제가 하는 말이에요.

  21. gbin HN

    이 아이디어가 마음에 들고 로보틱스 분야에서 일하신다면 copper-rs를 써 보세요. 같은 기본 개념에 기반하지만, Rust와 그래프 기술(description)을 이용해 컴파일 타임에 동작합니다. 커널에 최근 추가된 기능은 프로파일 기반 스케줄링인데, 결정적(deterministic) 로그에 남은 이전 타이밍 측정값을 바탕으로 그래프의 동시성을 역시 컴파일 타임에 최적화할 수 있게 해 줍니다.

  22. Haven880 HN

    Erlang이 이런 건 아주 잘하잖아요. LF가 더 나은 점이 있나요? 그냥 의견이 궁금해서요.

  23. Jtsummers HN

    제가 보기에 LF가 주로 제공하는 건 결정성(determinism)입니다. 질문하신 Erlang은 (특별히 신경 쓰지 않으면) 비결정적일 수 있고요. 저는 이 연구의 바탕이 된 학위논문을 읽으면서 어떻게 결정성을 보장하는지 확인하는 중이라, 지금은 그쪽 말을 그냥 믿고 있습니다.

    https://www2.eecs.berkeley.edu/Pubs/TechRpts/2020/Archive/EECS-2020-235.pdf

    1장에 다음 시나리오의 예가 나옵니다(Erlang 문법으로 쓴 것인데, 전혀 테스트하지 않았고 제가 Erlang을 오래 안 만져서 실행하려면 손볼 데가 있을 겁니다). 이 코드는 Erlang에서 결정적입니다.

      counter() -> counter(1).
      counter(N) ->
        receive
          dbl -> counter(N*2);
          {inc, Arg} ->
            N1 = N1 + Arg,
            io:format("~w~n", [N1]),
            counter(N1)
        end.
    
    main() ->
        P = spawn(counter),
        P ! dbl,
        P ! {inc, 1}.
    

    한 프로세스에서 다른 프로세스로 보낸 메시지는 보낸 순서 그대로 전달되는 것이 보장되므로 이 코드는 결정적입니다. 논문은 5쪽(PDF로는 21쪽)에서 릴레이를 도입한 동기 부여용 예제를 소개합니다.

      relay() ->
        receive
          P -> P ! dbl
        end,
        relay(P).
    
    main_with_relay() ->
        P = spawn(counter),
        R = spawn(relay),
        R ! P,    % <- trouble starts here
        P ! {inc, 1}.
    

    같은 노드에서 실행하면 첫 번째 예제와 같은 결과가 나올 수도 있지만 보장은 사라지고, 첫 번째 버전과 같은 순서로 일어나지 않을 수 있습니다. 결과가 달라질 가능성이 높은 경우를 들면, counter가 main과 같은 노드의 로컬 프로세스이고 relay는 지연 시간이 긴 다른 노드의 원격 프로세스라고 해 봅시다. 그러면 네트워크 지연 때문에 {inc, 1}이 dbl보다 먼저 도착할 가능성이 높습니다.

    LF에서는 (어떻게 되는지는 묻지 마세요, 저도 아직 읽는 중이니까요) 릴레이가 물리적으로는 즉각적이지 않더라도 논리적으로는 즉각적이도록 쓸 수 있습니다. 그러면 dbl 메시지가 논리적 시간상 "먼저" 도착하고, counter는 원래 Erlang 코드처럼 일을 처리하게 됩니다.

    물론 Erlang의 릴레이 동작도 결정적으로 만들 수 있지만, 그러려면 코드를 더 추가해야 합니다. LF는 원하는 결정성을 직접 추가 코드를 쓰지 않고도 더 간단하게 얻을 수 있게 해 주는 듯합니다. 결정성이 의미론에 내장되어 있으니까요.

  24. neilwilson HN

    정적인 구조에서만 결정적인 것 같습니다. 프로그램이 액터를 추가하고 삭제하면 LF는 문제가 생깁니다. 이를 피하려고 모드(modes)라는 것을 쓰려 하지만요.

    Sim-Diasca는 Erlang으로 만든 시뮬레이션 시스템으로, 분산 시간 관리자와 전역 배리어를 통해 LF에서 쓰는 슈퍼덴스 시간(superdense time) 단계 메커니즘을 구현해 재현 가능한 결과를 줍니다.

  25. scythmic_waves HN

    이 프로젝트는 설득력 있는 홍보 문구가 없네요. LF가 액터보다 낫다고 언급은 하는데, 저는 그렇게 믿을 만한 근거도 못 찾았어요.

    리액터(reactor)가 무엇인지는 깊이 있게 설명해 주지만, 제가 왜 굳이 배워야 하죠? OP가 제 댓글을 읽는다면, 이 랜딩 페이지에 그걸 추가하라고 말하고 싶어요. 언급된 대안들이 왜 열등한지를 보여 주세요.

  26. Surac HN

    모든 언어요? COBOL, Fortran, C#은 안 보이던데요.

  27. justinpombrio HN

    라이브러리를 만드는 접근 방식입니다. COBOL, Fortran, C#용으로도 구현할 수 있습니다.

  28. andvgal HN

    궁금한 게 있습니다. 연구를 그렇게 오래 하셨다면서, 왜 진짜 문제는 reactor/proactor/이벤트 루프가 아니라 실제 비동기 비즈니스 로직을 작성하는 일이라는 결론에 이르지 못하셨나요? 업계는 대체로 코루틴 방식으로 갔는데, 이 방식은 특별한 기술 지원이 필요하고, 미션 크리티컬한 경우에 필요한 세밀하고 예측 가능한 실행 및 스택 제어가 보통 부족합니다. 그래서 FutoIn AsyncSteps라는 개념과 C++, Java, JS/ECMAScript 참조 구현이 존재합니다. AsyncTool 인터페이스를 통해 설계상 외부 reactor와도 호환됩니다.

  29. rramadass HN

    흥미롭네요.

    "Reactor"라는 단어를 쓰셔서 처음엔 헷갈렸습니다. 이벤트 처리에 쓰는 "Reactor 패턴"( https://en.wikipedia.org/wiki/Reactor_pattern )이 있는데, 그 의미가 아니니까요.

    이 동시성 모델은 (분산 동시성의 대표 주자인) Erlang의 모델과 어떻게 다른가요? 모델을 자세히 설명하고 다른 언어들과 비교한 논문이 있나요?

    최근 Roman Obermaisser의 훌륭한 책 "Event-Triggered and Time-Triggered Control Paradigms"를 읽었는데, 여러분의 아키텍처에 적용될 만한 내용 같았습니다. 가장 아래 계층에는 동기화된 전역 시계와 결정적인 "시간 트리거(Time-Triggered, TT)" 의미론에 기반한 전역 통신 네트워크가 있습니다. 그 위에 비결정적인 "이벤트 트리거(Event-Triggered, ET)" 통신 채널이 겹쳐 놓입니다. 이렇게 해서 시스템이 하드 실시간(TT)과 소프트 실시간(ET) 의미론을 모두 제공합니다.

  30. einpoklum HN

    물론 제목은 틀렸습니다. 정확히는 "실제 작업에 쓰는 언어와는 별개의 언어로 하는 직관적인 동시성 프로그래밍"이죠.

    ... 그렇다고 의미가 없다는 건 아닙니다. 다만 이걸 위해 별도의 언어를 두는 게 왜 가치가 있나요? 여러 언어에 바인딩을 제공하는 라이브러리면 되지 않나요?

Hacker News에서 보기 ↗