[OS] 동기와 비동기, 블로킹과 논블로킹

동기(Synchronous)와 비동기(Asynchronous)

동기/비동기는 주로 어플리케이션에서 자주 다뤄지는 개념이며, 다음 작업이 요청되는 시간과 관련되어 있다.

동기(Synchronous)

비동기(Asynchronous)

image1

동기 예시

function run(a, b) {
    return a + b
}

const result = run(1, 2);

console.log("시작");
console.log("결과:", result);
console.log("");

/**출력 결과
 * 시작
 * 결과: 3
 * 끝
 */

비동기 예시

function run(a, b) {
    return a + b
}

let result;
setTimeout(() => {
    result = run(1, 2);
}, 1000);

console.log("시작");
console.log("결과:", result);
console.log("");

/**출력 결과
 * 시작
 * 결과: undefined
 * 끝
 */

블로킹(Blocking)과 논블로킹(Non-Blocking)

블로킹/논블로킹은 주로 멀티 스레딩, I/O 등에서 사용되는 개념이며, 함수의 리턴 시점제어권에 따라 차이가 난다.

블로킹(Blocking)

논블로킹(Non-Blocking)

image2

(http://homoefficio.github.io/2017/02/19/Blocking-NonBlocking-Synchronous-Asynchronous/)

블로킹 예시

function run() {
    // 오래 걸리는 작업
    console.log("작업 끝");
}

console.log("시작");
run();
console.log("다음 작업");

/** 출력 결과
 * 시작
 * 작업 끝
 * 다음 작업
 */

논블로킹 예시

function run() {
    // 오래 걸리는 작업
    console.log("작업 끝");
}

console.log("시작");
setTimeout(run, 0);
console.log("다음 작업");

/** 출력 결과
 * 시작
 * 다음 작업
 * 작업 끝
 */

이렇게만 보면 동기-블로킹, 비동기-논블로킹을 완전히 동일한 개념으로 생각할 수 있다. 물론 유사하게 동작하지만, 주요 관심사에 따라 차이가 난다. 앞서 언급한 호출한 함수 - 호출된 함수 이 개념을 상위 프로세스 / 하위 프로세스로 생각하면서 다음의 예시를 보자. 블로그의 예시를 좀 더 쉽게 표현하고자 노력했다(블로그에 설명이 정말 잘 되어 있으니 꼭 한 번 보면 좋을 것 같다).

상위 프로세스인 선생님(teacher)이 있고, 하위 프로세스인 학생들(student)이 있다고 가정하자. 각각의 선생님은 학생들에게 자습을 시키는데, 선생님들마다 각자의 자습 스타일이 있다. 이를 코드로 살펴보자.

동기 + 블로킹

동기+블로킹 선생님은 나이가 지긋한 FM 선생님이다. 입실 후 학생들의 자습이 모두 끝날때까지 아무것도 하지 않으면서 기다리고, 학생들의 자습이 끝나면 퇴실한다.

function student() {
    for(i=1;i<11;i++) {
        console.log(`학생: ${i}번 문제 푸는중...`);
    }
}

function teacher() {
    console.log("선생님: 입실");
    student();
    console.log("선생님: 퇴실");
}

teacher();
// 출력 결과
선생님: 입실
학생들: 1번 문제 푸는중...
학생들: 2번 문제 푸는중...
학생들: 3번 문제 푸는중...
학생들: 4번 문제 푸는중...
학생들: 5번 문제 푸는중...
학생들: 6번 문제 푸는중...
학생들: 7번 문제 푸는중...
학생들: 8번 문제 푸는중...
학생들: 9번 문제 푸는중...
학생들: 10번 문제 푸는중...
선생님: 퇴실

동기 + 논블로킹

동기+논블로킹 선생님은 딴짓을 열심히 하는 선생님이다. 입실 후 학생들의 자습이 모두 끝날때까지 기다리면서 딴짓을 하면서 기다리고, 학생들의 자습이 끝나면 퇴실한다.

function* students() {
    for(i=1;i<11;i++) {
        console.log(`학생들: ${i}번 문제 푸는중...`)
        yield;
    }
    return
}

function teacher() {
    console.log("선생님: 입실");

    const generator = students();
    let done;

    while (!done) {
        done = generator.next().done;
        if (!done) {
            console.log("선생님: 딴짓")
        }
    };

    console.log("선생님: 퇴실");
}

teacher();
// 출력 결과
선생님: 입실
학생들: 1번 문제 푸는중...
선생님: 딴짓
학생들: 2번 문제 푸는중...
선생님: 딴짓
학생들: 3번 문제 푸는중...
선생님: 딴짓
학생들: 4번 문제 푸는중...
선생님: 딴짓
학생들: 5번 문제 푸는중...
선생님: 딴짓
학생들: 6번 문제 푸는중...
선생님: 딴짓
학생들: 7번 문제 푸는중...
선생님: 딴짓
학생들: 8번 문제 푸는중...
선생님: 딴짓
학생들: 9번 문제 푸는중...
선생님: 딴짓
학생들: 10번 문제 푸는중...
선생님: 딴짓
선생님: 퇴실

비동기 + 논블로킹

비동기+논블로킹 선생님은 아주 바빠서 자신의 시간이 소중하다. 입실 하자마자 자습을 지시한 후 바로 퇴실한다. 학생들의 자습이 끝나면 반장을 통해 보고받는다.

function students(callback) {
    let i = 1;
    const interval = setInterval(() => {
        if (i > 10) {
            callback();
            clearInterval(interval);
        } else {
            console.log(`학생들: ${i}번 문제 푸는중...`);
            i++;
        }
    }, 10);
}

function teacher() {
    console.log("선생님: 입실");
    students(() => console.log("반장: 선생님께 자습이 끝났음을 보고"));
    console.log("선생님: 퇴실");
}

teacher();
// 출력 결과
선생님: 입실
선생님: 퇴실
학생들: 1번 문제 푸는중...
학생들: 2번 문제 푸는중...
학생들: 3번 문제 푸는중...
학생들: 4번 문제 푸는중...
학생들: 5번 문제 푸는중...
학생들: 6번 문제 푸는중...
학생들: 7번 문제 푸는중...
학생들: 8번 문제 푸는중...
학생들: 9번 문제 푸는중...
학생들: 10번 문제 푸는중...
반장: 선생님께 자습이 끝났음을 보고

비동기 + 블로킹

이 방식은 다른 블로그에서 설명이 많이 부족한 편이었는데, 자료마다 의견이 제각각이었다.

참고한 글에서도 적절한 코드가 없었기에 생략했다.

1. 비동기+논블로킹 방식을 사용하는 과정에서 의도치 않게 사용됨

2. 직관적인 코드의 흐름을 유지하면서 작업을 병렬적으로 처리하기 위함

  1. 동기 & 블록킹 I/O의 경우 직관적이나, 여러 개의 I/O를 동시에 처리할 수 없다.
  2. 논블록킹 I/O는 프로세스들의 작업을 컨트롤하는 것이 까다롭다. (대부분 이런 저레벨 프로그램은 C로 짠다. JS나 Python 같은 걸 생각하면 안된다.)
  3. 그렇다고 동기 & 블록킹 I/O와 멀티 프로세싱이나 쓰레딩을 결합해서 쓰자니 자원 문제도 있고 프로세스/쓰레드 간 통신이나 동기화가 빡셈

원문링크

[OS] 동기와 비동기, 블로킹과 논블로킹