일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 알고리즘
- GPU
- 항해 플러스
- 성장일지
- typescript
- 리뷰
- 개발공부
- 개발자
- 항해 플러스 프론트엔드
- frontend
- javascript
- 성능최적화
- rust
- 테스트 코드
- React
- 보안
- React Query
- 항해
- webGPU
- 개발 공부
- wil
- 분기 회고
- 회고
- FE
- 항해플러스
- 프론트엔드
- 백준
- 항해99
- naver
- 자바스크립트
- Today
- Total
느릿늘있
[SW_개념] 주니어 개발자가 이해한 동기/비동기 본문
한 줄 정리
싱크가 맞으면 동기(synchronous)고 싱크가 안맞으면 비동기(asynchronous)다.
동기/비동기 구분
동기/비동기 개념은 Javascript 개발자라면 무조건 들어봤을 개념이다. 많은 블로그에서 다루고 있는 주제이고 관련하여 많은 글들을 읽어 보고 나의 주관을 정리해 보았다. 반론은 언제나 환영이다.
사전적으로 보면 동기와 동시를 같은 개념으로 오해하기 쉽다. 이 두 단어의 차이를 설명하기 위해 비유를 하나 들어보겠다. 영상과 자막이라는 두 기능을 구현한다고 생각해보자. 이 때, 단순하게 생각해서 영상과 자막의 싱크가 잘 맞으면 동기고 싱크가 안 맞으면 비동기다. 이 비유에서 말하고자 하는 포인트는 영상과 자막이라는 두 기능이 동시에 동작하고 있느냐는 동기/비동기를 구분짓는 개념이 아니라는 점이다. 즉, 여기서의 동기는 동시가 아니다. 핵심은 영상이랑 자막이 싱크가 맞느냐이다.
동기/비동기 개념과 사례
동기/비동기를 구분짓는 핵심 개념 : 순서와 반환값
프로그래밍에서 동기/비동기의 구분은 "순서 보장"과 "반환값에 대한 관심"이 핵심이다. 동기 프로그래밍의 대표적인 예로 1000원이 들어있는 통장에 A와 B가 동시에 200원을 입금하는 케이스를 생각해보자. 이 때, A의 입금처리가 끝날 때까지 B의 입금이 처리되면 안된다. 만약에 A의 1000+200 계산이 끝나기 전에 B가 1000+200을 한다면 최종 결과는 1400이 아니라 1200이 된다. 즉, 입금이라는 기능은 순서가 보장되어야하고 반환값을 토대로 다음 작업이 진행되어야 하며 이를 통해 데이터의 무결성이 보장되어야 한다. 이를 동기적 기능이라고 한다.
반대로 비동기는 순서를 보장하지 않아도 되고 반환값과 상관 없이 다음 작업이 가능할 때 사용한다. 비동기적 설계는 데이터의 무결성을 보장하지 않기 때문에 데이터가 뒤죽박죽 난리가 날 수 있고 따라서 데이터를 배타적으로 설계 해야 한다. 모던 웹에서 화면 구성을 위한 데이터 처리를 AJAX(Asynchronous Javascript And XML)로 하는 이유가 이와 같다. CSR과 SSR에 대한 비교 및 설명은 논외로 하고 AJAX를 쓰면 메인 쓰레드(프레임 생성)가 트리거가 되어 개별 컴포넌트들을 생성하고 개별 컴포넌트는 누가 먼저 나오든지 상관없고 그냥 되는대로 나오면 된다. 또, 메인 쓰레드와 다른 컴포넌트가 현재 컴포넌트의 반환값에 대해 관심을 갖지 않는다. 그저 각자 할 일을 할 뿐이다. 비동기적 구현으로 각 컴포넌트가 데이터를 배타적으로 사용하기 때문에 SPA가 가능해졌다. 데이터가 변하면 그 데이터와 관련있는 그 컴포넌트만 변하면 된다.
혼돈의 Sync/Async - Block/Nonblock
위에서 동기(싱크)의 개념을 동시와 헷갈리면 안된다고 이야기했다. 동시의 개념은 오히려 Block/Nonblock을 설명하는 개념에 가깝다. A, B 태스크를 실행한다고 할 때, A 태스크 실행 중에 B 태스크를 실행하고 A가 B의 종료를 기다리면 Block 기다리지 않고 작업을 이어가면 Nonblock이다. 즉, A와 B가 동시에 작업을 하면 Nonblock이다.
이제 다른 블로그에서 설명하는 동기/비동기/블락/논블락 4분면 이미지와 많은 비유들을 보고 현기증이 온 사람들을 위해 내 주관을 담아 최대한 간단하게 설명해보겠다.
Sync - Block / Async - Nonblock 이 국.룰.(Gook Rule)이다.
동기 상태에서는 Block을 하는 게 국룰이다. A, B 태스크 비유에서 동기적으로 B가 작업을 하고 있다면 A 뿐만 아니라 다른 어떤 태스크도 그 일(데이터)에 영향을 줘서는 안된다. A는 동기적으로 구현되었다면 B의 반환값이 다음 로직을 수행하는데 필요하고 작업의 순서가 중요하기 때문에 무조건 기다리는 게 맞다. 반대로 비동기는 Nonblock이 국룰이다. 순서와 반환값이 중요하지 않기 때문에 각자 자기 할 일만 잘 하면 된다. 그렇기 때문에 작업을 중단할 필요가 없다.
Sync-Nonblock은 위험한 코드고 Async-Block은 비효율적인 코드다.
이제 논란의 Sync-Nonblock과 Async-Block에 대해 이야기해보자. Sync-Nonblock이 가능하려면 개발자가 직접 Nonblock인 상태에서 Synchronous한 작업이 서로 영향을 주지 않는 것을 보장해야 한다. Nonblock이기 때문에 B의 작업 동안 A가 동시에 어떤 작업이든 하고 있어야 하는데 이 작업은 B의 작업 결과와는 무관해야 한다. B의 작업이 끝났을 때, 그 반환값을 가지고 B의 작업 결과와 관련있는 작업을 이어서 하도록 설계 해야 한다. 물론 이렇게 코딩을 하는 것이 쉽지는 않겠지만 작업이 멈추지 않기 때문에 성능적으로는 더 좋을 것이다. 하지만 현실 공사판에서도 빨리 빨리보다 안전 제일이 우선인 시대에 이런 코드를 보고 좋은 코드라고 할 수 없을 것이다. Async-Block은 B의 반환값이 이후 A의 작업에 상관이 없는데 A가 멈춰서 B의 종료를 기다리는 상황이다. 이는 A의 작업 능률을 무의미하게 낭비함으로 비효율적이다.
async/await은 Sync다
FE 개발자라면 Callback Hell -> Promise -> Async/Await에 대해서는 알고 있을 것이다. 하지만 나같은 많은 초보 개발자들이 async/await을 비동기인지 동기인지 헷갈려 한다. 그 이유는 이름에 비동기(async)가 들어있는데 async/await은 동기적으로 동작하기 때문이다. 위에서 이해를 돕기 위해 비동기의 대표적인 예로 AJAX를 설명했다. 하지만 자바스크립트는 애초에 싱글 쓰레드 언어이기 때문에 여러 작업을 동시에 진행하기 위해서 태스크 큐와 이벤트 루프를 활용한 비동기 방식으로 동작한다. 이 때 순서와 반환값이 중요한 동기적 구현이 필요하다면 어떻게 할 것인가에 대한 해결책으로써 콜백함수부터 Async/Await까지 오게 된 것이다.
"즉, Async/Blocking은 비동기지만 Async/Await은 동기다."
극단적으로 표현하기는 했지만 핵심은 Async/Await가 자바스크립트의 특성 상 비동기적으로 동작하는 것은 맞지만 개념적으로는 동기 프로그래밍을 위한 구현 방식이라는 것이다.
async/await은 Blocking이다
마지막으로 풀고 넘어가야할 부분은 Async/Await이 Blocking인가이다. Async/Await도 결국 Promise를 다루는 한가지 방식이기 때문에 Promise를 쓰면 Blocking인가에 대해서 생각해보면 된다. Promise는 반환하는 값의 상태(pending, fulfilled, rejected) 변화에 따라 서로 다른 동작을 설계할 수 있도록 해주는 문법이다. 여기서 Promise의 동작을 내부(코드 레벨)와 외부(JS 엔진, 프로그램 레벨)로 구분할 필요가 있다. Promise 내부적으로 연결된 동작들인 then, catch의 구현은 Promise의 상태 변화 따라 실행되지 않다가 실행됨으로 Blocking으로 볼 수 있다. Async/Await 구문 내의 코드는 문법적으로 then 안에 구현된 것으로 간주함으로 마찬가지로 Blocking으로 볼 수 있다. 이는 실제로 쓰레드가 멈추는 Blocking의 개념은 아니고 Promise의 상태 변경을 기다리는 개념이다. 다시 말해 Promise 외부에 존재하는 JS 엔진의 싱글 쓰레드 - 이벤트 루프 - 태스크 큐의 반복 동작은 Nonblocking 상태로 계속 동작하고 Promise의 상태 변경(fulfilled) 시 then 구문 안에 구현된 코드들이 실행되어 태스크 큐의 맨 마지막에 추가됨으로 내부로 표현한 Javascript의 코드 레벨에서는 async/await을 Blocking의 개념으로 볼 수 있는 것이다.
마치며...
동기/비동기를 공부하면서 스스로 헷갈리고 어렵게 느낀 부분들을 공부하고 정리해 보았다. 모호하고 논란이 많은 영역이라 이 글이 반드시 정답이라고 말할 수는 없지만 주니어 개발자 수준에서 최대한 이해가 안되는 부분들을 풀어보려고 노력했다. 이 글을 여기까지 읽어준 사람이 있을까 싶지만 Javascript에서 동기/비동기에 관한 하나의 관점을 제시한 글로써 누군가에게 도움이 된다면 정말 좋을 것 같다.
'개발공부' 카테고리의 다른 글
[WebGPU]에 대해서 Araboza...(2) (0) | 2023.06.20 |
---|---|
[WebGPU]에 대해서 Araboza...(1) (0) | 2023.06.13 |
[ReactNative] Expo 쓰는 이유 (0) | 2023.05.27 |
[TIL] Naver FE News 2022-02 (0) | 2023.05.20 |
[TIL] Naver FE News 2022-01 (0) | 2023.05.05 |