팀원들에게 쓰레드 설명하기
동시성 프로그래밍 빌드업 치는데 필요한 내용이다. CS:APP의 11장-12장 사이에 있는 Tiny Web Server + Proxy-Lab 진도 사이에서 다뤄 질 수 있는 문제이다. Windows, MacOS, ctOS, Woo-PINT-OS lets go
2025.05.03 - [구현하기] - Tiny Web Server 개발 기록 #1
Tiny Web Server 개발 기록 #1
2025.05.02 - [분류 전체보기] - [CS:APP] 11 : 웹 서버 [CS:APP] 11 : 웹 서버모든 네트워크 애플리케이션은 클라이언트-서버 모델을 기반으로 한다.여기서 이야기하는 모델에 따르면, 애플리케이션은 한
hyeonistic.tistory.com
기본적인 echo는 끝났겠거니, 이번엔 동시성 프로그래밍 버전의 echo를 알아봅시다.
echo, tiny 하고 proxy 할텐데, proxy의 전체 단계 중 2단계 구현에서 필요한 내용입니다.
- 1단계는 echo, tiny 총 동원하면 어찌어찌 될 것으로 보입니다. (나도 아직 안해봄)
- 3단계 많이 어려워보이는데 2단계까진 할만해
2단계 구현은 12장을 읽어야 어느정도 구체화를 할 수 있는데 그 내용을 간략하게 볼게요.
우선 우리는 echo로 한 서버와 클라이언트를 만드는 경험을 할 수 있었다. 테스트 환경은 작동확인만 했지만, 진짜로 서버의 의의를 다하기 위해서는 클라이언트가 복수형이어도 가능해야겠지?..
- 근데 안됨. 이 echo 코드는 복수의 클라이언트를 당장 처리 시켜줄수가 없다.
- [시연을 해서 차순위 클라이언트가 연결은 된것같아도 서비스 활용이 불가능한걸 보여주기]
- 즉, 처음에 들어온 클라이언트가 다 끝나야 차순위로 들어온 클라이언트에게 서버의 컨텐츠인 echo를 이용 할 수 있게 된다는 것이다.
와 그럼 이런걸 서버라 하기엔 좀 하자가 있다. 그럼 동시 접속의 개념을 만들려면 무얼 해야 할까?
서버 역할을 할 수 있는 걸 사람 수 마다 만들어주면 되겠다. 여기서 동시성 프로그래밍을 논하게 된다.
접속을 하려는 클라이언트마다 자식 프로세스를 생성해서 1:1 대응을 시켜주는 것이다.
- 난 어릴 때 게임 서버도 컴퓨터면 IP가 있고 포트가 있으니 결과적으로 포트에는 한명만 매핑이 되는 줄 알았다. 하지만 사실 내 게임 데이터를 오고가게 하는건 이 자식 프로세스였다! : 나중가면 포트는 6.5만번까지밖에 안되니 그럼 100만명이 게임한다면 물리적인 서버가 100만명에서 6.5만명을 나눈 그 갯수가 필요한가? 라는 얄팍한 상상을 했던 기억이 있다.
- 그래서 이 자식 프로세스는 서버가 해야 할 일에 대한 코드를 갖고 있는채로 생성되어 새로운 차순위 접속자의 서비스 이용을 위해 1:1 대응되어 존재한다.
- 말이 자식 프로세스이지 사실상 서버와의 통신을 할수있는 매개체의 상대 끝 쪽을 말한다고 보는 것이다. 은행에 간 나를 상대하는 은행원이라고 생각하면 보다 확실하다.
- 서버 역할은 이렇다.
- 디스크립터로써 클라이언트와의 통신을 할 수 있는 번호표, 또는 통로에 대한 논함을 한적이 있다.
- 이 클라이언트에 대한 서비스 이용을 도와줄 수 있는 자식 프로세스를 생성한다음 자식 프로세스는 이 클라이언트가 종료되기 전까지 이 자식 프로세스에게 디스크립터를 배정하고 완전 전담하도록 한다.
- 클라이언트가 다 썼다고 끄면 이제 자식 프로세스도 정리하는 것이다.
- 이 자식의 개념을 어떻게 구현하냐를 이제 생각해봐야하는데 그게 세 가지로 나뉘어진다.
- 프로세스, I/O 멀티플렉싱, 쓰레드.
- 프로세스는 우리가 논하던건데, 이건 사실 tiny 완성하면 부분적으로 체험 할 수 있다.
- 프로세스는 자식을 만들어도 자식 프로세스 자체적으로 쓸 수 있는 공간이 있다. 이렇게 독립적이라 그런지 프로세스들끼리의 의사소통에는 다소 빈약하다. 즉, 다른 방식들의 구현을 생각하면 Cost가 드는 방법이다.
- I/O 멀티플렉싱은 프로그래머에게 다룰 수 있는 어떤 세밀한 영역을 더 쥐어주지만, 세밀해지는 정도에따라 코드 복잡도가 미친듯이 늘어난다. I/O 멀티플렉싱의 echo 코드는 프로세스 형태의 코드에 비해 3배나 많은 양이다.
- 쓰레드는 방금 논한 2가지에 대해 장점만 합한 것이다. 우리는 pintOS 과정에서 이걸 실제로 하게 될 것이다. 한 프로세스에서 실행되는 쓰레드 단위의 이 처리하는 주체들은 다 같은 메모리를 공유하게 된다.
쓰레드는 뭐가 끝내주는지/다른지?
- 한 프로세스에서 만들어지는 쓰레드는 이 한 프로세스라는 틀 안에서의 가상메모리 영역을 모두 공유한다.
- 개인 공간이 아예 없는건 아니다. 쓰레드 단위로 스택정도는 있다.
- 프로세스의 문맥 전환에 대해 기억 나시죠? 쓰레드의 문맥 전환도 있는데, 얘는 부피가 작아서 훨씬 빠르게 이루어진다.
- 처음부터 존재했던 메인 쓰레드를 빼고 쓰레드끼리 부모-자식 개념이 없다. 계층 관계에서 오는 이렇게까지 해야하나라는 코드같은걸 일절 생각하지 않아도 된다.
- 쓰레드에 대한 kill 에 대한 조건이 두 개가 있다.
- joinable한 쓰레드
- 내가 이 상태의 쓰레드라면 나는 다른 쓰레드에 의해 죽임을 당할 수 있다. 그래서 내가 죽임당한다면 내 뒷처리를 다른 쓰레드 내지 다른 주체가 따로 해주어야한다.
- detached한 쓰레드
- 내가 이 상태에 쓰레드라면 나는 죽임을 당하진 않는다. 대신 내가 죽을때가 되면 뒷정리를 직접 다 하고 떠난다.
- 나중에 이쪽 분야를 깊게 할거라면 기본값을 detached한 쓰레드로 고려해보길 바란다는 책의 조언도 있다.
좋다. 쓰레드는 외계기술임을 알았으니 쓰레드를 그냥 생각나는대로 쓰면 좋겠지만, 책에서 재밌는 예시가 있다.
바로 바로 동 기 화 문 제
badcnt.c라는 프로그램인데, 여기서의 코드는 두 개의 쓰레드를 생성해서 전역변수로 선언된 cnt를 올리기만 하는 코드를 수행한다.
- 근데 얘, 실행하면 계속 결과가 바뀐다. 10 + 10을 하랬더니 18 17이 나오고 이런 대참사가 나온다.
- 이게 왜이러나 하고 다양하게 시도해보다가 어셈블리어까지 까보니까 뭐 대략 수습이 된다.
- 사실 우리가 cnt + 1을 하면 불러와서, 증가를 시키고, 쓰는 이 일련의 과정이 있다.
- 근데 증가를 시키고 저장을 하기전에 다른 쓰레드가 냅다 나 할거있어 하고 불러오면 여기서 증가시킨 값이 반영이 잘 될까?
- 그래서 쓰레드를 여러 개 배정하는 상황에서는 이 동기화를 별개로 또 충분히 고려해야한다.
축가합니다. 당신은 멀티코어에 관한 논함을 전공하셨으니 이제 과장 조금 보태서 스타크래프트 3을 만들 수 있게 되었습니다.