"프로그램을 실행하면 프로세스가 생성됨. 프로세스 안에는 유일한 쓰레드가 있고 그 안에 프로그램 실행"
멀티쓰레드 프로그래밍 활용 용도)
- 기기에 있는 CPU(Core)를 모두 활용해야 할 때
- Single Core의 사용량이 100%가 되지 않으면 멀티쓰레드 프로그래밍을 절대 하면 안됨
- 오래 걸리는 일 1개와 빨리 끝나는 일 여러 개 같이 해야 할 때
- 어떤 긴 처리를 진행하는 동안 다른 짧은 일 처리해야 할 때
-> 비동기 IO를 사용하여 프로세스가 I/O의 완료를 기다리지 않게 함
- 기기에 있는 CPU 모두 활용해야 할 때 (싱글 코어에서 처리 불가하고 CPU의 클럭을 몇 배 높여야 해결될 작업인 경우)
- ex) FPS
쓰레드의 정체
: "두 가지 일을 동시에 하라"라고 시켰을 때
- 운영체제는 두 개의 작업을 각각 다른 Core에서 실행
- 코어 개수 부족하면 컨텍스트 스위칭을 하면서 시분할 처리
데이터 레이스 현상: 멀티 쓰레드/프로세스 환경에서 일어나는 오류로, 여러 쓰레드/프로세스가 공유자원에 동시에 접근하려 할 때, 일어나는 경쟁 상황을 의미
가령 한 쓰레드와 또 다른 쓰레드가 동시에 한 변수를 쓰려고 할 때, 한 쓰레드는 한 변수를 쓰고 있는데 다른 쓰레드는 그 변수를 읽으려고 할 때 등의 상황에서 데이터 레이스가 발생함
해결법)
상호배제: 공유자원을 모든 쓰레드/프로세스가 원하는 때 언제든지 접근할 수 있도록 허용하지 않고, 쓰레드/프로세스의 접근을 제한적으로 허용하는 방식
-> 뮤텍스와 세마포어
데이터 레이스를 해결하기 위해 뮤텍스/세마포어를 여기저기 걸다보면 처리 시간이 느려짐
원인은 컨텍스트 스위칭이 일어나기 때문
CPU는 동시에 한 개씩의 쓰레드/프로세스만 실행 가능, 멀티 쓰레드/멀티 프로세스란 단지 빠른 시간 동안 여러가지의 쓰레드/프로세스가 순차적으로 실행하면서, 동시에 실행되는 것처럼 보이는 것
이처럼 순차적으로 실행되는 과정에선 비용이 발생
뮤텍스의 관점에선 먼저 자원을 점유하고 있던 쓰레드/프로세스가 뮤텍스(세마포어)를 언락(포스트)했을 때, 대기하고 있던 다른 쓰레드/프로세스가 깨어나면서 컨텍스트 스위칭이 일어나고 이러한 컨텍스트 스위칭이 잦으면 오버헤드가 발생해 성능이 떨어짐
뮤텍스: 상호배제(mutual exclusion)의 줄임말
사용 방법)
- X, Y를 보호하는 뮤텍스 MX를 만듦
- 쓰레드는 X, Y를 건드리기 전에 MX에 "사용권을 얻겠다."라고 요청
- 쓰레드는 X, Y를 액세스함
- 액세스가 끝나면 MX에 "사용권을 놓겠다."라고 요청함
교착상태
: 두 쓰레드가 서로를 기다리는 상황
게임 서버에서 교착 상태가 되면 발생하는 현상
- CPU 사용량이 현저히 낮거나 0%, 동시접속자 수와 상관없음
- 클라이언트가 서버를 이용할 수 없음, 예를 들어 로그인을 못하거나 뭔가 요청을 보냈는데 응답이 오지 않음
잠금 순서의 규칙
재귀 뮤텍스: 한 스레드가 뮤텍스를 여러 번 반복해서 잠그는 것을 원활하게 처리
재귀 잠금 - "교착 상태를 예방하려면 첫 번째 잠근 순서를 지켜야 함(거꾸로 가지 말아야 함)"
병렬성과 시리얼 병목
- 병렬성: 여러 CPU가 각 스레드의 연산을 실행하여 동시 처리량을 올리는 것
- 시리얼 병목: 병렬로 실행되게 프로그램을 만들었는데 정작 한 CPU만 연산을 수행하는 현상
암달의 법칙(암달의 저주): CPU 개수가 많을수록 총 처리 효율성이 떨어지는 현상
암달의 저주를 줄이려면 시리얼 병목이 발생하는 구간을 최소로 줄여야 함
싱글 스레드 게임서버
- 싱글 스레드 서버를 구동하는 경우 CPU 개수만큼 프로세스를 띄우는 것이 일반적
- 방 개수만큼 스레드나 프로세스가 있으면 스레드나 프로세스 간 컨텍스트 스위치 횟수 증가
- 따라서 같은 동시접속자를 처리하는 서버라고 하더라도 실제로 처리할 수 있는 동시접속자 수를 크게 떨어뜨림
- 멀티스레드로 서버를 개발하는 경우
- 서버 프로세스를 많이 띄우기 곤란할 때, 예를 들어 프로세스 당 로딩해야 하는 게임 정보(맵 데이터 등)의 용량이 매우 클 때 (특히 MMO 게임 서버)
- 서버 한 대의 프로세스가 여러 CPU의 연산량을 동원해야 할 만큼 많은 연산을 할 때
- 코루틴이나 비동기 I/O함수를 쓸 수 없고 디바이스 타임이 발생할 때
- 서버 인스턴스를 서버 기기당 하나만 두어야 할 때
- 서로 다른 방이 같은 메모리 공간을 액세스해야 할 때
쓰레드 풀링
- 멀티쓰레드 모델의 게임 서버를 개발할 때 쓰레드는 몇 개 만들고, 각 쓰레드는 무엇을 위해 일을 하게 만들면 좋을까?
이벤트: 잠자는 스레드를 깨우는 도구
- C++11에서의 이벤트: future/promise, condition_variable 같은 것으로 구현
- 이벤트는 할 작업이 없는 스레드를 재워 CPU 낭비를 막을 수 있으나(아니면 작업이 생길 때까지 루프를 실행하면서 기다려야 함 <= Busy Waiting)
- 스레드의 실행 상태(running, blocked)를 바꿔야 하므로 운영체제 레벨에서 구현이 되어야 하고, 운영체제 호출 오버헤드를 가짐
- 따라서, 자주 실행되는 코드에서는 성능 문제로 사용하면 안됨
세마포어: 원하는 개수의 스레드가 자원을 액세스할 수 있게 함
- 세마포어는 이벤트와 여러모로 비슷한데, 이벤트는 상태 값이 0과 1로 제한되지만, 세마포어는 0 이상의 아무 값이나 가질 수 있음
원자 조작
: 뮤텍스나 임계 영역 잠금 없이도 여러 스레드가 안전하게 접근할 수 있는 것 의미
원자 조작은 하드웨어 기능이며, 대부분의 컴파일러는 원자 조작 기능을 쓸 수 있게 함
원자 조작은 32비트나 64비트의 변수 타입에 여러 스레드가 접근할 때 한 스레드씩만 처리됨을 보장
멀티스레드 프로그래밍 흔한 실수들
- 읽기와 쓰기 모두에 잠금하지 않기
- 잠금 순서 꼬임
- 너무 좁은 잠금 범위
- 디바이스 타임이 섞인 잠금
- 잠금의 전염성으로 발생한 실수
- 잠금된 뮤텍스나 임계 영역 삭제
- 일관성 규칙 깨기
'Old > Network' 카테고리의 다른 글
게임 네트워킹 (0) | 2023.07.27 |
---|---|
게임 서버와 클라이언트 (0) | 2023.07.24 |
Socket Programming (0) | 2023.07.24 |
컴퓨터 네트워크 (0) | 2023.07.24 |
REST / REST API (0) | 2023.02.11 |