본문 바로가기

Web

블로킹, 논블로킹 vs 동기, 비동기

블로킹, 논블로킹

  • A 함수가 B 함수를 호출해도 호출자가 제어권은 그대로 가지고 있는가(논블로킹), 제어권을 잃는가(블로킹)
  • 제어권은 자신(함수)의 코드를 실행할 권리 같은 것이다. 제어권을 가진 함수는 자신의 코드를 끝까지 실행한 후, 자신을 호출한 함수에게 돌려준다. 제어권을 가지고 있다는 것프로그램이 원하는 대로 다음 작업을 선택하고 실행할 수 있다는 의미다.
  • 블로킹: 호출된 작업이 실행되는 동안 호출한 작업은 제어권을 잃는다. 자신의 코드를 실행할 수 없다.
  • 논블로킹: 호출된 작업의 실행과 상관없이 호출한 작업은 자신의 코드를 실행한다. 

 

✅ 블로킹, 논블로킹은 I/O 작업과 관련된 개념???

 

관련 정보를 찾아보다 "블로킹, 논블로킹 개념은 I/O 작업과 관련된 것이다."라는 글을 발견했다.

결론부터 말하자면 

블로킹과 논블로킹은 I/O 작업에서 특히 자주 언급되지만, 반드시 I/O에만 국한된 것은 아니다.
이 개념은 제어권의 반환 여부에 관한 것으로, I/O 뿐만 아니라 다른 작업(ex. 동기화, 쓰레드 작업 등)에서도 적용될 수 있다.

더보기

I/O는 파일 읽기 및 쓰기, 데이터베이스 읽기 및 쓰기, 네트워킹 보내기 및 결과 받기 등의 작업들,
즉 CPU와 메모리의 밖과 소통하는 작업들,
컴퓨터와 주변 장치간에 데이터를 주고받는 작업들
I/O 작업은 CPU 작업에 비해 매우 느리다.

- 여기서부턴 모호하게 이해
I/O 작업은 주로 백그라운드에서 일어난다(항상은 아님)
대게 I/O 작업은 system call을 통해 운영체제에 요청되며,
이 요청을 처리하기 위해 새로운 스레드를 생성하거나,
스레드 풀에서 기존에 생성된 스레드에 I/O 작업을 할당해 진행된다.

 

 


 

동기, 비동기

 

  • Synchronous / Asynchronous: 영단어의 Sync는 그리스어로 '함께'라는 뜻이고, chrono는 '시간'이라는 뜻이다. 함께 시간을 맞춰서 실행한다. 이런 의미인 것이다. 동기(同期)의 '동시에 움직임'이라는 뜻보단 Synchronous의 '시간을 맞춰 움직임'이라는 의미에 더 적합하다.
  • 시간을 맞춰 움직임 -> 어떤 작업이 완료될 때까지 기다린다. 즉 순서대로 실행되냐의 여부에 관한 개념이다.
  • A 함수의 작업이 완료된 후, B 함수가 작업을 시작한다(동기), A 함수의 작업 완료 여부와 상관없이 B 함수의 작업이 이뤄진다(비동기)
  • 동기: 어떤 작업이 완료된 후 그 다음 작업이 실행된다. 
  • 비동기: 작업의 완료 여부와 상관없이 다른 작업이 실행된다.

 

결국 동기, 비동기블로킹, 논블로킹은 그게 그뜻 아닌가?

 

그럼 동기, 비동기와 블로킹, 논블로킹의 개념은 같은 것 아닌가?

이 두 개념은 서로 이야기하는 관점이 다르다.

 

블로킹과 논블로킹은 호출자의제어권 반환여부를 따지는 개념이고,

동기와 비동기는 이전 작업의완료여부따지는 개념인 것이다.

 

서로 무엇을 따지면서 이야기하느냐의 차이인 것이다.

 

물론 이론적 개념으로는 서로 다른 것이지만,

이 둘은 서로 밀접하게 관련있다고 말할 수 있다.

 

아무래도

블로킹 방식이면서, 동기적 방식으로 실행되는 동기&블로킹 조합의 작업방식,

논블로킹 방식이면서, 비동기적 방식으로 실행되는  비동기&논블로킹 조합의 작업방식,

프로그래밍에서 작업은 보통 이렇게 일어나기 때문이다.

 

그러나 블로킹이면서 비동기적 방식에 해당하는, 

또는 논블로킹이면서 동기적 방식에 해당하는 경우도 가끔 존재한다.

 

말로는 잘 떠오르지 않을테니 실제 사례를 보며 이해하자. 


 

동기/비동기와 블로킹/논블로킹 조합

 

  • 동기 블로킹
  • 비동기 논블로킹
  • 동기 논블로킹
  • 비동기 블로킹

 

동기 논블로킹 (Sync Non-Blocking) 

 

다른 작업이 진행되는 동안에도 자신의 작업을 처리하고(Non Blocking),

다른 작업의 결과를 바로 처리(즉시 반환)하여 작업을 순차대로 수행하는(Sync) 방식

 

호출자가 작업 완료 여부를 반복적으로 확인하면서 다른 작업을 수행할 수 있다.

즉 호출자가 제어권을 잃지 않으면서 작업이 순차적으로 실행된다.

✅ "즉시 반환"된다 해도, 엄밀히 따지면 반환되기까지는 호출자가 다른 작업을 하지 못하고 기다려야하므로, 그 짧은 순간동안은 제어권을 잃는 것 아닌가? 

perplexity의 답변을 빌려오겠다.

 

  1. 실제적인 영향
    • 이 미세한 대기 시간은 대부분의 애플리케이션에서 무시할 만한 수준입니다.
    • 하지만 극도로 빠른 반응 시간이 요구되는 고성능 시스템에서는 이 시간도 고려 대상이 될 수 있습니다.
  2. 프로그래밍 모델의 관점
    • 논블로킹 모델에서 중요한 점은 '즉시 반환'이라는 개념적 모델입니다.
    • 이는 프로그래머가 긴 대기 시간 없이 다음 작업을 계획할 수 있게 해줍니다.

결론적으로, 귀하의 지적은 정확합니다.

기술적으로는 함수 호출 완료까지 미세한 대기가 있지만,

이는 논블로킹의 주요 이점(긴 대기 없이 다른 작업 수행 가능)을 훼손하지 않을 정도로 짧습니다.

 

실생활 예시

 

팀장이 업무 A, B, C를 사원 1, 2, 3 에게 시키려고 한다. 그런데 업무 특성상 업무 A를 완료해야 업무 B를 할 수 있고, 업무 B를 완료해야 업무 C를 할 수 있다고 한다.

  1. 팀장 : 사원1씨 업무 A 좀 해주세요
  2. 사원1 : 네 알겠습니다. (A를 처리중)
  3. 팀장 : 다음 업무 B를 하려면 A가 완료되야 하는데.. 사원1씨 다했어요?
  4. 사원1 : 아직이요 A 처리중입니다
  5. 팀장 : 사원1씨 다했어요?
  6. 사원1 : 아직이요 A 처리중입니다
  7. 사원1 : 팀장님 A 모두 완료했습니다
  8. 팀장 : 수고했어요. 사원2씨 업무 B좀 해주세요.
  9. 사원2 : 네 알겠습니다. (B를 처리중)
  10. 팀장 : 다음 업무 C를 하려면 B가 완료되야 하는데.. 사원2씨 다했어요?
  11. ...생략

출처: https://inpa.tistory.com/entry/👩‍💻-동기비동기-블로킹논블로킹-개념-정리#sync_blocking_조합 [Inpa Dev 👨‍💻:티스토리]


실사례

 

  • 폴링(Poiing)


예시코드: 소켓통신에서의 데이터 수신

 

import socket

# 소켓 생성 및 논블로킹 모드 설정
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setblocking(False)
sock.connect_ex(('example.com', 80))

while True:
    try:
        data = sock.recv(1024)  # 논블로킹 호출
        if data:
            print("데이터 수신:", data)
            break
        else:
            print("아직 데이터가 없습니다.")
    except BlockingIOError:
        print("데이터를 기다리는 중...")
    
    # 다른 작업 수행
    do_other_tasks()

    # 잠시 대기
    time.sleep(0.1)

 

1. 프로그램이 네트워크 소켓에서 데이터를 읽으려고 recv()를 호출한다.

2. 소켓이 논블로킹 모드로 설정되어 있다면, 데이터 유무와 관계없이 recv()는 즉시 반환된다. 데이터가 없으면 예외를 발생시킨다.

3. 프로그램은 데이터를 읽을 때까지 주기적으로 recv()를 호출하여 상태를 확인한다.

 

 

  • JavaScript의 Promise와 async/await

 

JavaScript에서 비동기로 설계된 API(fetch())를 사용할 때, await 키워드를 사용하면 코드 흐름은 동기적으로 보인다.

하지만 내부적으로는 비동기로 동작하며, I/O 작업이 완료되지 않아도 다른 이벤트 루프가 실행될 수 있다.

 

I/O 작업이나 이벤트 처리를 기다리는 동안 즉시 반환되지만,

호출자가 반복적으로 상태를 확인하며 순차적인 흐름을 유지한다.

 

 

비동기 블로킹(Sync Blocking)

 

 

Async Blocking 조합은 다른 작업이 진행되는 동안 자신의 작업을 멈추고 기다리는 (Blocking), 다른 작업의 결과를 바로 처리하지 않아 순서대로 작업을 수행하지 않는 (Async) 방식

 

 

실무에선 거의 다룰 일 없다. 

 

다만 Async Blocking 이 실제로 적용된 실무 사례가 있긴 하다.

Node.js + MySQL의 조합이 대표적인데, Node.js에서 비동기 방식으로 데이터베이스에 접근하기 때문에 Async 이지만, MySQL 데이터베이스에 접근하기 위한 MySQL 드라이버가 블로킹 방식으로 작동되기 때문이다.

 

 

  1. JavaScript는 비동기 방식으로 MySQL에 쿼리를 보낸다. (Async)
  2. MySQL은 쿼리를 처리하면서 JavaScript에게 제어권을 넘겨주지 않는다. (Blocking)
  3. 그러면 JavaScript는 다른 작업을 계속 수행할 수 있지만, MySQL의 결과값을 필요로 하기 때문에 MySQL이 쿼리를 완료할 때까지 기다려야 된다.
  4. 결국 Sync Blocking과 작업 수행에 차이가 없게 된다.

이렇게 JavaScript와 MySQL의 조합은 비동기적이면서도 블로킹되는 Async Blocking 조합이라고 할 수 있다. 이러한 오묘한 조합은 오히려 개발자에게 혼동만 일으키기 때문에 그래서 실무에서는 Node.js 서버 프로그래밍할때 아예 async/await로 동기 처리를 하는 편이다.

const mysql = require('mysql');

// connectDB 함수를 정의
async function connectDB () {
  // DB 연결 정보를 담은 객체 생성
  let connectionInfo = {
    host: 'localhost', // DB 호스트 주소
    user: 'root', // DB 유저 이름
    password: '1234', // DB 비밀번호
    database: 'spyncdb' // DB 이름
  };
  
  // connection 객체를 생성하고 반환
  let connection = mysql.createConnection(connectionInfo);
  return connection;
}

// mysql 라이브러리를 사용하는 경우
async function userHandler (username, displayName, profilePicture, email) {
  // DB에 연결
  connection = await connectDB() 
  
  // DB를 선택
  await connection.query ('USE spyncdb;'); 
  
  // 쿼리를 실행하고 결과를 받음
  let result = await connection.query ('SELECT * FROM users WHERE username = ?', [username]); 
}

// userHandler 함수를 호출하고 user_id 값을 얻으려고 함
let user_id = await userHandler (aUser.username, aUser.displayName, aUser.profilePicture, aUser.email); // userHandler 함수가 비동기 작업을 완료할 때까지 기다림
console.log (user_id); // user_id 출력

 

출처: https://inpa.tistory.com/entry/👩‍💻-동기비동기-블로킹논블로킹-개념-정리#sync_blocking_조합 [Inpa Dev 👨‍💻:티스토리]