최근에 네트워크 서버를 개발하면서, 이에 따른 멀티스레드 구현을 하면서 몇가지 정리~

1 대의 서버가 N 개의 클라이언트들을 상대로 서비스를 하다보니 결국 멀티스레드로 구현하게 되었다. 네트워크 프로그래밍을 함에 있어서 스레드 사용은 필수라는 생각이 든다.

멀티 스레드에서 조심할 것은 하나의 자원에 대해서 여러개의 스레드가 동시에 접근하지 못하도록 제한을 걸어야 하는 것이다. 하나의 자원에 대해 두개 이상의 스레드가 동시에 접근할때 바로 OS 시간에 배웠던 Race Condition 상태가 발생할 수 있는 것인데, 보통 이를 해결하기 위해서 Mutex, Critical Section ( Critical Section 을 우리나라 말로는 "임계 영역" 이라고 하고, 일본에서는 "위험역(危險域) 이라고 한다는군), Semaphore 등을 사용 한다. 

Mutex 와 Critical Section 의 차이는  서로 다른 프로세스 간의 동기화도 지원하는 Mutex 와 달리 Critical Section 은 하나의 프로세스 내에서만 사용할 수 있다는 점이다. 그래서 보통 Mutex 는 프로그램의 중복 실행을 방지하는 용도로 많이 쓴다. Semaphore 는 뮤텍스와 비슷한데, 접근 가능한 스레드의 갯수를 지정할 수 있다는 점이 다르다.

외부에서 사용되는 변수를 스레드 내에서 변경하는 행위 등도 상당히 조심해야 한다. 특히 이 변수들이 스레드 외부 다른 곳에서 수정이 발생하는 경우 값이 꼬일 가능성이 커지므로 주의해야 한다.  이런 실수를 종종 하게 되는 이유는 스레드가 생성되는 순간부터 메인 프로세스 외에 스레드 함수가 병렬적으로 실행되고 있다는 것을 늘 머리속에 염두를 해 두어야 하는데, 사실 인간의 직관이 병렬 처리라는 개념으로 코딩하는게 쉽지 않기 때문이다.

서버와 클라이언트가 연결을 맺고 파일 전송과 같은 sequential 한 작업을 진행하는 도중에는 이 작업의 atomicity( DB 에서 트랜젝션 처리등에서 말하는 "원자성" 이다 ) 를 보존해 주는 처리가 있어야 한다. 예를 들면 파일 전송이 끝나지 않은 상태에서 또다른 파일을 열어서 같은 소켓으로 전송을 시도하려고 하는 경우등에 에러가 발생할 소지가 있을 수 있다.
 
여러 스레드가 동시에 파일을 읽는 경우, 동시에 파일을 쓰는 경우 등에 대해서도 잘 고려해 봐야 한다.
여기에 대해서는 친구가 좋은 팁을 알려줬는데, 일단 메모리상에 파일 버퍼에서 파일들을 저장해 놓고 있다가 큐에 일정 용량이 쌓이거나 일정 주기가 되면 그때마다 file write 를 하라는 것이다. client 가 주는대로 서버에서 그때마다 스레드 생성해서 각각이 file open 해서 파일을 쓰는 경우 상당히 퍼포먼스 측면에서 부담이 커진다. 그런데 현재 테스트 해 본 바로는 파일 크기가 그리 크지 않은 경우 ( 수백 kb 정도 ) 에서는 동시에 여러개 ( n < 10 ) 의 스레드로 파일 쓰기를 반복 시도 하여도 체감상 느껴지는 부하는 없었다.

지금은 최대한 심플하게 만들기 위해서 간단하게 비동기 소켓으로 만들고 있지만 보통 이렇게 다중으로 file write 를 해야 하는 경우 IOCP 를 많이 쓰는 것 같다. 이쪽은 나중에 시간나면 더 찾아봐야 겠다.

어쨌든 네트워크 프로그래밍은 어렵다. 일단 개발 및 테스트를 위해 장비가 최소 2 ~ n 개가 준비되어야 하고, 네트워크가 일시적으로 단절되는 경우 등 통신 상에서 필요한 여러가지 예외처리를 해 줘야 하는 부분들이 있다. 역시 많은 노하우가 필요하다고 생각된다.


  1. BlogIcon Hyperdash 2009.12.28 10:22

    서버에서 파일 쓰기를 할 경우에는 락을 거는것과 동일한 효과이기 때문에

    정확한 성능 테스트를 해보려면 테스트 클라이언트 천개정도 붙여서 지속적인 파일 쓰기 요청을 해본후

    응답시간이 어느정도인지 확인하는 것이 좋을 듯하다. 같은 네트워크에서 20ms 가 넘어간다면 딜레이가 발생한다는 뜻...

    • BlogIcon soyoja 2009.12.29 16:08 신고

      관리해야 하는 전체 client 숫자가 많기는 한데, 동시에 서버와 데이터를 주고 받는 client 의 숫자는 몇개 안되는 상황이 되어서 구현이 엄청 쉬워졌다...

  2. 김훈동 2009.12.30 15:12

    나 병특할때 그러니까 2000년대 초 쯤에는 , IOCP 니 Overlapped IO 니 그런걸로다 대용량 네트워크 서버를 많이 만들곤 했던 기억이 있당…
    2003년 이후부터는 ACE 프레임워크 같은걸 많이 썼었지.... 대용량 서버를 직접 만들고 최적화 하는 곳은 일부 게임회사등을 제외하고는 많지 않았던 듯.... windows nt 계열 서버를 쓰는 데는 거의 IOCP 를 많이 썻고, 유닉스 나 리눅스 쓰는 데는 게임회사들도 ACE 를 많이 썼었는데.. 그러다가 ACE 가 운영체제마다 최적화된 메커니즘이 아닌지라 ASIO 라는 프레임워크로 또 나중엔 대세가 넘어가는거 같기도 했고.(요즘유행은 잘 모르겠음. 게임업계를 떠난지가 언 5년이 다되가니..) ASIO 는 ACE 와 달리 운영체제마다 최적화된 메커니즘으로 되어 있어서 windows nt 계열에서는 IOCP 를 쓴다더군... ASIO 는 안써봤지만.. ACE 를 쓰면서 느낀점은....역시.... 공부해가면서 내가 직접 짜는 것 보다는 검증된 Open 소스도 잘쓰면 업계 평균 이상은 된다는 것...

    나같은 경우는 회사에선 걍 WCF 나 웹서비스로다가 몇줄만에 IOCP 로 하던 코딩을 끝네고 대용량 다중 접속 처리를 웹서버에다가 떠넘기고 있지만.... (WCF 가 좋은 점은 웹서비스로 가면 독립 어플리케이션은 양방향 통신에서 자체가 웹서버 역할은 못하니까 클라이언트 역할 밖에 못하는데.... WCF 는 웹서버 없는 클라이언트가 자체적으로도 웹서버 처럼 웹서비스 호스팅 기능을 가지고 있다는 것...)

    3년여 전부터 금융권에 몸담으면서 네트워크 코딩이 극명하게 두가지로 나뉘었는데..한가지는 편하면서 범용적으로 코딩하는 경우와 또 한가지는 실시간 계좌이체 처럼 속도와 안정성이 대단히 민감한경우... 전자는 자바등 레거시와의 연동이슈 때문에 최근 1~2년 사이에는 소켓통신 거의 안쓰고 SOAP 이나 REST, JSON 같은거 쓰고 있고.. 후자는 Tmax 나 Tuxedo 등 상용 미들웨어를 쓴다는점... 게임업계처럼 오픈소스나 자체 제작을 안해서 요즘은 IOCP 구경 할 일이 거의 없어서 IOCP 는 아련한 기억으로만 남아있당…

    • BlogIcon soyoja 2010.01.08 19:03 신고

      응... 다음에 기회가 되면 닷넷으로 서버를 구현해 보고 싶다.

+ Recent posts