[Backend] blocking/non blocking/sync/async socket io

2 minute read

Blocking

I/O 작업은 유저레벨에서 직접 수행할 수 없다. 실제 I/O를 수행하는것은 커널레벨에서만 가능하다. 따라서 유저 프로세스(또는 쓰레드)는 커널에게 I/O를 요청해야한다. I/O에서 블로킹 형태의 작업은 유저 프로세스가 커널에게 I/O를 요청하는 함수를 호출하고, 커널이 작업을 완료되면 함수가 작업 결과를 반환한다.

backend-8

만약 여러 클라이언트가 접속하는 서버를 블로킹방식으로 구현한다고 가정해보자. I/O작업이 blocking 방식으로 구현되면 하나의 클라이언트가 I/O작업을 진행하면 해당 프로세스(또는 쓰레드)가 진행하는 작업을 중지하게 된다. 따라서 다른 클라이언트의 작업에 영향을 미치지 않게 하기 위해서 클라이언트 별로 별도의 쓰레드를 만들어 연결시켜주어야 할 것이다. 그러면 쓰레드 수는 접속자 수가 많아질 수록 엄청나게 많아지게 된다. 쓰레드가 많으면 CPU의 컨텍스트 스위칭 횟수가 증가할 것이며, 이때 사용되는 컨텍스트 스위칭 비용 때문에, 실제 작업하는 양에 비하여 훨씬 비효율적으로 동작하게 될 것이다.

Non-Blocking

Blocking 방식의 비효율성을 극복하고자 만들어진 것이 Non-Blocking 방식이다. Non-Blocking은 I/O작업을 진행하는 동안 유저 프로세스의 작업을 중단시키지 않는다. 유저 프로세스가 커널에게 I/O를 요청하는 함수를 호출하면, 함수는 I/O를 요청한 다음 진행상황과 상관없이 바로 결과를 반환한다.

backend-9

위 그림은 Non-Blocking 방식으로 구현된 I/O의 대표적인 사례를 잘 보여준다. 유저 프로세스는 recvfrom함수를 호출하여 커널에게 해당 소켓으로부터 데이터를 받아오고 싶다고 요청하고 있다. 커널은 이 요청에 대해서 상대방의 데이터를 전송 받아서 recvBuffer에 저장하고, 유저에게 그 내용을 복사해줘야 한다. 상대방으로 부터 데이터를 받는 중에 recvBuffer가 비어있다면 유저 프로세스가 커널에게 받아올 수 있는 정보는 없다. 따라서 recvfrom 함수는 아직 작업 진행중이란 의미로 “EWOULDBLOCK”을 리턴한다. 이 결과를 받은 유저 프로세스는 다른 작업을 진행할 수 있다. 만약 recvBuffer에 유저가 받을 수 있는 데이터가 있다면, 버퍼로 부터 데이터를 복사하여 받아온다. recvBuffer는 커널이 가지고 있는 메모리에 적재되어 있으므로 메모리간 복사가 일어나 I/O보다 훨씬 빠른 속도로 데이터를 받아올 수 있다. 이때 recvfrom함수는 빠른 속도로 읽을 수 있는 데이터를 복사해주고 복사한 데이터의 길이와 함께 반환한다. 위의 모든 반환이 I/O의 진행시간과는 관계없이 빠르게 동작하기 때문에, 유저 프로세스는 자신의 작업을 오랜시간 중지하지 않고도 I/O 처리를 수행할 수 있다.

Socket에서 Non-Blocking 구현

통지모델

동기(synchronous)와 비동기(asynchronous)는 서로 메시지를 주고받는 상대방이 어떤 방식으로 통신을 하는가에 대한 개념이다. I/O 통지모델에서 대화하는 주체들은 커널과 프로세스이다. 프로세스는 커널에게 I/O처리를 요청하고,커널은 프로세스에게 I/O 상황을 통지한다. 우선 I/O 요청은 반드시 동일하게 처리될 수 밖에 없는 부분이고, 결국에 커널이 프로세스에게 어떤 방식으로 통지하느냐에 따라 동기형이냐 비동기형이 결정될 것이다.

동기형 통지모델

동기형 통지모델의 프로세스는 커널에게 지속적으로 현재 I/O 준비 상황을 체크한다. 즉 커널이 준비되었는지를 계속 확인하여 동기화 하는 것이다. 따라서 동기형 통지모델에서 Notify를 적극적으로 진행하는 주체는 유저의 프로세스가 되며 커널은 수동적으로 유저 프로세스의 요청에 따라 현재의 상황을 보고한다.

select와 epoll 모두 프로세스가 커널에게 지속적으로 I/O 상황을 체크하여 동기화 하는 개념은 여전히 유효하기 때문에 동기형 통지모델이다. (blocking/non-blocking은 timeout에 의해서 결정되는 부분이다.)

비동기형 통지모델

이와 반대로 비동기형 통지모델은 일단 커널에게 I/O작업을 맡기면 커널의 작업 진행사항에 대해서 프로세스가 인지할 필요가 없는 상황을 말한다. 유저의 프로세스가 I/O 동기화를 신경쓸 필요가 없기에 비동기형이라고 부를 수 있다. 따라서 비동기형 통지모델에서 Notify의 적극적인 주체는 커널이 되며, 유저 프로세스는 수동적인 입장에서 자신이 할일을 하다가 통지가 오면 그때 I/O 처리를 하게 된다.

Reference