front에서 race condition은 왜 발생할까?

gicomong·2021년 6월 5일
2

개발이야기

목록 보기
3/3
post-thumbnail

안녕하세요, gicomong 입니다~
저번주에 저의 포스팅이 거의 없었는데요, 그 이유는 바로 race condition 때문이었답니다...😫

분명 대학교 때는 시험도 잘 치고 개념과 해결방안도 다 이해했었는데,
실제로 서비스에서 발생하니 너무 당황스럽기 마련...

backend에서 race condition을 처리하는 사례는 보았지만,
이번에 frontend에서는 첫경험이었죠..!

그래서 오늘은 공부할 겸 간단히 race condition에 대해 알아봅시다!



1. race condition

1) 경쟁 상태라고요?

  • race condition은 운영체제에서 자주 언급되는 말인데요.
  • 정의는 아래와 같습니다.

둘 이상의 스레드가 하나의 자원을 놓고 서로 사용하려고 경쟁하는 상황
공용 데이터에 대한 접근이 어떤 순서로 발생했는지에 따라, 결과가 달라지는 상황

  • 그러니까, 원래라면 A스레드 다음에 B스레드가 실행되어야 하지만, 왜인지 경쟁상태에 놓이게 되서

  • 예상한 결과와 다른 행동을 하게 되는 것입니다.

  • 그런데 이게 왜...?왜!?! 발생하냐구욧! 😡 (누구 맘대로 경쟁하래!)


2) 왜 발생하는데요?

  • 아니 나는 분명 A스레드 다음에 B스레드를 실행시켰는데, 이게 왜 발생하는거죠?
  • 왜 발생하는지 예시를 한 번 봅시다!
한국초등학교의 점심시간에는 특이한 규칙이 있습니다.
그건 바로 12시부터 5분 간격으로 교실을 나가서 급식실에 갈 수 있다는 것이죠.
이 말은 12시에는 1, 125분에는 2, 10분에는 3반 순서대로 
교실을 나가서 급식실로 향할 수 있다는 것이죠.

그럼 일반적으로 12시에 출발한 1반이 2반보다 먼저 도착하는 게 맞습니다.

하지만 어째서인지, 1달 중 3~4번은 2반이 1반보다 먼저 급식실에 도착합니다..!


  • 그 이유는 바로...! 거리 그러니까 급식실까지는 가는 시간에 있었습니다.
  • 한국초등학교는 특이하게도 1반은 3층, 2반은 1층, 3반은 2층에 위치되어 있습니다.
  • 그렇다보니, 먼저 출발한 게 1반이어도, 1층에 위치한 2반이 가끔은 먼저 도착할 수 도 있다는 것이죠.

  • 이 예시를 코드로 나타내면 아래와 같습니다.
  • apiCall은 front에서 api를 요청하고 걸리는 시간에 대한 함수입니다.
function apiCall(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
  • process1은 1반, process2은 2반입니다.
  • 평소에는 층이 높아도 1반이 먼저 도착하지만, race condition 상황을 보기 위해
  • 1반의 처리시간을 3초라고 가정합니다.(2반은 1초)
async function process1() {
  console.log('1반 출발!');
  await apiCall(3000);     //3층이니까 최대 3초걸림
  console.log('1반 도착!');
}
async function process2() {
  console.log('2반 출발!');
  await apiCall(1000);     //1층이니까 최대 1초걸림
  console.log('2반 도착!');
}
  • 그리고, 1반이 12시, 2반이 12시 5분에 출발하니까,
  • 호출 순서를 process1 -> process2 순서대로 합니다.
process1();
process2();

그럼, 누가 먼저 급식실에 도착할까요?

  • console결과를 보니, 오잉! 2반이 1반보다 먼저 도착했습니다!
  • 이 말은 front에서는 아무리 순서대로 호출해도 api 처리 시간에 따라 순서가 보장되지 않을 수 있다는 말입니다.



3) 발생 지점은 어디?

  • 우리는 front에서 api 요청을 할 때 async await로 비동기 처리를 하곤 합니다.
  • 왜냐하면 이 api 요청이 언제 끝날 줄 알고, 계속 기다리냐는 것이죠 😕 😕
  • 이 api 요청이 끝날 때까지 다른 코드를 실행합니다.
async function getUserData() {
  const users = await this.FETCH_USER_DATA();  //이거 좀 시간 걸린다~, 그 동안 다른 거 처리해~
  ...                          
}
  • 그런데, front에서는 이 api call & async await부분 때문에 경쟁상태가 발생합니다.
  • 왜냐하면 이 비동기라는 녀석은 실행이 끝날때까지 기다리지 않고 다른 코드를 먼저 처리하니까요~
  • 앞서 설명했던 코드 예시를 보면, apiCall함수에 await가 걸려 있는 걸 알 수 있죠
async function process1() {
  console.log('1반 출발!');
  await apiCall(3000);     //3초 걸려, 다른 일 해~
  console.log('1반 도착!');
}
async function process2() {
  console.log('2반 출발!');
  await apiCall(1000);     //1초 걸려, 다른 일 해~
  console.log('2반 도착!');
}



4) 해결은 어떻게?

  • 경쟁상태를 해결하기 위해서는 먼저 어디서 발생했는지 알아야 합니다.
  • 원인 발생 지점을 알지 못하면, 어떤 짓을 해도 해결할 수 없죠.

발생지점을 찾았어요! 그럼 어떻게 처리하나요?

잠깐, 여기서 제시한 방식은 예시이지! 정석이 아닙니다 ⚠⚠

(1) lock 걸기

  • lock건다는 건, 특정 실행이 끝나기 전까지 다른 실행을 다 막는 방법입니다.
  • 예를 들면, 1반이 나가서 급식실에 도착할 때까지 다른 반들은 절대 나갈 수 없게 하는거죠!
let blocked = false;           //lock 변수
 async function process1() {
  if (!blocked) {
    blocked = true;
    await 어떤API처리;
    blocked = false;
  }
}
 async function process2() {
  if (!blocked) {
    blocked = true;
    await 어떤API처리;
    blocked = false;
  }
}

(2) 지연시간 정하기

  • 또는 process1이 처리되는 최대시간을 안다면, prcess2에 setTimeout을 걸 수도 있습니다.
  • 하지만, 이건 좋지 않은 방법이죠(api 처리시간은 사용자의 컴퓨터, 네트워크 환경별로 다르니까요)

(3) lock의 다른 모습, 순서 정하기

  • process1이 실행을 마치고, process2가 실행되어야 하죠?
  • 그럼 turns라는 변수를 두어, process1이 다 끝났을 때 turns을 2로 변경합니다.
  • 그리고 process2에서는 turns === 2라면 실행하게 하는거죠
let turns = 1;
 async function process1() {
  if (turns === 1) {
    await 어떤API처리;
    turns = 2;
  }
}
 async function process2() {
  if (turns === 2) {
    await 어떤API처리;
  }
}
process1()
process2()

  • 3가지 정도를 소개했지만, 상황에 따라 처리방식은 다릅니다.
  • 대부분 이 방법이 아닌 다른 방식을 쓰거나 리팩토링하는 게 나을 수도 있기 때문이죠.
  • 결국 최고의 방법은 예방이지만... 만약 발생했다면 원인지점을 파악하고 해결하는 게 제일 중요합니다!!





출처
정말 유용한 정보가 많은걸? 가서 보는것도 좋을지도 몰라🤔

profile
⚠ 이 블로거는 퇴근 후 극심한 피로감 + 강렬한 휴식 욕구로 인해 일주일 이상 포스팅이 없을 수 있습니다. 하지만 항상 좋은 내용을 담고자 합니다🙇🏼

관심 있을 만한 포스트

0개의 댓글