메모리 구조와 멀티스레드

Jaemyeong Lee·2024년 12월 24일

게임 서버1

목록 보기
111/220

메모리 4영역을 스레드 관점으로 보기

영역별 특성 정리

영역대표 예시프로세스 내 공유 여부멀티스레드 위험도핵심 포인트
코드(Text)함수 본문, 명령어공유낮음보통 읽기 전용
스택(Stack)지역 변수, 함수 인자스레드별 독립낮음각 스레드가 자기 스택 사용
힙(Heap)new, make_shared로 만든 객체공유 가능높음"공유된 힙 객체"가 위험
데이터(Data/BSS)전역 변수, static 변수공유높음동시 쓰기 시 레이스 발생

안전성 판단 2문제

  • 질문 1: 같은 메모리 주소를 둘 이상 스레드가 접근하는가?
  • 질문 2: 그 접근 중 쓰기(write)가 하나라도 있는가?
  • 두 질문이 모두 Yes면 동기화(mutex, atomic 등)가 필요합니다.
  • 여러 스레드의 읽기 전용 접근은 일반적으로 안전합니다.

포인터와 힙을 정확히 구분하기

int* p = new int(100);  // p는 스택(지역 변수), *p는 힙 객체
  • p 변수 자체는 스택에 있으므로 스레드마다 독립일 수 있습니다.
  • 하지만 p가 가리키는 힙 주소를 공유하면 즉시 공유 자원이 됩니다.
  • 핵심은 "힙이냐 아니냐"보다 "같은 객체를 공유하느냐"입니다.

레이스 컨디션 재현과 해결

레이스 컨디션 재현

#include <iostream>
#include <thread>

int counter = 0;

void Work() {
    for (int i = 0; i < 1'000'000; ++i) {
        counter++;  // 원자적 연산이 아님
    }
}

int main() {
    std::thread t1(Work);
    std::thread t2(Work);
    t1.join();
    t2.join();
    std::cout << counter << '\n';  // 기대: 2,000,000 / 실제: 더 작을 수 있음
}
  • counter++는 읽기-수정-쓰기의 복합 연산이라 경쟁 상태가 발생합니다.
  • 결과가 매번 다르게 나오는 것 자체가 레이스의 대표 신호입니다.

해결 1 - mutex

#include <mutex>

std::mutex m;
int counter = 0;

void Work() {
    for (int i = 0; i < 1'000'000; ++i) {
        std::lock_guard<std::mutex> lock(m);
        ++counter;
    }
}
  • 장점: 여러 변수/불변식(invariant)을 함께 보호하기 쉽습니다.
  • 단점: 락 경합이 커지면 성능이 떨어질 수 있습니다.

해결 2 - atomic

#include <atomic>

std::atomic<int> counter{0};

void Work() {
    for (int i = 0; i < 1'000'000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}
  • 장점: 단순 카운터/플래그에 매우 유용하고 락 오버헤드가 적습니다.
  • 주의: 복합 규칙(예: "HP 감소 + 상태 전환")은 atomic만으로 안전하지 않을 수 있습니다.

atomic vs mutex 선택 기준

도구적합한 상황장점주의점
atomic카운터, 플래그, 단일 변수 상태빠르고 간결복합 불변식 보호 어려움
mutex여러 변수 동시 보호, 트랜잭션성 갱신표현력 높음, 범용락 경합/교착 가능성 관리 필요

강의 시 유의사항

강조 포인트

  • "힙 = 무조건 위험"이 아니라 "공유 + 쓰기"가 위험 조건입니다.
  • counter++가 원자적이지 않다는 사실을 반드시 시연하세요.
  • 락은 "최대한 짧게" 잡되, 데이터 불변식이 깨지지 않게 설계해야 합니다.

자주 하는 오해

오해바로잡기
atomic은 항상 mutex보다 좋다단일 상태에는 유리하지만 복합 상태 보호에는 한계가 있음
지역 변수는 항상 안전하다지역 포인터가 공유 객체를 가리키면 위험할 수 있음
읽기만 해도 무조건 락이 필요하다불변 데이터에 대한 동시 읽기는 일반적으로 안전

체크 질문 (스스로 답해보기)

  • "힙 + 공유 + 쓰기" 조건을 실제 코드에서 찾을 수 있는가?
  • counter++가 왜 원자적 연산이 아닌지 설명할 수 있는가?
  • 어떤 상황에서 atomic 대신 mutex를 선택해야 하는가?

profile
李家네_공부방

0개의 댓글