이론적인 관점보다는, 프로그래밍 관점에서 살펴보자
sync, async I/O부터 비교해보면 async I/O는 CPU 작업과 I/O 작업을 병렬처리 할수있는 방식이다
대표적인 예제는 서브스레드에서 I/O 작업이 완료되면 callback을 호출하는 형식이다
이렇게 하면 메인스레드에서는 I/O 작업동안 UI 처리와 같은 CPU 작업을 할수있다
반대로 sync I/O의 대표적인 예제는 싱글스레드에서 CPU 작업과 I/O 작업을 모두 처리하는 형식이다
즉 I/O 작업이 끝날 때까지 기다려야 한다
이럴 경우, 유저 입장에서는 프로그램이 멈춘 것처럼 보이게 된다
이러한 이유로 Android Java에서는 메인스레드에서 Network I/O 작업을 할수없다
Network I/O 통신 중이라 하더라도 UI가 멈추지않게 하기 위함이다
그래서 Retrofit2 라이브러리를 메인스레드에서 사용할 때는 async request를 사용한다
결과적으로 async I/O는 I/O 작업과 CPU 작업을 동시에 처리할수 있게 된다
MSDN에서 Async Client/Server Socket을 C# Callback으로 구현한 예제를 제공하고 있다
https://msdn.microsoft.com/ko-kr/library/bew39x2a(v=vs.110).aspx
https://msdn.microsoft.com/ko-kr/library/fx6588te(v=vs.110).aspx
이제 blocking, non-blocking I/O를 비교해보자
이것은 socket에 직접 option을 설정해주는 형식으로 구현된다
blocking일 경우 recv/send/connect/accept system call이 완료될 때까지 blocking되었다가 완료되면 return된다
non-blocking일 경우 반대로 system call이 완료되지 않아도 즉시 return된다 (blocking되지 않는다)
그 결과, non-blocking일 경우 I/O 작업 중간에 CPU 작업을 처리할수 있게된다
async I/O도 마찬가지지만 non-blocking으로 구현할 경우 프로그램 구조가 복잡해진다는 단점이 있다
------
I/O 방식은 대표적으로 멀티클라이언트를 처리하기 위해서 선택되는데, 아래는 멀티클라이언트를 처리하기위한 여러가지 구조에 대한 설명이다
가장 간단한 I/O 구조는 1:1 통신만 가능하다
멀티클라이언트(1:N) I/O를 처리하기 위한 가장 간단한 방법은 멀티스레딩이다
하지만 이것은 메모리 오버헤드가 크게 증가한다는 단점이 있다
Apache 서버가 멀티스레딩 방식인데, 메모리에 의해 최대 동시 연결갯수가 제한된다
그래서 Nginx, Node.js 서버는 멀티스레딩 방식을 사용하지 않는다. 자세한 내용은 async 방식에서 언급한다
메모리 오버헤드를 극복하기 위해, 싱글스레드에서 멀티클라이언트를 처리하기 위한 방법 중 첫번째는 I/O Multiplexing이다
대표적인 것이 select(), poll() system call인데, (시스템 콜: 유저 권한으로 할수없는 작업을 커널에 요청하기 위한 인터페이스) select()를 설명하자면
blocking된 상태로 fd table (socket table)을 관찰하면서 1개 이상의 socket이 "ready" 상태가 될 경우 reutrn한다 (이 과정을 loop로 반복)
select()는 synchronous I/O multiplexing API이다. 그 내용은 linux select man page에서 확인할 수 있다
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O multiplexing
select()의 가장 큰 문제점은 클라이언트들이 동시에 접속할 경우, 다른 클라이언트의 처리완료를 기다려야한다는 것이다 (싱글스레드에서 모든 요청과 I/O를 처리하기 때문에)
그래서 select()는 작업단위가 작은 서버에 적합하다
이 외의 단점은 CPU 오버헤드 증가(매 loop마다 fd table을 검사해야 하므로)가 있다
멀티클라이언트 처리에 non-blocking 방식도 사용할수 있다
우선 이 방식은 구조가 복잡하다는 단점이 있다
기존 방식과의 차이점은, socket 생성 후 non-blocking 옵션을 세팅해주는 과정이 추가되고, I/O system call을 1번이 아니라 loop를 통해 여러번 호출하게 된다
I/O 함수가 즉시 리턴되므로 또다른 작업을 할수있게 된다. 즉 blocking되지 않으므로 다른 클라이언트에 대해 I/O를 처리할수 있다는 의미이다
정리하여 non-blocking socket의 단점으로는 복잡한 구조와 CPU 오버헤드 증가 (I/O 작업이 일어나지 않았더라도 계속 loop를 돌며 확인하므로)를 들수 있겠다
마지막으로 async 방식은 곧 callback이라고 생각해도 큰 문제 없을 듯하다
아까 언급했던 Nginx Node.js이 바로 async 방식의 서버로서, I/O 요청 후 작업이 완료되면 callback이 호출된다
그리하여 I/O 작업이 이뤄지는 와중에도 I/O 요청들은 딜레이없이 처리할 수 있게된다
아래 참고사이트들을 살펴보면, Apache 서버와 Node.js 서버를 비교했을 때 스레드를 생성하지 않으므로
훨씬더 많은 클라이언트 요청을 처리할 수 있다고 설명한다 (= 확장성이 뛰어나다)
---
각 I/O 방식의 구조, 장단점을 설명하였고 멀티클라이언트 처리 관점에서 I/O의 다양한 구현들을 설명하였다
async와 non-blocking 개념이 헷갈려서 쓰게 되었고, 이 둘은 굉장히 많이 혼동하는 개념이므로 정확하게 이해해둘 필요가 있다
좀더 쉽게 설명하기 위해 구현 관점에서 설명해보았다
(글을 여러번 수정하면서 장황해졌는데 이후에 다시 간단하게 정리해야겠다)
Threading is about workers; asynchrony is about tasks
참고:
http://www.tutorialspoint.com/unix_system_calls/_newselect.htm
http://ozt88.tistory.com/20
http://www.nextree.co.kr/p7292/
https://qkraudghgh.github.io/node/2016/10/23/node-async.html
http://la-stranger.tistory.com/m/entry/understanding-node-js-event-loop
https://stackoverflow.com/questions/34680985/what-is-the-difference-between-asynchronous-programming-and-multithreading
'etc' 카테고리의 다른 글
[Database] redis docker & python redis 설치하기 (0) | 2017.12.07 |
---|---|
FFmpeg vs Libav 차이점 (0) | 2017.11.29 |
FFmpeg Camera, Mic 스트리밍 프로젝트 경험담 (0) | 2017.11.25 |
윈도우에 caffe2 설치 (on docker) (0) | 2017.11.25 |
티스토리 - 네이버 웹마스터 도구 연동 (0) | 2015.11.19 |
WRITTEN BY
- hojongs
블로그 옮겼습니다 https://hojongs.github.io/