Home > OS > 동기/비동기 and 블로킹/논블로킹 이해

동기/비동기 and 블로킹/논블로킹 이해
OS

동기/비동기 and 블로킹/논블로킹 이해


프로그래밍에서 동기(Synchronous)/비동기(Asynchronous)블로킹(Blocking)/논블로킹(Non-blocking)은 자주 혼동되는 개념입니다. 이 글에서는 이 두 가지 개념의 차이점을 알아보고, 파일 읽기 예시를 통해 어떻게 적용되는지 살펴보겠습니다.

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


작업 간의 흐름과 완료 시점에 초점을 둡니다.

  • 동기: 작업들이 순차적으로 진행되며, 완료 결과를 받아야 다음 작업이 시작됩니다.
  • 비동기: 작업들이 동시에 또는 독립적으로 진행되며, 하나의 작업이 완료될 때까지 기다리지 않고 다음 작업을 시작합니다. 콜백(callback), 시그널(signal) 등을 통해 작업의 완료를 통지받습니다.

Sync, Async

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


함수 호출 시 호출자의 대기 여부에 초점을 둡니다.

  • 블로킹: 함수 호출 시 호출자는 작업이 완료될 때까지 제어권을 잃고 대기하는 상태가 됩니다. 그 동안 다른 작업을 수행하지 못합니다.
  • 논블로킹: 함수 호출 시 바로 제어권을 반환받아 다른 작업을 수행할 수 있습니다. 호출자가 대기하지 않습니다.

Blocking, Non-blocking

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


동기 + 블로킹

동기(Synchronous)와 블로킹(Blocking)의 조합은, 함수가 호출되면 작업이 완료될 때까지 대기하고, 그 다음 코드가 실행되는 일반적인 함수 호출 방식입니다.

Synchronous + Blocking

파이썬 코드 예시

def synchronous_blocking_read():
    print("파일 읽기 시작")
    with open('example.txt', 'r') as f:
        data = f.read()
    print("파일 읽기 완료")
    print("데이터:", data)

print("메인 프로그램 시작")
synchronous_blocking_read()
print("메인 프로그램 종료")

Output:

메인 프로그램 시작
파일 읽기 시작
(파일 읽는 동안 대기)
파일 읽기 완료
데이터: (파일 내용 출력)
메인 프로그램 종료

코드 설명:

  • open() 함수를 사용하여 파일을 열고 read() 메서드로 내용을 읽습니다.
  • 파일을 모두 읽을 때까지 프로그램은 다음 단계로 진행하지 않고 대기합니다.
  • 작업이 완료된 후에야 다음 코드가 실행됩니다.순차적으로 진행됩니다.

동기 + 논블로킹

주로 폴링(Polling) 기법을 사용하여 상태를 주기적으로 확인하는 방식에 사용됩니다. 호출자는 즉시 제어권을 반환받지만 작업은 순차적으로 진행됩니다.

Synchronous + Non-blocking

파이썬 코드 예시

이 코드에서는 BlockingIOError가 발생하지 않을 수 있으며, 파일 시스템과 운영체제에 따라 다를 수 있습니다.
보통 소켓, 파이프와 같은 I/O 스트림에서 사용하는 것이 일반적이지만 이해를 돕기 위해 파일 read예시로 코드를 작성했습니다.

import os
import errno
import time

def synchronous_nonblocking_read():
    print("파일 읽기 시작")
    fd = os.open('example.txt', os.O_RDONLY | os.O_NONBLOCK)
    data = b''
    while True:
        try:
            chunk = os.read(fd, 1024)
            if not chunk:
                break
            data += chunk
        except BlockingIOError as e:
            if e.errno in (errno.EAGAIN, errno.EWOULDBLOCK):
                print("데이터를 기다리는 중...")
                time.sleep(0.1)  # 잠시 대기 후 다시 시도
                continue
            else:
                raise
    os.close(fd)
    print("파일 읽기 완료")
    print("데이터:", data.decode())

print("메인 프로그램 시작")
synchronous_nonblocking_read()
print("메인 프로그램 종료")

Output:

메인 프로그램 시작
파일 읽기 시작
데이터를 기다리는 중...
데이터를 기다리는 중...
데이터를 기다리는 중...
파일 읽기 완료
데이터: (파일 내용 출력)
메인 프로그램 종료

코드 설명:

  • os.open() 함수를 사용하여 파일을 논블로킹 모드(os.O_NONBLOCK)로 엽니다.
  • os.read()를 통해 파일을 읽으려 하지만, 데이터가 준비되지 않았다면 BlockingIOError 예외가 발생합니다.
  • 예외 처리에서 EAGAIN, EWOULDBLOCK 오류 코드를 확인하여 데이터가 준비될 때까지 루프를 돌며 재시도합니다.
  • 파일을 모두 읽으면 루프를 종료하고 파일을 닫습니다.

비동기 + 블로킹

작업을 비동기로 호출했지만, 완료될 때까지 기다립니다. 비동기의 장점을 살리지 못하고 블로킹되기 때문에 비효율적입니다.

Asynchronous + Blocking

비동기 + 논블로킹

작업을 비동기로 호출하고, 호출자는 즉시 제어권을 반환받아 다른 작업을 수행합니다. 작업이 진행되는 동안 다른 작업을 수행할 수 있어 자원 활용에 효율적입니다.

Asynchronous + Non-blocking

파이썬 코드 예시

import asyncio
import aiofiles

async def asynchronous_nonblocking_read():
    print("파일 읽기 시작")
    async with aiofiles.open('example.txt', 'r') as f:
        data = await f.read()
    print("파일 읽기 완료")
    print("데이터:", data)

async def main():
    print("메인 프로그램 시작")
    task = asyncio.create_task(asynchronous_nonblocking_read())
    print("다른 작업 수행 중...")
    await task  # 파일 읽기가 완료될 때까지 대기
    print("메인 프로그램 종료")

asyncio.run(main())

Output:

메인 프로그램 시작
다른 작업 수행 중...
파일 읽기 시작
(파일 읽는 동안에도 다른 작업 가능)
파일 읽기 완료
데이터: (파일 내용 출력)
메인 프로그램 종료

코드 설명:

  • aiofiles.open()을 사용하여 비동기적으로 파일을 엽니다.
  • await f.read()로 파일을 읽는 동안에도 이벤트 루프는 다른 작업을 수행할 수 있습니다.
  • asyncio.create_task()를 통해 작업을 스케줄링하고, 즉시 제어권을 반환합니다.
  • 파일 읽기가 진행되는 동안 “다른 작업 수행 중…“이 출력됩니다.

개념 정리


동기 vs 비동기

  • 동기는 작업의 순서의존성에 초점을 둡니다.
  • 비동기는 작업이 독립적으로 진행될 수 있음을 나타냅니다.

블로킹 vs 논블로킹

  • 블로킹은 호출한 측에서 기다리는지 여부를 나타냅니다.
  • 논블로킹은 호출한 측에서 즉시 제어권을 반환받는지 여부를 나타냅니다.

언제 어떤 방식을 사용할까?


  • 동기 블로킹: 구현이 비교적 쉽기 때문에 간단한 스크립트나 순차적 작업에 적합합니다.
  • 동기 논블로킹: 비동기 처리를 도입하기 어려운 경우에 사용됩니다. 주로 폴링에 사용됩니다.
  • 비동기 블로킹: 비효율적인 방식이라 거의 사용하지 않습니다.
  • 비동기 논블로킹: 대규모 네트워크 요청이나 입출력 작업에서 성능 향상을 위해 사용됩니다.

결론


동기/비동기블로킹/논블로킹은 프로그래밍에서 중요한 개념으로, 시스템의 성능과 효율성에 큰 영향을 미칩니다.
각 개념을 올바르게 이해하고 상황에 맞게 적용하면, 프로그램의 응답성과 자원 활용을 최적화할 수 있습니다.