User Tools

Site Tools


lecture:core.async:rationale_by_rich_hickey

core.async by Rich Hickey

근거

좋은 프로그램이 되려면 컴포넌트나 서브 시스템 사이의 직접적인 통신을 멈추어야만 할 때가 있다. 이럴 때는 데이타의 생산자와 소비자 사이에 큐를 중개하게 된다. 이러한 아키텍쳐를 통해 어느 정도의 독립성을 갖고 중요한 결정이 가능하게 되는데, 이렇게 해서 시스템이 좀 더 이해, 관리, 감시, 변화가 쉽게 되고, 시스템의 컴퓨팅 자원을 보다 더 잘 이용할 수 있게 된다.

자바의 java.util.concurrent 패키지는 몇 개의 좋은 동시성 블러킹 큐를 제공하는데, 클로져 프로그램에 실용적이고 인기있는 선택이 된다. 하지만 이것을 이용하려면 스레드를 생성해야 한다. 하지만 이것은 시스템의 스레드 한계에 제한이 걸린다. j.u.c 큐의 또 다른 문제점은 일단의 대안들을 기다리면서 블러킹할 방법이 없다는 것이다.

자바스크립트에서는 큐도 스레드도 없다.

스레드 제한이나 스레드가 없다는 문제로 인해 이벤트/콜백 시스템이 대안으로 나타난다. 보다 더 좋은 효율성을 추구하면서. 이벤트는 통신과 제어흐름을 뒤섞어 놓는다. 이벤트/콜백을 더 깔끔하게 만드는 시스템들이 있다. FRP, Rx/Observables 등. 하지만 근본적인 그들의 성질은 변하지 않는데, 그것은 이벤트가 발생하면 한 스레드상에서 임의의 코드가 실행된다는 것인데, “핸들어에서 너무 많은 일을 하지 마라” 혹은 “콜백 지옥”이라는 현상이 나타나게 된다.

콜백 지옥

Escape from Callback Hell Ajax나 node.js 같은 콜백 기반 시스템들에서 작업하다 보면, 콜백 지옥에 쉽게 빠지게 된다. 어플리케이션이 콜백 함수사이에 흩어지게 되는데, 코드는 금방 가독성 및 관리가 어렵게 된다. 이것을 스파게티 코드라고 하는데, goto 시절에도 그랬다.

goto 처럼 콜백 함수는 코드들 사이들 널뀌게 한다. goto 처럼 콜백은 읽기도 유지하기도 이해하기도 어려운 비선형 코드를 만든다. 그래서 콜백은 goto가 그랬던 것처럼, 고차의 제어 흐름 메카니즘을 통해 해결될 수 있다. 그것이 FRP이다!

goto : structured programming = callback : reactive programming

core.async의 목적

  • channel을 통해 통신하면서, 독립적인 스레드를 위한 편이성을 제공한다.
  • 진짜 스레드 혹은 스레드 풀의 공동 사용 지원한다. ClojureScript에서도.
  • CSP나 그 후계들에 이루어진 작업에 기반하여 만든다.

async channel이 효과적인 서버사이드 Clojure 프로그램이 엄청 단순화되기를, 그리고 ClojureScript 프론트 엔드 프로그래밍을 위한 보다 단순하고 튼튼한 기술을 제공하기를 희망한다.

역사

이것은 다 Hoare's Communicating Sequential Processes (CSP) 에 그 뿌리를 둔다. 이것은 나중에 occam, Java CSP, GO Programming Language가 발전되었다.

현대에서는 channel은 1급이 되었다. 그래서 우리는 간접성과 독립성을 획득한다.

channel의 중요 특징은 그것은 블로킹이라는 것이다. 기본적으로 버퍼없는 채널은 랑데뷰처럼 행동하는데, 리더는 라이터를, 라이터는 리더를 기다린다. 버퍼링이 도입될 수 있으나, 무제한 버퍼링은 장려되지 않는다. 블로킹이 있는 제한된 버퍼링은 페이싱과 백 압력을 조율하는 중요한 도구가 될 수 있는데, 이것은 시스템이 그 자체보다 더 많은 일을 하지 않도록 보장한다.

자세한 내용

채널 만들기

chan 함수로 채널을 만든다. 이 채널은 다중 reader와 writer를 위한 것이다. 기본적으로 채널은 unbuffered이다. 버퍼 크기를 정할 수 있고, 혹은 다음과 같은 버퍼 객체를 건넬 수 있다. buffer, dropping buffer, sliding-buffer.

채널에 대한 기본 연산은 값을 넣고 빼는 것이다. 연산은 잠재적으로 블럭되지만, 블로킹의 성질은 연산이 수행된 스레드의 성질에 좌우된다. core.async는 2 종류의 스레드를 지원한다. 보통 스레드와 IOC(inversion of control) 스레드. 보통 스레드는 어떤 방식으로든 만들어 질 수 있지만 IOC 스레드는 go block을 통해 만들어 진다. JS는 스레드가 없기 때문에, go block과 IOC스레드는 ClojureScript에서 지원하는 것이다.

go block과 IOC 스레드

go는 body를 취하고, 채널 연산을 위해 body를 수행하는 매크로이다. go는 body를 상태머신으로 바꾼다. 블로킹 연산에 도달하면(역자주:채널에서의 입력대기), 상태머신은 '주차'되고, 제어를 하는 실제 스레드는 해제될 것이다. 이러한 방식은 C# async와 비슷하다. 블로킹 연산이 완료되면(역자주: 채널에서 입력이 들어오면), 코드는 다시 수행된다(스레드 풀 스레드에서, 혹은 JS VM의 유일한 스레드에서) 이런 식으로 프로그램을 이벤트/콜백 시스템으로 빠지게 했던 IOC가 그런 메카니즘을 통해 캡슐화되고, 당신에게는 간단한 순차 코드만 남는다. 이것은 스레드환상을 주는데, 더 중요하게는 분리가능한 순차 시스템을 ClojureScript에 제공한다.

go 블러에서의 1차 연산은 >! ('put')과 <! ('take')이다. go 블럭 자체는 즉각 채널을 리턴한다. 이 채널에 결과적으로 바디의 마지막 표현식의 값이 들어가고, 채널을 닫힌다.

보통 스레드에서의 채널

보통 스레드에서의 비스한 연산은 >!!('put blocking') <!!('take blocking')이다. 이것은 완료될 때까지 호출시의 스레드를 블로킹한다. future등으로 만들어진 스레드에서 사용할 수도 있지만, 이를 위한 별도의 thread 매크로가 있다. go와 유사하게. thread는 1급 스레드를 만들고 채널을 리턴하기 때문에 채널 작업을 위해서는 future보다 선호되어야 한다.

alt!

채널 연산이 완료되기를 기다릴 수 있다면 좋을 것이다. 이것은 alts!를 통해 수행된다. go 블럭에서. 보통 스레드에서는 alts!! 사용. 만약 하나 이상의 연산이 완료되길 기다린다면, 임의적으로 혹은 입력된 순서대로 선택될 수 있다. alt!와 alt!! 매크로는 표현식의 조건적 평가와 선택을 결합할 수 있다.

타임아웃

타임아웃은 일정 시간이 지나면 자동적으로 닫히는 채널이다. timeout 함수로 만든다. 보동 alt에 timeout을 넣는다.

Go 언어와의 비교

  • 모든 연산은 표현식이지 구문이 아니다.
  • 이것은 라이브러리지, 언어 차원의 문법이 아니다.
  • alts!는 함수이다. 실행시 여러 연산을 지원한다.
  • 우선성은 alt에서 지원된다.

Actor는 별로?

액터는 프로듀서와 컨슈머를 결합한다.

데드락

데드락이 있을 수 있다.

lecture/core.async/rationale_by_rich_hickey.txt · Last modified: 2019/02/04 14:26 (external edit)