Blocking과 Non-Blocking, 동기와 비동기

5 분 소요

Block과 동기의 차이는?

동기와 비동기, Blocking과 Non-Blocking은 언뜻보면 비슷한 개념으로 보여 헷갈릴 수 있는 개념입니다. 아예 같은 개념으로 알고있는 사람들도 있을 정도죠. 오늘은 이 두 개념의 차이에 대해서 적어보려 합니다.

먼저 간단하게 이 두 차이점을 먼저 설명하자면, 동기와 비동기는 프로세스의 수행 순서 보장에 대한 개념이고, 블로킹과 논블로킹은 프로세스의 제어권에 대한 개념입니다.

무슨 말인지 직관적으로 감이 안온다면 한번 자세히 이 개념에 대해서 알아보도록 합시다.

동기와 비동기

먼저, 동기와 비동기의 개념에 대해서 알아봅시다.

동기는 현재 작업의 응답과 다음 작업의 요청을 맞추는 것을 말합니다.

예를 들어서, 어떠한 함수안에 다른 함수가 있으면, 해당 함수에게 요청을 보내고, 그 응답을 기다렸다가 해당 함수의 응답을 받아야 다음 작업을 실행 함으로써, 우리가 사용해야할 리턴값을 받을때 다음 작업을 실행하는 것이죠.

코드로 보도록 합시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function boss () {
  console.log('사장: 출근');
  console.log('직원에게 작업 지시');
  employee();
  console.log('사장: 퇴근');
}

function employee () {

  for (let i = 1; i < 101; i++) {
    console.log(`직원: 지시받은 작업 ${i}번 수행`);
  }
}

boss();

위에서 설명한 것처럼 boss() 함수안에 employee()라는 함수가 있습니다. boss()가 employee() 함수에게 작업을 지시하면, 직원이 지시받은 작업을 처리하는 것이죠.

그럼 동기에서는 이 작업이 어떻게 이루어질까요?

1
2
3
4
5
6
7
8
9
사장: 출근
직원에게 작업 지시
직원: 지시받은 작업 1번 수행
직원: 지시받은 작업 2번 수행
	        .
		    .
	        .
직원: 지시받은 작업 100번 수행
사장: 퇴근

이렇게 순차적으로 직원이 모든 작업을 다 해야만 employee() 함수가 종료되므로 그동안 사장은 기다렸다가 퇴근 할 것입니다.

그렇다면 비동기는 어떨까요?

비동기는 현재 작업의 응답과 다음 작업의 요청을 맞추지 않는 것입니다.

위의 예제로 말하면, 사장은 지시를 내리고 바로 퇴근하고, 직원은 해당 작업을 사장이 퇴근하고 마무리하고 종료하는 것이죠.

마찬가지로 코드로 보도록 합시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function employee (maxWorkCount = 1, callback) {
  let workCount = 0;
  const interval = setInterval(() => {
    if (workCount > maxWorkCount) {
      callback();
      clearInterval(interval);
    }
    workCount ++;
    console.log(`직원: 직원: 지시받은 작업 ${workCount}번 수행`);
  }, 10);
}

function boss () {
  console.log('사장: 출근');
  console.log(`직원에게 작업지시`);
  employee(100, () => console.log('직원: 작업이 마무리 되었습니다.'));
  console.log('사장: 퇴근');
}

boss();

이 경우에는 결과가 어떻게 나오게 될까요?

1
2
3
4
5
6
7
8
9
10
사장: 출근
직원에게 작업 지시
사장: 퇴근
직원: 지시받은 작업 1번 수행
직원: 지시받은 작업 2번 수행
	        .
		    .
	        .
직원: 지시받은 작업 100번 수행
직원: 작업이 마무리 되었습니다.

차이를 아시겠나요? 비동기의 경우 boss() 함수안에 employee() 함수가 있음에도, employee() 함수가 끝나지도 않았는데 boss() 함수가 종료됩니다.

따라서 employee()의 응답을 다음작업인 boss()의 퇴근과 맞추지 않은 것이죠.

블락과 논블락

자 그럼 이제 블락과 논블락에 대해서 알아봅시다.

블락과 논 블락은 제어권과 관련이 있다고 했습니다.

만약 Blocking의 경우에서 Boss()가 employee()를 호출한다면, boss가 employee()에게 제어권을 넘기는 것입니다. 한마디로 함수실행이 멈추는 것이죠. 따라서, 일을 시킨 사장이 퇴근하지 않고 직원이 일을 끝마칠때까지 기다린다는 것이죠.

이렇게 말로만 보면, 첫번째 예제였던 동기에서의 예제와 똑같다는 생각이 들겁니다. 다시 한번 아까의 예제를 보도록 할까요?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function boss () {
  console.log('사장: 출근');
  console.log('직원에게 작업 지시');
  employee();
  console.log('사장: 퇴근');
}

function employee () {

  for (let i = 1; i < 101; i++) {
    console.log(`직원: 지시받은 작업 ${i}번 수행`);
  }
}

boss();
1
2
3
4
5
6
7
8
9
사장: 출근
직원에게 작업 지시
직원: 지시받은 작업 1번 수행
직원: 지시받은 작업 2번 수행
	        .
		    .
	        .
직원: 지시받은 작업 100번 수행
사장: 퇴근

사장이 출근을 하고, 직원에게 작업을 지시한다음, 작업이 끝날때까지 제어권을 넘겨주고 기다립니다. Blocking이죠. 왜 아까는 동기와 Blocking은 다르다고 해놓고, 이제는 왜 결과가 똑같다고 할까요?

사실 위의 코드는 Blocking + 동기 방식입니다. 보통 Blocking과 동기를 같이 사용하죠. 위 코드는 제어권을 하위 함수에게 넘기는 Blocking인 동시에, 해당 함수의 응답과 다음 작업을 맞추기 위해 기다리는 동기방식입니다.

자 그렇다면 동기 + Non-Blocking은 어떨까요?

다음 코드를 보도록 합시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function* employee () {
  for (let i = 1; i < 101; i++) {
    console.log(`직원: 지시받은 작업 ${i}번 수행`);
    yield;
  }
  return;
}

function boss () {
  console.log('사장: 출근');

  const generator = employee();
  let result = {};

  while (!result.done) {
    result = generator.next();
    console.log(`사장:  사장 업무 처리...`);
  }

  console.log('사장: 퇴근');
}

boss();

이 코드는 동기 + Non-Blocking 코드입니다.

어떤 결과가 나올까요?

1
2
3
4
5
6
7
8
9
10
11
사장: 출근
직원: 지시받은 작업 1번 수행
사장:  사장 업무 처리...
직원: 지시받은 작업 2번 수행
사장:  사장 업무 처리...
            .
		    .
	        .
직원: 지시받은 작업 100번 수행
사장: 사장 업무 처리...
사장: 퇴근

어떤가요?

위 코드는 동기이므로, boss() 함수는 employee()의 응답과 다음 작업을 맞추려하지만, Non-Blocking이기 떄문에 제어권은 넘기지 않습니다. 따라서 자신의 작업을 진행합니다. 자신의 작업을 진행하면서, 직원이 일을 마쳤는지 계속 확인하다가 직원의 작업이 끝나면 타이밍을 맞추어 다음 작업, 퇴근을 진행하는 것이죠.

차이가 이해가 좀 되시나요?

그럼 이제 동기 + Blocking, 동기 + Non-Blocking, 비동기 + Non-Blocking을 모두 알아봤습니다.

그럼 비동기 + Blocking은 무엇일까요?

비동기 + Blocking

이 방식은 위에서 배운내용을 토대로 생각해봤을때, 이상한 느낌이 들 것 입니다. 비동기이므로, 응답과 다음 작업의 타이밍을 맞출 필요가 없는데, 어차피 Blocking이면 타이밍을 맞추지 않는데도 제어권을 넘겨버려, 결국 하위 함수가 끝날때까지 상위 함수가 자신의 일을 처리할 수 없기 때문이죠.

이 방식은 일반적인 어플리케이션 레이어에서는 자주 사용되지 않고 Linux와 Unix 운영체제의 I/O 다중화 모델 정도의 저레벨에서 사용되고 있습니다. 그래서 지금까지 예제로 사용하던 사장님과 직원의 예제로 설명하기는 어려운 개념이므로, 따로 다뤄보도록 하겠습니다.

이 개념이 사용되는 이유는 다음과 같습니다.

  1. 동기 & 블록킹 I/O의 경우 직관적이나, 여러 개의 I/O를 동시에 처리할 수 없습니다.
  2. 논블록킹 I/O는 프로세스들의 작업을 컨트롤하는 것이 까다롭습니다.
  3. 그렇다고 동기 & 블록킹 I/O와 멀티 프로세싱이나 쓰레딩을 결합해서 쓰자니 자원 문제도 있고 프로세스/쓰레드 간 통신이나 동기화가 어렵습니다.

그래서 이를 해결하기 위해서 “프로세스를 블록킹 해놓은 상태로 (제어권을 넘겨 작업을 할 수 없는 상태로 만들고) 비동기로 여러개의 I/O를 다중화해서 받자”는 비동기 + Blocking의 개념이 등장한것입니다.

이 부분은 자세히 다루면 너무 로우한 개념이기도 하고, 특수한 상황에만 쓰이는 개념이므로 여기까지만 간단하게 설명하도록 하겠습니다.

마무리

이제 동기와 비동기, Block과 Non-Block의 차이에 대해서 조금 이해가 가셨나요?

마지막으로 정리하자면, 동기와 비동기는 응답과 다음 작업의 타이밍을 맞출래 말래? 인 것이고, Block과 Non-Block은 하위 함수한테 제어권을 넘길래 말래?의 개념인 것입니다.

어려운 개념이지만 위의 코드를 예제로 직접 작동해보면 이해가 더 쉽게 될 것 같습니다.

이상으로 동기와 비동기, Block과 Non-Block의 차이에 대한 글을 마칩니다.

카테고리:

업데이트:

댓글남기기