동기/비동기, 블로킹/논블로킹은 관점의 차이
동기(Synchronous) / 비동기(Asynchronous) : 순서, 상황, 필요성
- 순서가 필요하면 동기 / 함수의 반환값이 필요하여 블럭/논블럭이던 대기하고 있으면 동기.
- 순서 상관없으면(독립적) 비동기 / 함수를 부르고 하던 일 하다가(병렬적), 콜백함수를 통해 결과값을 전달받으면 비동기.
동기화 함수 : 독립된 스레드/프로세스간에 동기성(순서)를 가질수 있게 하는 함수들 ->
블로킹(Blocking)/논블로킹(Nonblocking) : 제어권, 동작, I/O
- 블로킹 :제어권이 넘어가서 호출한 스레드는 대기(BLOCK)하는 것.
- 논블로킹 :제어권을 그대로 가지고 있음. 함수를 호출한 프로세스/스레드는 다음 코드로 진행하고, 호출된 함수는 병렬적으로 동작.
동기/비동기, 블로킹/넌블로킹 실제
read()함수로 데이터를 읽어서 파싱하는 코드
- I/O로부터 데이터를 읽어올때까지 기다리므로 블로킹
- 데이터를 받아와야 이후 파싱이 가능하므로 동기적인 코드
write()함수로 처리한 데이터를 파일에 입력.
- 통상적인 write()는 쓰기 작업이 완료될때까지 block된다.
- 그러나 쓰고자하는 fd가 non-blocking모드라면 write()또한 non-blocking으로 동작한다.
- 또한 block/non-block과는 별개로 쓰기 완료한 코드는 이후 동작에 영향을 미치지 않으므로 비동기적이다.
비동기와 멀티스레딩 동기/비동기는 작업의 흐름에 관한 관점. 스레딩은 작업자의 개수에 따른 관점.
- 동기 단일 스레드: 하나의 프로세서가 순차적으로 진행함.
- 동기 멀티 스레드: 여러 프로세서가 협업/대기 하면서 순차적으로 진행함.
- 비동기 단일 스레드 : 하나의 프로세서가 컨택스 스위칭을 하며 여러 작업을 동시에 수행하는거처럼 보이게 하는 것.
- 비동기 멀티 스레드 : 여러 프로세서가 각자 작업을 수행하며 병렬적으로 작업을 수행하는 것.
non-blocking과 멀티플랙싱
selec
select와 write buffer
WSAAsyncSelect흐름 : 콜백함수 호출. 각종 초기화(윈도우 클래스 등록 및 생성, 윈속초기화, 리슨소켓 생성) -> 바인드 -> 리슨 -> 셀렉트 -> 메세지 루프에서 무한 반복.
WSAEventSelect 윈속 초기화 -> 소켓 생성(비동기) -> 바인드 -> 리슨 -> wsaeventselect(소켓과 이벤트 연결) -> 이벤트대기(비동기) -> 이벤트 확인(FD_…) -> 각 이벤트에 맞는 처리
공통점 : 소켓들의 정보를 따로 자료구조에 담아서 처리해야 하는데, 각 함수들은 이를 수행하지 않으니 알아서 추가 및 삭제해야함.
멘토링
1. 책에서 소개한 여러 select모델들이 있는데, 일반 select나 IOCP만 사용하는 이유
WSAAsyncSelect나 WSAEventselect 둘다 내부적으로 Select를 사용하고 있고, 이를 쉽게 쓰기 위한 함수들이다. 차라리 Select를 멀티스래딩해서 쓰는게 호환성에서도 좋고 성능도 큰 차이가 안난다.
2. 소켓 버퍼
윈도우 7이전에는 소캣 버퍼 크기가 정적이여서 부족한경우가 많았고, 이를 소켓 생성시 설정해주었다(특히 send). 하지만 요즘은 OS가 동적으로 관리해줘서, 버퍼 크기를 유저가 설정해버리면 자원 낭비가 될 가능성이 크다.
3. 소켓 모델 선택
- 동기 모델(select) : 사용/구현/유지보수에 중점
- 비동기 모델(IOCP) : 성능 중점 비동기 모델이 성능은 좋은건 맞지만, 비동기 모델은 멀티스레딩이 동반되므로 간단한거에도 IOCP를 써버리면 닭잡는데 소잡는칼 써버릴수도 있다. 대다수의 서버는 select로도 충분히 구현 가능하고, 최근에 HW가 좋아서 충분히 커버 가능하다.(근데 이런 말을 신입이 면접에서 말하면 건방저 보이거나 회피성 대답으로 보일수 있을거 같다…)
하지만 게임 서버 프로그래밍에서는 성능이 최우선이고, 업게 전반적인 분위기도 그렇고 IOCP를 쓰는게 거의 무조건 좋다고 한다(선택한건데 모르는거라고 오해할수도 있다고…)
리눅스쪽은 epoll을 많이 쓴다. 최근에 io_uring이 나왔지만, 리눅스쪽은 서버 HW의 버전 업데이트가 늦는 편이므로 아직은 epoll이 적합하다.
4. udp는 select같은 방식으로 처리할 필요가 없는가?
udp는 연결 개념이 없으므로 단일 소켓으로 처리해도 충분하다.
5. select에서 write_set
select사용시 write_set이 true가 되는 경우는 소켓 송신 버퍼가 비어있을때 이다. 하지만 대부분의 경우 해당 버퍼는 비어있으므로 확인을 안한다. 하지만 확인을 해야하는 경우는 다음과 같다.
- send()반환값이 보내야할 값보다 적은 경우, 버퍼가 다 차있어서 다 못 보낸 경우 이므로, 이때만 write_set을 select가 확인하게 한다. 하지만 send()버퍼가 꽉 차있다는 의미는, 상대가 통신을 잘 못받거나 끊어졌다는 의미이다. 이때 계속 느리게라도 계속 데이터를 보낼지(write_set 확인), 통신을 끊어버릴지는 상황에 따라서.