Skip to content
Underbleu
GithubLinkedin

3-5. 병렬적으로 동시성 다루기 - findValC

Functional programming1 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 = 0
5 for(const a of iter) {
6 ++i
7 go(
8 a, f,
9 b => b === undefined ? undefined : resolve(b),
10 _ => (i == ++j) && resolve()
11 // i는 이미 6
12 // 비동기함수 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 심어주기

  1. collIter(coll)의 동작을 위임해주는 stepIter를 만들어 감싸준다
  2. limit까지 findVal해본다 -> limit에 도달하면 {done: true}를 반환하여 이터레이터 일시정지
  3. remain플래그 - collIter(coll)를 다 순회하지 못했다면 한 번 더 findVal한다
1var stepIter = (iter, limit) => {
2 let i = 0
3 return {
4 next: () => {
5 if(i++ == limit) {
6 i = 0
7 return { done: true } // 2.
8 }
9 const cur = iter.next()
10 this.remain = !cur.done // 3.
11 return cur
12 },
13 [Symbol.iterator] () { return this },
14 remain: true
15 }
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 = 0
22 for (const a of iter) {
23 ++i
24 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