반응형

본 글은 책 컴퓨터 밑바닥의 비밀  2.6 장 내용을 요약한 내용입니다.

고된 프로그래머 예시를 통한 동기/비동기 비교

동기 요청(왼쪽)과 비동기 요청(오른쪽)의 예

 
  • 상사가 프로그래머에게 일을 시킬 때, 옆에서 계속 기다리고 있으면 동기, 일을 시키고 다른 일을 하면 비동기라고 예를 들어 설명

또 다른 예로 전화통화와 이메일을 비교해보면,

  • 전화통화 : 상대방이 말할 때 들으며 기다려야 함(동기)
  • 이메일: 작성하는 동안 다른 사람들은 다른 일 처리 가능(비동기)

 

동기 호출

  • 일반적인 함수 호출
funcA()
{
	// funcB 함수가 완료될 때까지 기다림
	funcB();
	
	// funcB 함수는 프로세스를 반환하고 계속 진행
}

funcA와 funcB 함수가 동일한 스레드에서 실행됨

입출력 작업의 경우,

...
read(file, buf); // 여기에서 실행이 중지됨
...
// 파일 읽기가 완료될 때가지 기다렸다가 계속 실행함
  • 최하단 계층은 실제로 system call로 운영체제에 요청을 보냄
    • 파일 읽기 작업을 위해 read 호출 스레드를 일시 중지하고, 커널이 디스크 내용을 읽어 오면 일시 중지 되었던 스레드가 다시 깨어남 (Blocking input/output 이라고 함)

Blocking input/output

 

비동기 호출

  • 디스크의 파일 읽고 쓰기, 네트워크 IO, 데이터 베이스 작업처럼 시간이 많이 걸리는 입출력 작업을 백그라운드 형태로 실행
read(file, buf); // 여기에서 실행이 중지되지 않고 즉시 반환
// 이후 내용의 실행을 블로킹하지 않고 바로 
  • read 함수가 비동기 호출되면 파일 읽기 작업이 완료되지 않은 상태에서도 read 함수는 즉시 반환될 수 있음

  • 호출자가 블로킹 되지 않고 read 함수가 즉시 반환되기 때문에 다음 작업을 실행할 수 있음

 

그럼 비동기 호출에서 파일 읽기 작업이 언제 완료되었는지 어떻게 알 수 있을까? 이 경우, 처리에 대한 2가지 상황이 있을 수 있음

  1. 호출자가 실행 결과를 전혀 신경쓰지 않을 때
  2. 호출자가 실행 결과를 반드시 알아야 할 때

 

1. 호출자가 실행 결과를 전혀 신경쓰지 않을 때

void handler(void* buf)
{
	... // 파일 내용 처리 중
}

read(buf, handler)

"계속해서 파일을 읽고, 작업이 완료되면 전달된 함수(handler)를 이용해 파일을 처리해주세요."
⇒ 파일 내용은 호출자 스레드가 아닌 콜백 함수가 실행되는 다른 스레드(호출되는 스레드) 또는 프로세스 등에서 처리

2. 호출자가 실행 결과를 반드시 알아야 할 때

  • notification 작동 방식을 사용하는 것
    • 작업 실행이 완료되면 호출자에게 작업 완료를 알리는 신호나 메시지를 보내는 것
    • 결과 처리는 이전과 마찬가지로 호출 스레드에서 함

 

웹 서버에서 동기와 비동기 작업

아래의 작업을 한다고 가정

  • A, B, C 세 단계를 거친 후 데이터 베이스를 요청
  • 데이터 베이스 요청 처리가 완료 되면 D, E, F 세 단계를 거침
  • A, B, C, D, E, F 단계에는 입출력 작업이 포함되어 있지 않음
// 사용자 요청을 하는 단계
A;
B;
C;
데이터 베이스 요청;
D;
E;
F;

 

먼저 가장 일반적인 동기 처리 방식

// 메인 스레드
main_thread()
{
	while(1)
	{
		요청 수신;
		A;
		B;
		C;
		데이터베이스 요청을 전송하고 결과가 반환될 때까지 대기;
		D;
		E;
		F;
		결과 반환;
	}
}

// 데이터베이스 스레드
database_thread()
{
	while(1)
	{
		요청 수신;
		데이터베이스 처리;
		결과 반환;
	}
}
  • 데이터 베이스 요청 후 주 스레드가 블로킹 되어 일시 중지됨
  • 데이터 베이스 처리가 완료된 시점에서 D, E, F가 계속 실행됨

  • 주 스레드에서 빈공간은 유휴 시간(idle time)으로 기다리는 과정
  • 유휴 시간을 줄이기 위해서 비동기 작업을 활용할 수 있다

 

1. 주 스레드가 데이터 베이스 처리 결과를 전혀 신경 쓰지 않을 때

  • 주 스레드는 데이터 베이스 처리 완료 여부 상관하지 않음
  • 데이터베이스 스레드가 D, E, F 세 단계를 자체적으로 직접 처리
  • 데이터 베이스 처리 후 DB 스레드가 D, E, F 세 단계를 알 수 있는 방법은?
    • 콜백 함수
void handle_DEF_after_DB_query()
{
	D;
	E;
	F;
}

주 쓰레드가 데이터베이스 처리 요청을 보낼 때 위 함수를 매개변수로 전달

DB_query(request, handle_DEF_after_DB_query);
  • 데이터 베이스 쓰레드는 데이터 베이스 요청을 처리한 후 handle_DEF_after_DB_query 함수 호출하기만 하면 됨

이 함수를 데이터 베이스 쓰레드에 정의하고 직접 호출하는 대신 콜백 함수를 통해 전달받아 실행하는 이유은?

⇒ 소프트웨어 조직 구조 관점에서 볼 때 데이터베이스 쓰레드에서 해야 할 작업이 아니기 때문

 

2. 주 쓰레드가 DB 작업 결과에 관심을 가질 때

  • 알림 작동 방식을 이용하여 작업 결과를 주 스레드로 전송
  • 주 스레드는 사용자 요청의 후반부를 계속 처리
  • 주 스레드에는 유휴시간이 없음
    • 비 동기 호출만큼 극단적으로 효율적이진 않지만 동기 호출에 비하면 여전히 효율적
반응형

'OS' 카테고리의 다른 글

Event loop와 coroutine  (0) 2024.08.11
블로킹/논블로킹  (0) 2024.08.10
파일 시스템  (0) 2024.07.28
가상 메모리(Virtual memory)  (0) 2024.07.28
교착상태(Deadlock)  (0) 2024.07.19

+ Recent posts