3-5. 병렬적으로 동시성 다루기 - findValC
— Functional programming — 1 min read
프로그래머스에서 진행한 유인동님의 ES6로 알아보는 동시성 & 함수형 프로그래밍 강의를 들으며 정리한 내용입니다.
Q: NodeJs가 Single Thread닌깐 Multi Thread를 쓰는 서버보다 느리지 않은가요 ?
예전엔 개개인이 0.1초라도 빠른게 중요했다. 하지만 요즘시대는 개개인이 빠른것보단, 조금 느리더라도 모든 유저가 동시성을 가질 수 있는게 중요하다 (모두가 평균적으로 적당히 빠르게)
ex) 페이스북에서 동시에 피드를 봐야 잼, 채팅서버에서 동시에 대화가 보여져야 대화가능
- 코어를 하나 쓴다는 가정하에, 동시에 접속한 유저가 적을경우, JAVA나 컴파일언어로 만든 서버의 속도를 따라갈 수 없지만
- NodeJs는 2만명의 유저의 동시성을 지켜줄 수 있다는점에서 우수하다
findValC
: 비동기처리를 병렬적으로 처리하여 값을 찾기
- 기존의 findVal처럼 프로미스 값을 풀어서
recur(acc)
전달하기위한 시간소요를 하지 않도록, Promise내에 다 출발시켜 놓고 처음들어온 값을 리턴 - map계열의 함수보다, find계열의 함수에
limit
이 유용하다
=> map은 모든 요소를 순회하며 mapping해야하지만, find계열 함수는 값을 찾으면 바로 함수를 종료할 수 있도록limit
필수로 가져야한다
1. Promise.resolve에 처음 찾은 값을 Keep
Promise는 resolve에 처음으로 채워지는 값이 최종값이 된다. 그 이후의 resolve는 무시된다. for문을 돌며 처음으로 찾은 값을 프로미스에 Keep해두고, 루프가 종료되 면 리턴해주자
1const findVal = (f, coll) => {2 const iter = collIter(coll)3 return function recur(res) { // res 선언4 for (const a of iter) {5 res = f(a)6 if (res !== undefined) 7 return go(res, res => res !== undefined ? res : recur())8 }9 } ()10}11
12const findValC = curry((f, coll, limit = Infinity) => {13 const iter = collIter(coll)14 return new Promise(resolve => {15 for (const a of iter) {16 go(a, f, b => b === undefined ? undefined : resolve(b))17 }18 })19})20
21const nums = [1, 2, 3 ,4 ,5, 6]22
23go([1,2,3,4,5,6],24 findVal(a => time(a > 3 ? a : undefined)),25 log) // 비동기의 순차적 실행 -> 3초 소요.26 27go([1,2,3,4,5,6],28 findValC(a => time(a > 3 ? a : undefined)),29 a => log(`결과 ${a}`)) // 4. 비동기의 병렬적 실행 -> 1초 소요
2. 조건에 해당되는 값이 없는 경우
- coll에 조건에 해당값이 없으면 응답이 안와서 프로미스는 pending 상태가 된다 -> 응답없음
- 플래그 i와 j를 심어주자
- coll을 다 순회했는지 체크하여 해당이 없으면 undefined를 반환
- coll로 배열이 온다면 i대신 arr.length를 쓸 수도 있겠지만, coll에는 length속성이 없는 여러가지의 컬렉션들이 오기 때문에 i를 플래그로 삼는게 좋다
1const findValC = curry((f, coll) => {2 const iter = collIter(coll)3 return new Promise(resolve => {4 let i = 0, j = 05 for(const a of iter) {6 ++i7 go(8 a, f,9 b => b === undefined ? undefined : resolve(b),10 _ => (i == ++j) && resolve() 11 // i는 이미 612 // 비동기함수 f처리후 증가하는 j가 6이 될 때까지 resolve(b)가 안되어 있다면 resolve()을 리턴해라13 )14 }15 })16})17
18go([1,2,3,4,5,6],19 findValC(a => time(a > 3 ? a : undefined)),20 a => log(`결과 ${a}`)) // 결과 4 --> 1초 소요
go의 마지막 함수 _ => i == ++j && resolve()
안에서 i는 for루프내에서 동기적으로 처리되기 때문에 이미 6이 되어있는 상태. j 는 time함수에서 setTimeout으로 걸려있는 Promise를 받기위해 1초를 기다려야하기 때문에, 1초뒤 순차적으로 1 2 3 4 5 6으로 증가한다. 만약 이전함수에서 resolve(b)
가 되어있다면 b가 반환값이 되고, j가 6이 될 때까지 resolve값이 차있지 않다면 빈 값의 프로미스가 반환된다
3. stepIter
: iterator의 done프로퍼티 활용하여 limit 심어주기
collIter(coll)
의 동작을 위임해주는 stepIter를 만들어 감싸준다- limit까지 findVal해본다 -> limit에 도달하면
{done: true}
를 반환하여 이터레이터 일시정지 remain
플래그 -collIter(coll)
를 다 순회하지 못했다면 한 번 더 findVal한다
1var stepIter = (iter, limit) => {2 let i = 03 return {4 next: () => {5 if(i++ == limit) {6 i = 07 return { done: true } // 2.8 }9 const cur = iter.next()10 this.remain = !cur.done // 3.11 return cur12 },13 [Symbol.iterator] () { return this },14 remain: true15 }16}17
18var findValC = curry((f, coll, limit) => {19 const iter = stepIter(collIter(coll), limit) // 1.20 return new Promise(function recur (resolve) {21 let i = 0, j = 022 for (const a of iter) {23 ++i24 go(a, f, 25 b => b === undefined ? undefined : resolve(b),26 _ => (i == ++j) ? iter.remain ? recur(resolve) : resolve() : undefined)27 }28 })29})
4. 잘 동작하는지 확인하기
1console.time()2go([1,2,3,4,5,6,7,8,9,10],3 nums => findValC(a => time(a > 9 ? a : undefined), nums, 2),4 a => log(`결과 ${a}`),5 _ => console.timeEnd()) // 결과 10. 5번의 루프 -> 5초 소요
limit 10 -> 1초소요
limit 5 -> 2초소요
limit 3 -> 4초소요
limit 4 -> 3초소요
limit 2 -> 5초소요
yeah ! :D