Back to Posts
2019년 11월 11일
코드스피츠85 2회-(2) 동시성 모델을 직접 구현하며 이해하기.
코드스피츠 85에서는 none blocking에 대한 이야기와 자바스크립트를 짜는 근본적인 방법에 대한 고찰을
이야기해본다.
🌕🌑🌑
TL;DR
setTimer에서부터 promise까지 동시성 모델을 기반으로 구현하며, 루프 제어권의 통제에 대하여 알아본다.
1. setTimer를 구현해보기
entity
- Item
- 실행될 시간과 실행할 시간을 갖고 있는 객체
- queue
- callback queue 역할을 하는 큐
- 객체 리스트 형태인 Set으로 생성
const Item = class {
time: number; // 몇초 후에 실행할지
block: Function; // 몇초 후에 실행할 함수
constructor(block, time) {
this.block = block;
this.time = time + performance.now();
}
};
const queue = new Set();cf__1. performace, Set, value
performance.now()- 브라우저가 실행되 이후에 지난 시간
- date.now()보다 좋은 점은 나노초까지 볼 수 있다.
Set- 배열에 담을 수 있는건 값만 담을 수 있다.
- 같은 객체가 중복으로 들어가지 않는다.
- 객체를 담는 리스트
value- 불변
- 자체의 값으로 판단한다.
- 값으로 식별된다.
Core Action
- callback 큐를 지속적으로 체크한다.
- 큐에 호출시간보다 작으면 실행하지 않는다.
- 큐에 호출시간보다 크면
- 실행하고
- 삭제한다.
@params time 현재시간
const checkQueue = (time: number) => {
queue.forEach((item: {time: number, block: Function}) => {
if(item.time > time) return; // 현재시간이 호출시간보다 작다면 실행하지 않음.
else { // 현재시간이 호출시간보다 작다면 실행.
queue.delete(item); // 실행할 예정이기때문에 삭제
item.block(); // 실행
}
});
requestAnimationFrame(checkQueue);
}
requestAnimationFrame(checkQueue);cf__2
requestAnimationFrame
- 브라우저에게 수행하기를 원하는 애니메이션을 알리고, 다음 리페인트가 진행되기 전에 해당 애니메이션을 업데이트하는 함수를 호출하게 한다.
- 리페인트 이전에 실행할 콜백을 인자로 받는다.
- 엔진이 렌더링이 끝나면 직접 발생시키는 함수
트리거
const timeout = (block: Funtion, time: number) => queue.add(new Item(block, time));확인해보자.
timeout(_ => console.log('hello'), 1000);정리
방금 구현한 setTimer를 동시성 모델로 구현해보면?
좀더 큰 그림에서 보면?
- 동시성을 만들어내는 이벤트 루프 안에 작은 이벤트 루프를 만들어낸 것.
2. Non Blocking For 구현해보기
const working = _ => {};
for(let i=0; i < 100000; i++) working();
@params max 최대 루프수
@params load 한번에 로드할 카운트
@params block 실행할 함수
const nbFor = (max:number, load: number, block: Function) => {
let i = 0;
const f = (time:number) => {
let curr = load;
// 한번에 로드할 카운트를 상태로 받기 위해 변수에 할당
while(curr-- && i < max) {
// 1. 1회돌때마다 현재 상태를 하나씩 뺀다.
block();
// 2. 실행
i++;
}
console.log(i);
if(i < max-1) requestAnimationFrame(f)
}
requestAnimationFrame(f); // --- (1)
}requestAnimationFrame으로 내부함수f가 실행- load기준으로 반복 카운트가 chunk된다.
- while문이 한 셋트가(load 카운트가 종료) 끝나면,
requestAnimationFrame으로f함수를 다시 실행시킨다.
- 하나의 프레임이 끝나면 제어권을 다시 엔진에게 돌려준다.
- 다시
- max까지 루프가 끝나면 더이상 f를 실행하지 않음.
- 하나의 프레임이 끝나면 제어권을 다시 엔진에게 돌려준다.
- 클로저 패턴이 존재, i가 상태를 물고 있음
3. Generator 구현해보기
const infinity: Iterator = (function* () {
let i = 0;
while (true) yield i++;
})();
console.log(infinity.next());// lib.es2015.iterable.d.ts
interface IteratorResult<T> {
done: boolean;
value: T;
}
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}- 제너레이터는 유사 iterable이다.
- 제너레이터 자체는 iterable이 아니다.
- iterable은 iterator라는 함수(
[Symbol.iterator]())를 호출하면 iterator 객체를 주는데, generator를 호출하면 iterator가 반환된다. - generator는
for...of를 사용하지 못한다. for…of는 iterable이 와야하기 때문에
yield가 일어날 때마다next로 다음 턴을 줄 수 있다.
function*
- 내부적으로 suspend 구간을 생성한다.
- 동기명령은 절대로 멈출 수 없다.
- generator는 멈출 수 있다.
- generator는 중간에 끊을 수 있다.
- 동기명령은 절대로 멈출 수 없다.
- yield를 호출하면 suspend가 일어난다.
- 멈춘다.
- 다음번 next 호출시 내부적으로 다시 재개되어서 루프돈다.
- next 호출할때마다
suspend가 일어난 곳에서resume이 일어난다. - 멈추는 것:
suspend - 다시 재개:
resume
- next 호출할때마다
const gene = function* (max: number, load: number, block: Function) {
let i = 0,
curr = load;
while (i < max) {
if (curr--) {
block();
i++;
} else {
curr = load; // curr을 초기화하고
console.log(i);
yield; // 제어권을 밖에 둔다.
}
}
};- suspend로 멈춰서 제어권을 외부에 위임할 수 있다.
gene.next()
const nbFor = (max, load, block) => {
const iterator: Iterator = gene(max, load, block);
const f = _ => iterator.next().done || timeout(f);
timeout(f); // timeout을 쓰는 위치를 밖으로 옮겼다.
};제어 시스템의 반제어권을 외부에 줌으로써 내부에서 제어와 관련된 로직을 분리시킬수 있게 된다는게 제너레이터의 장점
4. Promise 구현해보기
- 비동기 반제어
- 트리거를 걸었음
- 서버가 3초만에 데이터를 줬음
- 3초 안에는 제어할 권한이 없음
- 3초 이후에는 제어할 권한이 있음
- Promise에 바로
then을 사용하는 것은 반제어권이 이점을 활용하지 않고 콜백처럼 쓰는 형태 - 내가 원할때 then을 호출할 수 있다.
const gene2 = function* (max, load, block) {
let i = 0;
while (i < max) {
yield new Promise(res => {
let curr = load;
while (curr-- && i < max) {
block();
i++;
}
console.log(i);
timeout(res, 0);
});
}
};- yield를 보낼때 Promise로 감싸서 보내고 있다.
- 제어권을 완전 양도했었지만 위 코드는 capsulizing해서 Promise안의 작업이 끝나면 then을 호출할 수 있게끔 반제어권을 주었음.
const nbFor = (max, load, block) => {
const iterator: Iterator<Promise> = gene2(max, load, block);
const next = ({ value, done }) => dome || value.then(v => next(iterator.next()));
next(iterator.next());
};- nbFor에서는 트리거 역할만하는 것이고,
- 제어는 Promise가 한다.
- co함수, redux-saga, …
참고자료
Related
2019년 11월 10일
2019년 10월 27일