임계 영역과 뮤텍스

gugyeoj1n·2023년 3월 31일
0

게임 서버

목록 보기
1/3

도대체 얼마만에 벨로그를 작성하는가 !!

지금까지 열심히 달려 오면서 수많은 찍먹을 해온 결과 드디어 게임 서버로 진로가 정해졌다. 서버와 API에 대한 기본적인 이해를 위해 Node.js 를 다시 공부하고, 게임 서버 프로그래밍 교과서 라는 책으로 게임 서버 개발에 있어 효과적인 알고리즘과 아키텍처를 배우고 있다. 근데 너무 어려워서 정리해야 됨 ;

멀티스레딩

프로세스는 여러 개의 스레드를 가질 수 있고, 이를 활용하는 것을 멀티스레딩 이라고 한다. 그리고 OS 는 여러 프로세스와 각 프로세스 안의 여러 스레드를 번갈아 가며 실행하는 과정인 컨텍스트 스위치 를 한다. 이 과정에서 서로 다른 스레드가 하나의 값에 동시에 (번갈아 가면서) 접근하면 중간에 값이 바뀌어 충돌이 생길 수 있다.

예시로 한 스레드가 A 라는 배열 객체를 채우다 메모리를 재할당하고, 이 때 메모리를 가리키는 주소가 바뀌었다. 스위칭되어 다른 스레드가 A에 접근하려고 했는데 이미 힙에서 해제된 메모리여서 뇌정지가 오는 것이다 !

이런 데이터 레이스 때문에 한 스레드가 객체에 접근하여 멤버 변수를 바꾸는 동안 다른 스레드는 절대 접근하지 못하게 하는 원자성 atomicity 를 지켜야 하고, 이래야 멤버 변수들이 상태를 유지하는 일관성 consistency 를 가질 수 있다. 멀티스레딩 프로그래밍을 할 이 원자성과 일관성을 위해 사용하는 특수한 조치들을 동기화 synchronization 이라고 부른다.


동기화 기법에는 대표적으로 뮤텍스 mutex 가 있다. 특정 정보를 감싸 보호하고 있으며, 스레드는 그 정보를 건드리기 전에 뮤텍스에 사용권을 얻겠다는 요청을 해야 한다. 정보에 대한 접근이 끝나면 사용권을 놓겠다는 요청을 보내고, 그제서야 다른 스레드가 정보에 접근할 수 있게 된다.

뮤텍스는 임계 영역 critical section 이라고도 부르고, 사용권을 얻는 과정을 잠금 lock 이라 한다. 반대로 사용권을 놓는 과정은 잠금 해제 unlock 이다.


C++ 에서는 mutex 헤더 파일을 제공하고 있다.
아래는 4개의 스레드를 사용해 소수를 구하는 예제 코드 !

#include <mutex>
#include <vector>
#include <thread>
#include <memory>
#include <iostream>

const int ThreadCount = 4;
const int MaxCount = 150000;

// 얘네는 생략 !
bool IsPrime(int n);
void PrintNum(const vector<int> &primes);

int main(void) {
	recursive_mutex num_mutex;
    recursive_mutex primes_mutex;
    vector<shared_ptr<thread>> threads;
    vector<int> primes;
    
    int targetNum = 1;
    
    for(int i = 0; i < ThreadCount; i++) {
    	shared_ptr<thread> thread(new class thread([&]() {
            while(true) {
                int n;
                {
        			// 잠금, 다른 스레드는 targetNum에 접근 불가
                    lock_guard<recursive_mutex> num_lock(num_mutex);
                    n = targetNum;
                    targetNum++;
                }

                if(n >= MaxCount) break;

                if(IsPrime(n)) {
                	// 잠금, 다른 스레드는 primes에 접근 불가
                    lock_guard<recursive_mutex> primes_lock(primes_mutex);
                    primes.push_back(n);
                }
            }
        }));
    }
}

뮤텍스를 사용해 스레드끼리 꼬이는 현상을 막을 수 있지만, 잠그는 범위를 잘못 설정했을 때도 큰 문제가 발생한다. 뮤텍스를 잘게 나눴을 때, 뮤텍스에 접근하는 과정 자체가 꽤나 무겁기 때문에 프로그램 성능이 하락하고 교착 상태 dead lock 가 쉽게 발생할 수 있다. 반대로 범위를 너무 크게 잡으면 멀티스레딩의 이점을 활용하지 못하기 때문에 병렬 연산이 유리한 부분만 잠금 단위를 나누는 것이 좋다고 한다 !




아직 학교 수업 중 운영체제라는 과목을 수강하지 않아 어렵지만 재미있다. 제일 친한 친구 2명과 게임 개발 스터디를 진행 중인데, 나는 서버, 친구들은 유니티 클라이언트 개발을 공부 중 ! 열공해야딩

0개의 댓글