1P by GN⁺ 11시간전 | ★ favorite | 댓글 1개
  • 비동기동시성은 자주 혼동되는 개념이지만, 서로 다른 의미를 가짐
  • 비동기는 작업들이 순서에 상관없이 실행될 수 있는 가능성을 뜻함
  • 동시성은 시스템이 여러 작업을 동시에 진행할 수 있는 능력을 의미함
  • 언어 및 라이브러리 생태계에서 두 개념의 명확한 구분 부재로 비효율성과 복잡성이 발생함
  • Zig 언어에서는 비동기와 동시성의 분리를 통해 코드 중복 없이 동기 및 비동기 코드의 공존이 가능함

서론: 비동기와 동시성의 구별 필요성

Rob Pike의 유명한 발표로 '동시성은 병렬성이 아니다'라는 문장이 잘 알려져 있지만, 이보다 실질적으로 중요한 논점이 존재함. 바로 '비동기'라는 개념의 필요성임. 위키백과 정의에 따르면,

  • 동시성: 시스템이 동시에 여러 작업을 시간 분할 또는 병렬적으로 처리할 수 있는 능력
  • 병렬 컴퓨팅: 실제 물리적 수준에서 여러 작업을 동시 실행하는 것
    이 외에, 우리가 놓치고 있는 중요한 개념이 바로 '비동기'임.

예시 1: 두 파일 저장

두 파일(A, B)을 저장할 때 순서가 상관없다면,

io.async(saveFileA, .{io})
io.async(saveFileB, .{io})
  • A를 먼저 저장하거나 B를 먼저 저장해도 무방하며, 중간에 번갈아가며 저장해도 문제가 없음
  • 심지어 A 파일을 다 저장한 후 B 파일을 시작해도 코드상으로는 올바름

예시 2: 두 소켓 (서버, 클라이언트)

동일 프로그램 내에서 TCP 서버를 만들고 클라이언트를 연결해야 할 때,

io.async(Server.accept, .{server, io})
io.async(Client.connect, .{client, io})
  • 이 경우에는 두 작업의 실행이 반드시 겹쳐서 진행될 필요가 있음
  • 즉, 서버가 연결을 받아들이는 동안에 클라이언트도 연결을 시도해야 함
  • 첫 번째 파일 예시처럼 직렬로 처리하면 의도된 동작이 나오지 않음

개념 정리

비동기, 동시성, 병렬성의 개념을 다음과 같이 정의함

  • 비동기(asynchrony) : 작업들이 순서를 벗어나 실행되어도 올바른 결과가 나오는 성질
  • 동시성(concurrency) : 병렬적이든 분할 실행이든 여러 작업을 동시에 전개할 수 있는 능력
  • 병렬성(parallelism) : 물리적으로 여러 작업이 실시간으로 동시에 실행되는 능력

파일 저장과 소켓 연결 두 예시는 모두 비동기적이지만, 두 번째(서버-클라이언트)는 동시성이 반드시 필요

비동기와 동시성 구분의 실익

이 구분을 하지 않으면 다음과 같은 문제가 초래됨

  • 라이브러리 제작자들은 비동기/동기 버전의 코드를 두 번 짜야 함 (예: redis-py vs asyncio-redis)
  • 사용자는 비동기 코드가 '전염성'을 띠어, 단 하나의 비동기 라이브러리 의존성만 있어도 전체 프로젝트를 비동기로 바꾸어야 하는 불편함이 발생
  • 이를 피하고자 편법적인 우회가 생기고, 이는 종종 *데드락(deadlock)* 과 비효율을 유발함

따라서, 두 개념의 명확한 분리는 라이브러리와 사용자 모두에게 큰 이점을 제공함

Zig: 비동기와 동시성의 분리

Zig 언어는 io.async를 통해 비동기를 사용하지만, 이는 동시성을 보장하지 않음

  • 즉, io.async를 써도 내부적으로는 싱글 스레드, 블로킹 모드로 실행 가능
  • 예를 들어
    io.async(saveFileA, .{io})
    io.async(saveFileB, .{io})
    
    이 코드는 블로킹 환경에서
    saveFileA(io)
    saveFileB(io)
    
    와 동일하게 동작할 수 있음
  • 즉, 라이브러리 제작자가 io.async를 사용해도 사용자는 원하면 순차적 블로킹 IO로 실행할 수 있는 유연성을 확보

동시성 도입과 작업 전환(스케줄링) 메커니즘

동시성이 필요한 경우, 실제 효과적 동작을 위해서는

  1. 블로킹이 아닌 이벤트 기반 IO (epoll, io_uring 등) 사용
  2. 작업 전환(스위칭) 프리미티브(예: yield) 사용 필요
  • 예시로, Zig는 그린스레드 환경에서 스택 스와핑 기법을 사용해 작업 전환을 수행
  • OS 수준의 스레드 스케줄링과 유사하게, CPU 레지스터와 스택 등 상태를 저장/복원하여 여러 태스크 간 전환 진행
  • 이러한 전환 메커니즘이 있어야 비동기 코드를 실제 동시적으로 스케줄할 수 있음
  • 스택리스 코루틴 구현(예: suspend, resume)도 동일 원리임

동기 코드와 비동기 코드의 공존

아래처럼 두 번의 saveDataio.async로 실행하면,

io.async(saveData, .{io, "a", "b"})
io.async(saveData, .{io, "c", "d"})
  • 두 작업이 서로 비동기적이므로, 내부적으로 동기적으로 작성된 함수여도 자연스럽게 작업들을 동시성 컨텍스트에서 스케줄 가능
  • 사용자나 라이브러리 제작자가 코드 중복 없이 동기/비동기 함수를 함께 써도 문제가 없음

동시성이 '필수'인 상황 명시하기

특정 함수(예: TCP 서버의 accept)는 실행 시 명시적으로 동시성이 필요함을 코드에 표현할 필요가 있음

  • 이를 Zig에서는 io.asyncConcurrent 등 명시적 함수로 구분
  • 이러한 방식은 해당 작업이 실행 환경에서 동시성을 지원하지 않으면 에러를 발생시켜줌
  • 비동기 목적의 io.async와 달리, 동시성 보장이 필수이므로 failable 함수로 구현됨

결론

  • 비동기와 동시성은 전혀 다른 개념이며, 명확히 구분해야 함
  • 동기 코드와 비동기 코드를 공존시키는 것이 가능함
  • Zig의 비동기/동시성 모델은 코드 중복 없이 두 세계를 함께 활용하게 해줌
  • 이러한 구조는 Go 등의 다른 언어에도 적용된 바 있으며, async/await의 전염성을 극복할 수 있는 길 제시
  • Zig의 새로운 async I/O 설계를 통해, 앞으로 더욱 직관적인 동시성/비동기 프로그래밍 환경 기대 가능
Hacker News 의견
  • async 정의는 참 어렵게 느껴짐, 나 역시 JavaScript에서 async를 설계한 여러 사람 중 한 명임, 이 글에서 제시한 정의에는 동의하지 않음, 단순히 async라는 이유로 올바르게 동작하는 건 아님, async 코드에서도 여전히 여러 종류의 유저 레벨 레이스 컨디션이 발생할 수 있음, 언어가 async/await를 지원하든 그렇지 않든 마찬가지임, 내가 최근 내린 정의는 async란 “동시성을 위해 명시적으로 구조화된 코드”라는 것임, 이 관점도 아직 더 다듬어져야 함, 관련해 내가 직접 정리한 글도 있음 Quite a few words about async 참고 바람

    • 비동기(Asynchronism)라는 추상적 개념과 실질적 구현을 구분하는 것이 중요하다고 생각함, 후자는 언어 차원의 추상 혹은 기계적 조정 수단 모두를 포함함, 최고 수준의 추상적 개념에서 동기(Synchronism)의 반대가 바로 비동기임, 보통 여러 주체가 함께 동작해야 할 때(예를 들면 한 작업이 끝나야 다른 작업이 진행됨) 언제 그 일이 일어날지 알 수 없거나 정의되지 않았을 때가 비동기의 핵심임, 이 정의 자체는 어렵지 않음, 문제는 언어 차원에서 이런 추상을 설계할 때 생기는 인지적 부담임

    • 이 주제에 깊지는 않지만 내 생각엔 async 코드란 원래 블로킹되는 작업을 논블로킹으로 바꿔줌으로써 다른 작업들이 동시에 진행될 수 있도록 하는 것이라 생각함, 특히 내 경우 임베디드 루프에서 오랫동안 블로킹되는 코드가 I/O를 망가뜨리고, 눈에 보이거나 귀로 들리는 오류를 유발할 수 있으므로 이러한 관점이 명백하게 와닿음

    • 애초에 async를 정의해야 하는지조차 의문임, 실제로 정의하는 게 힘든 이유가 한 가지 개념에 완전히 맞아떨어지는 게 없기 때문임, async나 event loop를 꼭 정의해야 할 필요가 있는지도 의심스러움, 실제 병렬 처리가 가능한 물리적 칩 영역에는 내가 모르는 수많은 개념이 있을 것임, 나는 “user finger”(손가락 터치 등을 지칭)와 “quickies”(수행 시간이 매우 짧은 작업), job queue, 블로킹/논블로킹 API만 알면 충분함, 내 목적을 달성하려면 논블로킹 API가 좋음, 왜냐면 시간이 오래 걸리는 작업은 하위 시스템에 맡기고 나는 원하는 데이터 저장 같은 “quicky”만 적고, 성공/실패별로 다른 quicky도 정의해놓으면 됨, sync와 async의 구분 자체가 큰 도움은 안 됨, 물론 남들이 말할 때 개념을 이해해야 하긴 함, 본질적으로 async는 논블로킹 API라고 생각함, async 프로그래밍 모델이란 사실상 (수행 시간 기준) 작고 원자적인 블로킹 작업을 “혼돈스럽고 비결정적인” 이벤트에 맞추어 작성하는 형태임, 시스템 내부가 무엇을 하든 브라우저, OS, 디바이스 자체가 멀티 실행 유닛, 좋은 스케줄러를 제공한다고 믿음, async란 나에겐 애매하게 정의된 개념임, 설령 정의 가능해도 그게 꼭 유용할지 의문임, 오히려 이벤트, 내가 작성하는 작업의 블로킹 성질, 함수 클로저, API를 사용할 때 어떤 일이 다른 작업(job)으로 나눠지는지 같은 개념들이 훨씬 실용적임, “callback” 용어 자체도 시작할 땐 매우 헷갈렸음, 코드가 거기서 멈춰있는 줄 알았지만 실제론 해당 부분을 끝까지 실행하고 난 뒤 “callback”이 호출될 때 어떤 코드가 돌고, 어떤 정보를 볼 수 있는지 정밀하게 이해해야만 했음, 솔직히 이건 혼돈이자 천재적 발상임, “async” 자체보다는 근본 모델 즉, 이벤트, 블로킹 작업, 작업 큐, 논블로킹 API가 훨씬 간단함, 그리고 내가 무엇을 하고, 브라우저/OS 등은 또 뭘 하는지 이해하는 것도 상당히 중요함, 예를 들어 cpp는 concurrent 모델을 선언하는 반면에 OS가 진짜 실행을 맡김, JS에서는 논블로킹 API로 “아마도” concurrency가 있다는 걸 브라우저나 Node에 선언하고, 실제로 내부에서 concurrent하게 처리함, 가장 중요한 건 각 작업을 짧게(<50ms) 유지하고, 논블로킹 API로 의도만 알릴 수 있으면 됨, cpp나 rust는 실제 태스크를 concurrent하게 실행해달라고 OS에 알려주기 때문에 물리적으로 한 스레드밖에 없어도 UI 반응성이 유지됨, 결국 async 프로그래머가 해야 할 일은 “멋진 UX 모델”을 만들고 이벤트를 quickies에 잘 맵핑하는 것임

  • 글쓴이가 “실행 양도(yield) 개념”을 동시성 정의에서 꺼내 새로 “비동기(asynchrony)”란 용어에 넣은 듯함, 그리고 이 개념이 없다면 동시성(concurrency) 전체가 망가진다고 주장함, 내 생각에 동시성에는 애초에 실행 양도 기능이 필수라서 그 자체에 내재된 개념임, 중요한 개념이긴 하지만 새로운 용어로 따로 떼어내는 건 혼란만 더하는 것임

    • 나는 1:1 병렬성이 실행 양도가 없는 동시성의 한 형태라고 생각함, 그밖에는 모든 비(非)병렬형 동시성(concurrency)은 실행을 어떤 주기로든 양보(yield)해야 함, 명령어 레벨이라도 마찬가지임, 예를 들어 CUDA에선 같은 워프 내에서 분기된 스레드가 서로 명령을 교차실행하게 되어 한쪽 분기가 다른 쪽을 블로킹할 수 있음

    • 인용한 글에서 오히려 “실행 양도는 동시성의 개념”이라고 명시되어 있음을 강조함

    • 동시성이 반드시 실행 양도를 의미하지는 않음, 동기적 로직은 명확한 동기화가 필요하고, 실행 양도는 동기화 수단일 뿐임, 내가 말하는 비동기 로직은 동기화나 실행 양도 없이 동작하는 동시성을 의미함, 실제 관점에서 동시성이나 비동기 로직은 폰 노이만 머신에선 온전히 존재하지 않음

  • 이 맥락에서 비동기란 요청 준비·제출과 결과 수집을 분리하는 추상화임, 여러 개의 요청을 제출한 후에만 그 결과를 확인하는 것이 가능해짐, 동시적 구현을 허용하지만 필수는 아님, 그래도 이 추상의 목적은 동시성 확보임, 동시성이 없으면 얻고자 하는 이득도 없어짐, 일부 비동기 추상화는 최소한의 동시성이 없으면 구현 자체가 불가함, 예를 들어 콜백 방식은 싱글스레드에서도 흉내낼 수 있지만 비재귀 mutex를 쥐고 있을 때 deadlock이 발생하는 등 한계가 있음, 즉 동시성 없는 비동기 추상화는 반드시 실패하게 되어 있음, 요청자가 mutex를 쥔 채로 요청하고, 언락 전 콜백이 실행되면 언락이 영원히 실행되지 않을 수 있음, 최소한 분리된 쓰레드를 통해 요청자가 언락까지 도달할 수 있도록 해야 함

    • 동의함, 글의 서버-클라이언트 예시는 한 가지에 불과하고, 지금 언급한 case는 전혀 다른 방식의 해결책이 필요한 예시임, 앞으로도 더 많은 유사 케이스를 발견하게 될 거라 생각함, 결국 async를 쓸 때 항상 동시성을 보장해야 함
  • "협력형 멀티태스킹(cooperative multitasking)은 강제형(preemptive)이 아님", "비동기"라는 용어는 보통 "단일 쓰레드, 협력형 멀티태스킹(명시적 yield)과 이벤트 기반", 그리고 외부 연산이 동시적으로 실행되며, 결과를 이벤트로 리포트하는 것을 의미함, 멀티스레드나 동시적 실행 모델에서는 비동기가 별 의미가 없음, 해당 쓰레드가 블록돼도 프로그램은 계속 진행함, yield 포인트가 굳이 명시적일 필요가 없어짐

    • Rust, C#, F#, OCaml(5+) 등은 OS 쓰레드와 async를 모두 지원함, OS 쓰레드는 CPU 바운드 작업, async는 IO 바운드 작업에 적합함, async 또는 Go 스타일의 M:N 스케줄링의 최대 장점은 메모리만 충분하다면 태스크/고루틴을 자유롭게 늘릴 수 있음, OS 쓰레드 방식에서는 컨텍스트 스위치 부담, 쓰레드/메모리 부족 등 문제가 있으므로 IO 바운드만 넘어가도 deadlock 문제에 봉착할 수 있음
  • Zig의 새로운 IO 아이디어는 일반 앱 개발엔 참신하게 보임, 스택리스 코루틴이 필요 없는 사람에겐 최적임, 하지만 라이브러리 작성에는 오류가 많아질 듯함, 라이브러리 저자는 주어진 IO가 단일/멀티쓰레드인지, 이벤트 기반 IO인지 파악이 어려움, 동시성/비동기/병렬 관련 코드는 IO 스택을 완전히 알고 있어도 작성하기 어려운데, IO가 외부에서 주어지는 구조에서는 어려움이 배가됨, IO 인터페이스가 "작은 OS"처럼 방대해지면 테스트 해볼 시나리오도 폭발적으로 늘어남, 인터페이스에서 제공하는 async 프리미티브만으로 실제 엣지케이스를 모두 처리할 수 있을지 확신이 서지 않음, 다양한 IO 구현을 지원하려면 코드가 매우 "수비적"이어야 하고 항상 가장 병렬적인 IO를 상정해야 할 것 같음, 특히 스택리스 코루틴과 이 방식을 섞는 게 쉽지 않을 것으로 예상됨, 불필요한 코루틴 스폰을 줄이려면 코루틴 명시적 폴링이 필요한데 대부분의 개발자가 그런 코드를 직접 작성하지 않을 듯함, 결국 일반 async/await 코드와 비슷한 구조로 귀착될 것 같음, 동적 디스패치 및 Zig의 상향식 디자인 경향까지 감안하면 최종적으로 꽤 고수준 언어가 될 것 같음, 아직 실제 적용사례가 없어 "타협 없는" 접근이라 부르기엔 이름값이 너무 과감함, 몇 년간의 활용 후에야 진정한 평가가 가능함

    • 스택리스 코루틴은 어차피 지원 예정임, WASM 타깃 지원에 필요하니까 반드시 들어갈 것임, 동적 디스패치는 IO 구현이 2개 이상일 때만 사용됨, 하나만 쓰면 다이렉트 콜로 대체됨, 아직 현장에서 검증이 안 되었으니 “타협없는”이란 표현은 시기상조라고 봄, Jai 언어에서 비슷한 모델을 성공적으로 쓰긴 한다고 들었지만(명시적 컨텍스트 전달이 아닌 암묵적 IO 컨텍스트라는 차이 있음), 이것도 현장에서 실제 쓰였다고 보긴 어려움

    • 동기와 비동기 실행을 모두 지원하려면 코드가 항상 가장 병렬적인 IO를 상정해야 한다는 점 공감함, 그러나 하위 레벨 IO 이벤트 핸들러에서 비동기가 잘 구현되어 있다면, 모든 곳에서 동일 원칙만 적용해주면 됨, 최악의 경우에도 코드가 그냥 순차적으로(느리게) 돈다는 것 뿐, 레이스/데드락 문제엔 빠지지 않음

  • 두 개의 라이브러리를 따로 안 쓸 수 있다는 점에서 Zig의 아이디어가 아주 좋다고 생각함, 다만 비동기 코드의 테스트가 항상 걱정임, 오늘 통과한 테스트가 실제 도중에 발생할 모든 시나리오/순서를 복제한 건지 어떻게 확신할 수 있을지 모르겠음, 스레드 프로그램도 동일 문제인데, 멀티스레드 코드는 쓰고 디버깅하는 게 항상 더 힘듦, 나는 웬만하면 쓰레드 사용을 피함, 실제 문제는 ‘개발자에게 비동기/스레드 환경을 정확히 이해시키는 것’임, 최근 Python 시스템에서 JS 반, Python 반을 써본 팀과 일했는데, 대규모 코드 async, threaded로 바꿔놓음, 근데 Global Interpreter Lock(GIL)이 뭔지도 모름, 내 얘기가 잔소리만 같았던 듯, 게다가 그들의 테스트는 실제로 코드를 부숴도 항상 통과함, mangum이 HTTP request 끝날 때 background 및 async 작업을 강제로 finishing시키는데, 정작 이걸 몰랐음, 이런 걸 알린다고 해도 다들 무심해함, 아는 게 중요한 게 아니라, 남들이 그걸 봐주느냐가 더 중요함

    • Zig에서는 Io 테스트 구현체를 도입할 예정임, 이걸로 병렬 실행 모델 하에서 퍼즈 테스트 등 스트레스 테스트도 가능하게 만들 계획임, 하지만 핵심은 대부분의 라이브러리 코드가 io.async나 io.asyncConcurrent를 직접 호출할 일이 없으리라는 점임, 예를 들어 대부분의 데이터베이스 라이브러리는 순수 동기 코드만으로 충분함, 해당 코드를 어플리케이션 개발자가 io.async(writeToDb), io.async(doOtherThing) 식으로 손쉽게 비동기화할 수 있음, 이렇게 하면 async/await가 코드 전체를 스프링클하는 것보다 덜 에러프론하며 훨씬 이해가 쉬움

    • 공감됨, 비동기·멀티스레드 코드에서 모든 인터리빙을 테스트하는 건 악명 높게 어려움, 퍼저나 동시성 테스팅 프레임워크를 써도 실제 운영에서 겪는 교훈 없이는 확신하기 힘듦, 분산 시스템에서는 더 악화됨, 예를 들어 웹훅 인프라 설계할 땐 자체 코드 내 async뿐 아니라 네트워크 재시도, 타임아웃, 부분 실패 등 다양한 외부 문제까지 겹침, 고동시 환경에서 리트라이, deduplication, idempotency 보장 등 자체적인 엔지니어링 이슈가 됨, 그래서 Vartiq.com 같은 전문 서비스를 쓸 필요가 생김(여기서 일하고 있음), 이런 서비스가 운영상 동시성 복잡도를 일정 부분 추상화해 blast radius를 줄여주지만, 내 코드 내 async 테스트 이슈는 여전함, 결론적으로 async, threading, 분산 동시성은 서로 리스크를 증폭시키므로, 커뮤니케이션과 시스템 설계가 어떤 문법/라이브러리보다 더 중요함

  • 저자는 동시성 정의에서 혼동이 있는 것 같다고 봄, Lamport의 논문을 참고할 만함

    • 논문 링크만 남기지 말고 설명 부탁함, 내 생각엔 정의 자체는 괜찮았음, 예를 들면 비동기: 작업들이 순서대로 실행되지 않아도 옳다면 그게 비동기임, 동시성: 병렬이든 태스크 스위칭이든 여러 작업을 동시에 진행 가능한 시스템 성질, 병렬성: 실제 물리적 수준에서 동시에 둘 이상 작업이 돌아가는 것임

    • 이런 이유로 나는 이 용어 사용을 완전히 그만둠, 누구와 이야기하든 이해가 달라서 용어 자체가 소통에 의미가 없어짐

    • 저자도 블로그 글에서 그 용어에 대한 기존 정의가 있었던 걸 알고 있음, 스스로 새 정의를 제안한 거고, 그 정의만 일관적이라면 충분함, 독자가 받아들일지 여부만 차이임

    • Lamport 논문의 절반은 대부분 언어에서 개념적으로 표현할 수 없음, 스레드를 만든다고 해서 전체 및 부분 순서 논의를 할 일은 거의 없음, TLA+로 프로토콜 설계할 때나 이런 논의가 필요함, Zig async API에서 “비동기 실행 환경에서만 동작”하는 함수가 컴파일 오류를 내는 걸 새 이론으로까지 부를 필요는 없음

  • “비동기(asynchrony)”란 용어가 정말 필요한지 가늠하는 좋은 방법은, 한 언어/모델뿐 아니라 다양한 동시성 모델에서도 유용한지 따져보는 것임, 예컨대 Haskell, Erlang, OCaml, Scheme, Rust, Go 등 여러 환경에서 공통적으로 필요한 용어라면 가치가 높음, 일반적으로 협력형 스케줄이 들어오면 시스템 전체가 한 코드의 문제로 락업, 지연 이슈 등으로 더 많은 신경을 써야 하고, 선점형 스케줄이면 이런 문제들이 대거 사라짐, 전체 시스템 락업이 불가능해지므로 문제군이 확 줄어듦

  • “비동기(asynchrony)”는 이 경우 부적절한 단어이고, 이미 잘 정의된 수학적 용어 “교환법칙(commutativity)”가 있음, 어떤 연산은 순서가 중요하지 않은데(덧셈, 곱셈 등), 어떤 연산은 순서가 중요함(뺄셈, 나눗셈 등), 보통 코드의 연산 순서는 줄 번호(위→아래)로 표현되는 데, async 코드에서는 이 순서가 깨짐, 이런 식으로 작성된 asyncConcurrent(...)는 상당히 헷갈릴 수밖에 없음, 블로그 글 내용을 완전히 숙지한 게 아니면 무슨 의미인지 파악이 어려움, Zig(그리고 내가 좋아하는 Rust도)에선 이런 “힙스터”적 접근이 자주 나오는 듯, 절차형(async-기반) 교환법칙/순서 체계를 Rust의 라이프타임같이 구현하거나, 아니면 사람들이 익숙한 걸 그냥 쓰는 게 나음

    • “asyncConcurrent(...)가 혼란스럽다”는 얘기에 동의하지 않음, 블로그 글 핵심을 내재화하면 전혀 혼란스럽지 않음, 그 아이디어를 학습할 가치가 있냐는 별개 문제임, 실제로 이걸 내면화한 사람들이 많이 실습하고, 시간이 지나면 현장에서 이 아이디어가 좋은지 아닌지 알게 될 것임, 그리고 “교환법칙(commutativity)”란 단어를 다른 걸로 대체할 때 오히려 Zig에선 오히려 연산자가 교환법칙을 따르는 게 있으므로 더 혼동됨, f() + g()라면 덧셈이 교환법칙을 따르니까 Zig가 병렬로 실행해도 되냐 같은 혼란이 나오기 쉬움, 실행 순서와 교환법칙은 완전히 다른 얘기니까 구분해야 함

    • 엄밀히 말해 교환법칙(commutativity)은 (이항)연산에 성립하는 특성임, connect/accept 같은 두 async 문이 교환 가능하다고 할 때 “어떤 연산에 대해?”란 질문이 나옴, 현재로선 bind(>>=) 연산자(혹은 .then(...) 등)가 그런 역할에 근접하지만 아직은 직관의 영역임

    • 비동기는 부분 순서도 허용함, 두 연산이 같은 순서로 퇴역해야 하더라도 실제 실행 순서와는 별개임, 예를 들어 뺄셈이 교환법칙은 아니지만 잔액 계산과 차감액 계산을 두 쿼리로 병렬 수행한 뒤, 결과를 적절한 순서로 적용하는 것도 가능함

    • 다른 용어가 이 개념을 포함한다고 해서 “asynchrony”보다 좋은 단어가 된다고 볼 수 없음, “commutativity”란 단어는 읽기에도, 듣기에도, 쓰기에도 지저분함, asynchrony가 훨씬 친숙함

    • 교환법칙이란 주장엔 한계가 있음, A와 B가 각각 C와 교환된다면 ABC=CAB지만, 이게 ACB와도 반드시 같다고 할 순 없음, 비동기에서는 ABC=ACB=CAB 모두가 같아야 함(기존 수학 용어가 있다면 모르겠지만 잘 모르겠음)

  • 네트워크 프로그래머로서 동시성과 병렬성, 비동기 코드를 엄청 많이 작성해봤는데, 이 글은 좀 혼란스럽게 느껴짐, 마치 허점 많은 추상 위에서 답을 찾으려 애쓰는 느낌임, 툴이나 구현 자체가 잘못됐다면 이렇게 쉽게 “망가질” 수 있다는 게 문제임, 사실 멀티쓰레드 코드 디버깅 자체가 꽤 재밌음, 다른 사람들이 멀티쓰레드 괴물을 엄청 두려워하는 걸 보면 오히려 즐거움