[WAFFLE] 04-4 ChatGPT Q&A
Published:
In this post, terminology explanation about concurrent programming by ChatGPT will be provided.
(2025.09.22. ver.)
동시성(Concurrency) 란, 여러 작업을 겉보기에 동시에 실행하는 것처럼 보이게 하는 것을 의미하며, 이는 OS 스케쥴링 과 CPU interrupt 를 통해 구현된다. CPU의 명령어 실행 모델은 동기적이다 (멀티 스레드의 경우에도 마찬가지이다). 예를 들어, CPU에서 load 명령어를 실행하면 디스크 I/O가 발생해 디스크에서 데이터를 읽어와야 하는데 그 때까지 CPU는 멈추어 기다리게 된다. 따라서 이렇게 낭비되는 시간을 없애기 위해 OS 스케쥴링 을 통해 OS는 CPU에게 다른 프로세스의 명령어 또는 같은 프로세스 안의 다른 스레드 명령어를 할당해, 일을 처리하게 한다. I/O 장치가 작업을 끝내면 interrupt 로 CPU에게 신호를 보내고 CPU는 그때 가서 결과를 처리한다.
스레드(thread) 란 프로그램 실행의 가장 작은 단위, 즉 OS가 스케쥴링 하는 최소 단위를 의미한다. multithread 란, 하나의 process 안에서 여러 개의 스레드가 동시에 동작하는 것을 의미한다. 예를 들면, 웹브라우저 프로그램에 프로세스로서 실행중일 때, 스레드 A는 사용자 입력 처리, 스레드 B는 화면 렌더링 등의 작업을 동시에 수해하고 있을 수 있다. 단일 코어(CPU) 에서 멀티 스레드의 구현은 실제로는 OS 스케줄러가 아주 짧은 시간 단위로 스레드를 바꿔가며 실행하는 방식으로 이루어진다. 멀티 코어(CPU) 에서 멀티스레드의 구현은 물리적으로 여러 코어가 있으니, 여러 스레드가 동시에 실행 가능하다. 일반적으로, 프로세스 내의 각 스레드는 자기만의 스택을 가지고, 코드, 데이터, 힙, fd를 공유한다. multiprocess 대신, multithread 를 이용하면, 프로세스마다 코드, 데이터, 힙 영역이 따로 필요하지 않으므로 메모리를 절약할 수 있고, 데이터 공유 과정에서 IPC를 필요로 하지 않는다. 또한, 프로세스 간 Context Switch의 비용이 같은 프로세스 내 스레드 간 Context Switch의 비용보다 훨씬 크다.
프로그램이 동기(Synchronous) 혹은 비동기(Asynchronous) 적으로 실행되는 것은 개발자가 프로그램을 어떻게 작성했느냐에 따라 (즉, 프로그램이 명령어 집합으로서 어떻게 컴파일 되었냐에 따라) 결정된다. 비동기 는 “작업 완료를 기다리지 않는 코드 작성 방식”이다. 예를 들어, 동기적 방식으로 작성된 아래 코드를 보자.
import time
def task(name):
print(f"{name} 시작")
time.sleep(2) # 2초 동안 블로킹
print(f"{name} 끝")
print("A")
task("작업1")
task("작업2")
print("B")
A
작업1 시작
(2초 대기, 이때 context switch 가능)
작업1 끝
작업2 시작
(2초 대기, 이때 context switch 가능)
작업2 끝
B
이 파이썬 프로그램을 실행하면 다음과 같이 순서대로 실행될 것이다. 이 과정에서 다른 스레드 혹은 프로세스로의 context switch가 일어나는 것은 동시성에 관련된 것이고, 위 처럼 프로그램의 실행 순서가 우리가 기존에 알고 있던 것처럼 순차적으로 동작하는 것은 동기적 으로 코드를 작성했기 때문이다. 참고로, 하나의 프로세스 안에서 각 스레드들은 서로 간의 실행 진도가 서로에게 영향을 미치지 않도록 배분되기 때문에 하나의 스레드에서 디스크 I/O, 네트워크 I/O가 발생하면 다른 스레드로 context swtich 되고, interrput 가 발생하면 다시 해당 스레드로 돌아갈 수 있다. 이 과정도 동시성에 관련된 것이고 프로그램은 여전히 동기적으로 실행된다. 반면 비동기적 방식으로 작성된 아래 코드를 보자.
import asyncio
async def task(name):
print(f"{name} 시작")
await asyncio.sleep(2) # 2초 동안 다른 작업 실행 기회 줌
print(f"{name} 끝")
async def main():
print("A")
# 두 작업을 동시에 실행
await asyncio.gather(
task("작업1"),
task("작업2")
)
print("B")
asyncio.run(main())
A
작업1 시작
작업2 시작
(2초 대기, 하지만 동시에 대기)
작업1 끝
작업2 끝
B
하나의 스레드에서 위 프로그램이 실행되는 것을 생각해보자.(이벤트 루프는 기본적으로 단일 스레드에서 동작한다.) 이벤트 루프(event loop) 란, 언어 런타임/라이브러리 레벨의 개념 (JS, Python asyncio, Node.js 등에서 쓰이는 코드) 으로, OS가 넘겨준 이벤트(“데이터 도착”, “타이머 끝남” 등)를 큐에 모아두고, 준비된 콜백/코루틴을 실행하는 반복문이다. 위 코드에서 task("작업1") 을 수행하던 중, await asyncio.sleep(2) 를 만나면, CPU는 제어권을 이벤트 루프에 반환한다(즉, 이벤트 루프 코드가 CPU에서 수행된다). CPU는 task("작업2") 라는 다른 코드를 실행하게 된다. 2초 후, OS가 “이제 끝났어!” 이벤트를 이벤트 루프에 전달한다. 즉, “기다림”(await) 자체는 CPU가 하지 않고 OS 타이머/네트워크 카드 같은 외부 장치가 담당한다. 이후, 다시 작업 끝 을 수행하게 된다.
동기/비동기의 개념과 블로킹(Blocking)/논블로킹(Non-blocking) 의 개념은 다른 차원의 이야기이다.
- 블로킹/논블로킹 → 호출된 함수가 바로 리턴하냐, 안 하냐
- 동기/비동기 → 작업 완료 후 결과를 어떻게 받냐 (직접/콜백 or 이벤트)
예:
- 동기 + 블로킹:
read()→ 데이터 올 때까지 기다림 - 동기 + 논블로킹:
read()→ 당장 데이터 없으면 “없다” 리턴, 반복 확인 필요 (polling) - 비동기 + 블로킹: 드묾 (거의 사용하지 않음)
- 비동기 + 논블로킹: 요청 후 즉시 리턴, 완료되면 콜백/이벤트로 결과 전달 (
asyncio,fetch)
(2024.10.05. ver.)
동시성(Concurrency) vs. 병렬성(Parallelism)
- 동시성(Concurrency):
- 여러 작업(task)을 논리적으로 동시에 처리하는 것을 의미한다.
- 물리적 CPU 코어 개수와 상관없이 단일 코어에서도 멀티태스킹처럼 구현할 수 있다.
- 예: Python의 코루틴 기반 비동기 실행 (async/await).
- 병렬성(Parallelism):
- 여러 작업을 물리적으로 동시에 처리하는 것을 의미한다.
- 멀티코어 CPU나 분산 시스템에서 실행된다.
- 예: 멀티 프로세싱을 이용한 데이터 병렬 처리.
- 차이점:
- 동시성은 여러 작업을 번갈아 실행하는 방식이고, 병렬성은 여러 작업을 동시에 실행하는 방식이다.
동기(Synchronous) vs. 비동기(Asynchronous)
- 동기(Synchronous):
- 작업을 순차적으로 처리하며, 이전 작업이 끝나야 다음 작업을 시작한다.
- 함수 호출자가 작업이 끝날 때까지 대기한다.
- 예: 파일 읽기 함수가 데이터를 읽을 때까지 기다렸다가 결과를 반환.
- 비동기(Asynchronous):
- 작업이 시작된 후, 결과를 기다리지 않고 다음 작업을 수행한다.
- 결과는 나중에 콜백, 프로미스 또는 async/await로 처리된다.
- 예: 네트워크 요청을 비동기로 보내고 다른 작업을 먼저 실행.
- 비동기 방식은 동시성 프로그맹의 구현 방식 중 하나임.
블로킹(Blocking) vs. 논블로킹(Non-blocking)
- 블로킹(Blocking):
- 호출된 작업이 완료될 때까지 호출자가 대기하는 방식이다.
- 작업 중 리소스를 점유하며 다른 작업을 방해할 수 있다.
- 예: time.sleep().
- 논블로킹(Non-blocking):
- 호출된 작업이 즉시 반환되며, 호출자는 다른 작업을 계속 실행할 수 있다.
- 예: 논블로킹 소켓 통신.
- 블로킹/논블로킹과 동기/비동기는 관련이 있지만, 서로 다른 개념이다.
- 블로킹/논블로킹은 호출자가 대기하느냐의 문제이다.
- 동기/비동기는 작업의 순서와 흐름에 초점이 맞춰져 있다.
스레드(Thread) vs. 프로세스(Process)
- 프로세스(Process):
- 실행 중인 프로그램의 독립적인 인스턴스.
- 각 프로세스는 자신만의 메모리 공간을 가지며, 다른 프로세스와 메모리를 공유하지 않는다.
- 예: Python의 multiprocessing 모듈.
- 스레드(Thread):
- 하나의 프로세스 내에서 실행되는 독립적인 작업 단위.
- 동일한 메모리 공간을 공유하며 실행된다.
- 스레드는 가벼운 리소스를 사용하지만, 동기화 문제가 발생할 수 있다.
- 예: Python의 threading 모듈.
- 단일 코어에서는 스레드가 동시성을 지원.
- 멀티코어에서는 스레드가 병렬성을 지원.
- 차이점:
- 프로세스는 무겁고 독립적이며, 스레드는 가볍고 메모리를 공유한다.
코루틴(Coroutine)
- 코루틴은 경량화된 스레드의 개념으로, 비동기 작업을 위한 함수이다.
- 코드가 실행 중 특정 지점에서 실행을 멈추고, 다른 코루틴에게 실행 권한을 넘길 수 있다.
- Python에서는 async def로 코루틴을 정의하고, await 키워드로 다른 코루틴의 결과를 기다린다.
- 코루틴은 동시성을 처리하기 위한 강력한 도구이다.
이들의 관계
- 동시성과 병렬성은 작업 실행 방식을 나타낸다.
- 동시성은 코루틴이나 스레드로 구현된다.
- 병렬성은 멀티프로세싱으로 구현된다.
- 동기/비동기와 블로킹/논블로킹은 작업 흐름과 대기 방식에 영향을 준다.
- 비동기는 주로 코루틴으로 구현되며, 논블로킹 방식이 선호된다.
- 스레드와 프로세스는 작업 실행의 물리적 단위를 제공한다.
- 동시성과 병렬성을 구현하는 기본 단위이다.
- 코루틴은 동시성을 최적화하고 비동기 작업을 효율적으로 처리한다.
비유로 이해하기
- 동시성: 여러 사람이 같은 테이블에서 번갈아가며 일하는 모습 (시간 분할).
- 병렬성: 여러 테이블에서 각자 동시에 일하는 모습 (물리적으로 동시에).
- 동기: A가 끝나야 B가 시작 (순서대로 진행).
- 비동기: A가 끝나지 않아도 B가 진행 가능 (병렬로 진행).
- 블로킹: A가 끝날 때까지 기다림.
- 논블로킹: A를 기다리지 않고 다른 작업 진행.
- 스레드/프로세스: 스레드는 같은 공간에서 협업, 프로세스는 각자 공간에서 작업.
- 코루틴: 스레드보다 가볍게 번갈아가며 일하는 작업자.
블로킹/논블로킹/동기/비동기
- 블로킹/논블로킹: 호출자가 작업이 끝날 때까지 대기하는지 여부에 초점.
- 동기/비동기: 작업의 흐름과 순서를 제어하는 방식에 초점.
- 이 둘은 상호 배타적이지 않으며, 조합되어 사용될 수 있습니다. 예를 들어, 비동기적으로 작업을 처리하면서도 블로킹 호출이 발생할 수 있고, 동기적 작업 흐름에서도 논블로킹 방식이 활용될 수 있다.

Leave a Comment