오늘의 나보다 성장한 내일의 나를 위해…













면접 질문으로 단골로 나오는 것들이 몇가지 있다. 지금까지 면접을 보면서 계속 나왔던 질문이나 제대로 답해본 적이 없다. 꼭 관련되서 게시글을 써야겠다는 마음이 있었는데 앞에 질문들에 대해 포스팅하느라 이제야 글을 써본다.


처음 동기, 비동기, 블로킹, 논블로킹을 듣게되면 헷갈린다.


마치 동기와 블로킹이 같은 말 같고 비동기와 논블로킹이 같은 말 같아 보이기 때문이다. 하지만 둘은 다르다!


먼저 Blocking과 Non-Blocking의 차이를 살펴보자.


그러기 앞서 여기서 기준점이 있어야 된다. Blocking과 Non-Blocking를 구별해주는 기준점을 여기서는 제어권이라고 하겠다. 제어권이란 함수를 실행할 권리라고 생각을 하자. 블로킹에 대해서 먼저 살펴보자.


:pushpin: 블로킹


  • 호출된 함수가 제어권을 넘겨주지 않아 호출한 함수측에서는 다른 작업을 수행할 수 없고 제어권이 돌아오기만을 기다리는 것을 말한다.


상황으로 이해하자. [신발가게 안에서]



위 그림을 보면 제어권을 가지고 있는 A함수가 진행하다가 B함수를 호출하게 된다. 그리고 제어권을 B 함수에 넘긴다. 이때 A 함수는 제어권이 없기 때문에 함수 실행이 멈추게 된다. 그리고 제어권을 넘겨받은 B 함수는 함수를 완료시키고 나서 제어권을 다시 자신을 호출한 함수 A에게 제어권을 넘겨준다.



이제 Non-Blocking에 대해 살펴보자.


:pushpin: 논블로킹


  • 어떤 함수를 호출한 함수가 제어권을 넘겨주지 않고 그대로 자신이 가지고 있는 것을 말한다.


상황으로 이해하자. [신발가게 매장 안에서]



A 함수가 B함수를 호출하면 B 함수는 실행되지만 제어권은 A 함수가 그대로 가지고 있는다. A 함수는 계속 제어권을 가지고 있기 때문에 B 함수를 호출한 이후에도 자신의 코드를 계속 실행한다.


동기와 비동기를 살펴보자. 살펴보기 전에 여기서도 기준점을 가지고 있어야 한다. 여기서 기준은 작업 완료누가 신경 쓰는가 이다.


:pushpin: 동기


  • ‘호출한 함수’가 스스로 신경을 쓴다. 즉, 호출된 함수의 수행 결과 및 종료를 호출한 함수가 신경 쓰면 동기이다.



Thread1, Thread2가 존재할때 Thread1에서 처리하려고 했던 일을 Thread2에게 보낸 경우, Thread2가 해당 작업을 수행하는 동안 Thread1은 Thread2가 끝날 때까지 대기상태다.


상황으로 이해하자. [신발가게 매장 안에서]


:pushpin: 비동기


  • 호출되는 함수에게 callback을 전달해서 호출되는 함수의 작업이 완료되면 호출되는 함수가 전달받은 callback을 실행하고 호출하는 함수는 작업 완료 여부를 신경쓰지 않으면 비동기이다.



Thread1, Thread2가 존재할때 Thread1에서 처리하려고 했던 일을 Thread2에게 보낸 경우, Thread2가 해당 작업을 수행하는 동안 Thread1은 대기 없이 나머지 Task2, Task3를 실행한다.


상황으로 이해하자. [시발가게 매장 안에서]



:pushpin: 조합


:pushpin: 동기 / 블로킹

  • 동기: 호출한 함수가 작업 완료 여부를 확인한다.
  • 블로킹: 호출된 함수가 제어권을 가진다.



상황으로 이해하자. [신발가게 매장 안에서]


Example

import java.util.Scanner;

/**
 * 동기 + 블로킹
 */
public class BlockingAndSync {
    public static void main(String[] args) {
        System.out.println("메시지를 입력하세요 : ");

        Scanner scanner = new Scanner(System.in);

        /* 제어권이 넘어갔다. 입력이 되기 전까지 그 다음 로직이 실행되지 않는다. */
        String message = scanner.nextLine();

        /* 입력이 된 후, 결과를 받아서 그때 처리된다. */
        System.out.println(message);
    }
}


:pushpin: 동기 / 논블로킹

  • 동기: 호출한 함수가 작업 완료 여부를 확인한다.
  • 논블로킹: 호출한 함수가 제어권을 가진다.



Thread1은 task1의 완료 여부에 상관 없이 다른 작업을 진행할 수 있다. 하지만 task1의 완료 여부를 지속적으로 확인한다.


상황으로 이해하자. [신발가게 매장 안에서]


Example



흔한 게임 업데이트 진행중인 화면이다. 오른쪽 하단에 게임 업데이트가 계속 진행되고 있다. 남은 시간 및 현재까지 업데이트된 정도를 보여주고 있다. 완료여부를 계속해서 확인하고 있는 상태로 보인다.


:pushpin: 비동기 / 블로킹

  • 비동기 : Callback 함수가 작업 완료 여부를 확인한다.

  • 블로킹 : 호출된 함수 (task1)이 제어권을 가진다.



Thread1이 task1의 작업 완료 여부를 신경쓰지 않으나, 작업이 완료될때 까지 아무것도 하지 못하는 대기 상태다. 해당 경우는 비동기인데 굳이 블로킹인 경우인데, 이는 보통 비동기 (Asynchronous) + 논블로킹 (Non-Blocking) 로 작업을 시도했을때 잘못된 경우 발생한다.


상황으로 이해하자. [신발가게 매장 안에서]


비동기 / 논블로킹을


  • 비동기 : Callback 함수가 작업 완료 여부를 확인한다.
  • 논블로킹 : 호출한 함수가 제어권을 가진다.



Thread1이 task1의 완료 여부를 신경쓰지 않고 다른 자신의 작업을 진행한다. 성능과 자원 효율면에서 가장 우수하다.


상황으로 이해하자. [신발가게 매장 안에서]


Example

function getData() {
  let data;

  $.ajax({
    type: "post",
    url: "https://recordboy.github.io/",
    data: {
      // 전송 데이터
    },
    success: function (result) {
      // 통신 성공시 결과값 할당
      data = result;
    },
  });

  return data;
}

console.log(getData()); // undefined

:pushpin: 장단점


  • 블로킹
    • 장점
      • 작업이 순차적으로 이루어지기에 작업 흐름을 쉽게 이해할 수 있음
    • 단점
      • 블로킹이 이루어지는 동안엔 하드웨어 리소스를 효율적으로 이용하지 못함


  • 논 블로킹
    • 장점
      • 리소스가 낭비되는 시간이 없기에 하드웨어 리소스를 균일하고 효율적으로 이용가능
    • 단점
      • 업무 흐름이 매우 복잡해지는 단점 존재


  • 동기
    • 장점
      • 요청한 작업의 완료여부와 결과를 바로 알 수 있다는 장점이 있음
    • 단점
      • 요청한 작업의 완료여부와 작업 결과를 반환받는 데 필요한 시간이 긴 경우 문제가 발생


  • 비동기
    • 장점
      • 이미 요청한 작업의 결과를 기다리고 있을 필요 없이 바로 다음 작업을 요청할 수 있기에 작업 효율이 높아짐(자원의 효율적 사용)
    • 단점
      • 특정 작업의 경우 선행 작업의 결과값을 이어받아 순차적으로 작업을 진행해야 할 수 있다. 이런 작업을 비동기 작업으로 구현할 경우 프로그램이 복잡해짐


:pushpin: 비동기는 언제 써야 할까?


웹서버로 예를 들어보겠다.

웹서버에서 받는 요청을 처리해주는 쓰레드가 100개가 있다고 해보자.


만약 웹서버로 100개의 requests가 들어오는 경우에는 동기를 쓰든 비동기를 쓰든 속도가 비슷하다.


request가 100개가 넘어가는 순간 비동기를 쓴다고 해보자. 200개의 requests가 들어오면 그냥 무작정 기다리는 게 아니라 100개를(request 200개 중에 100개) 실행하다가 잠깐 멈추고 나머지 100개(나머지 request 200개 중에 100개)를 실행하고 왔다갔다 한다.


결과적으로 하나하나의 job은 빨리 안 끝날 수 있지만 모든 사람에게 너무 느리게 끝나지 않게, 그러니까 웹에 접속할 때 이미지를 보여주는 것과 같은 경우 이미지가 쫙 보이기 시작하면 사람들은 로딩되고 있구나 라고 느낄 것이다. 반면에 이미지가 몇 분동안 아예 안 보인다면 짜증이 난다. 그것과 같다.


이것만이 문제가 아니다 만약 비동기를 안 할 경우 뒤에 있는 task는 앞의 task가 끝날 때까지 기다린다. request의 200개 중 100개가 모두 동일한 시간에 끝난다면 그냥 smooth하게 흘러가면 된다.(기다리는 시간만 길어짐) 그런데 재밌는 건 이렇게 하다가 이 중 하나, 즉, 중간에 있는 게 다른 거에 비해 100배의 시간이 걸린다 하면 이 뒤에 있는 딜레이는 엄청날 것이다.


그래서 자기가 가지고 있는 쓰레드 풀에 있는 테스크 수보다 request가 많지가 않다면 동기로하나 비동기로하나 성능상에 별 차이가 없다


이때는 코드 가독성이라던가 오버헤드를 줄이는 측면에서 비동기를 안 쓰는 게 낫다.


나중에 스케일이 커져서 로드가 몰리게 되면 갑자기 평탄하던 그래프가 치솟는다. 이때 비동기를 쓰면 완만하게 그래프가 올라간다. 즉 어느 정도 유지가 된다.


로드가 몰리면 모든 사람이 어느 정도는 느려질 수 있어도 한명이 엄청나게 오래 기다리다가 타임아웃되는 것을 방지하기 위해서는 비동기가 낫다.


YoungKyonYou

Integration of Knowledge