여러분이 식당의 웨이터라고 생각해 봅시다. 장사가 꽤 잘되는 식당이라 손님들이 슬슬 들어 옵니다. 자 그럼 이제 주문을 받으러 가야 할 것입니다. 그런데 첫 번째 손님에게 주문을 받고 이를 한 요리사에게 주문에 맞는 요리를 요청합니다. 그리고 요리가 나올 때까지 기다립니다. 1, 2, .. 10분 그리고 요리가 나오면 첫 번째 손님에게 요리를 드리고 두 번째 손님의 주문을 받습니다


 그런데 이렇게 주문을 받고 요리를 전달하니 너무나 비효율적입니다. 이렇게 일하다가는 바로 짤릴겁니다. 여러분들도 느끼셨을 테지만 요리사가 요리를 할 때 기다릴 필요가 없기 때문입니다. 그러면 어떻게 하면 될까요? 첫 번째 손님에게 주문을 받고 한 요리사에게 요리를 해달라고 요청합니다. 그리고 두 번째 손님의 주문을 받고 다른 한 요리사에게 요리를 해달라고 요청합니다. 이런 식으로 10명의 손님의 주문을 받았다고 가정해 봅시다. 그리고 어떤 요리사에게 요청했는지 표를 가지고 있습니다. 그리고 나서 표를 보고 요리사 1번부터 10번까지 한 명씩 돌면서 요리사님! 요리가 다 끝났나요?” 라고 물어보는 겁니다. “아직!”이라는 답변이 온다면 다음 요리사에게 갑니다. 이렇게 10명의 요리사를 순서대로 돌면서 물어봅니다. 그러다가 요리가 다 끝났다!” 라고 말하는 요리사의 음식을 고객에게 드리면 됩니다.



 그런데 이것 역시 효율적이지 못합니다. 이렇게 일했다가는 요리사들에게 미친놈 소리를 들을 수도 있고 아마 지쳐서 나가 떨어질 것입니다. 그럼 다른 방법이 없을까요? 요리사에게 이렇게 요청하는 겁니다. “요리사님 요리가 다 끝나면 종을 쳐주세요.” 라고 말이죠. 그러면 요리사는 요리가 끝나는 대로 종을 칠 것이고 거기로 가서 음식을 고객에게 전달하면 될 것입니다.



이것이 바로 Blocking(블록킹) I/O 모델, Non-blocking(넌블록킹) I/O 모델, Asynchronous(비동기) I/O 모델 개념입니다. Application(프로그램)I/O를 처리하지 못합니다. 마치 웨이터가 음식을 만들 수 없는 것처럼 말이죠. 그래서 Kernel에게 요청을 해야 합니다. 그리고 만약 kernel에서 I/O처리를 마칠 때까지 Application에서 기다리면 이것이 Blocking I/O 입니다.



 하지만 이것은 이 방법은 너무나 비효율적임을 우리는 봤습니다. 그래서 넌블록킹 함수를 호출하면 넌블록킹 함수는 바로 리턴을 해줍니다. 그런 다음 코드상에서는 루프(loop)를 돌면서 작업이 모두 끝났는지 물어봅니다. 만약 끝나지 않았다면 에러 값을 리턴합니다. ! 아직 작업이 끝나지 않았을 때의 에러 리턴 값이 다릅니다. 그리고 작업이 모두 끝났을 땐 그에 맞는 처리를 해주면 됩니다. 하지만 여기에는 busy waiting이 생겨서 CPU 점유율을 많이 차지하게 될 것입니다. 그래서 loop 마다 sleep 을 해주어야 합니다

참고로 멀티쓰레드를 공부해 보셨던 분들은 멀티쓰레드로 처리하면 되지 않나요? 라고 질문할 수 있습니다. 네 맞습니다. 멀티쓰레드를 사용해도 됩니다. 다만 요청이 많아 지면 content swiching이 빈번하게 발생하게 되고 이 역시 성능을 떨어트리는 요인이 됩니다.



 그렇다면 비동기 처리는 어떻게 되는 것일까요? 넌블록킹과는 다르게 kernel에 요청을 한 뒤 끝나면 어떤 것을 하라고 하는 event 혹은 callback 함수를 등록합니다. 그러면 넌블록킹처럼 지속적으로 검사를 하는 것이 아니라 작업이 끝나는 대로 등록된 event 혹은 callback 함수가 호출 됩니다. 마치 요리가 끝나면 벨을 눌러줬던 것처럼 말이죠


+ Recent posts