C++ – 멀티스레드의 기초: Mutex(뮤텍스) 가이드

By | 2026년 4월 2일
Table of Contents

C++ – 멀티스레드의 기초: Mutex(뮤텍스) 가이드

멀티스레드 프로그래밍을 하다 보면 여러 스레드가 하나의 변수나 객체에 동시에 접근하는 상황이 발생합니다. 이때 데이터가 깨지는 것을 방지하기 위해 사용하는 가장 기초적인 도구가 바로 Mutex(뮤텍스)입니다.

1. Mutex란 무엇인가?

MutexMutual Exclusion(상호 배제)의 약자입니다.

쉽게 비유하자면, 여러 사람이 이용하는 화장실의 단 하나뿐인 열쇠와 같습니다.

  • 열쇠가 있는 사람만 화장실(공유 자원)에 들어갈 수 있습니다.

  • 안에 사람이 있다면, 밖의 사람들은 열쇠가 반납될 때까지 줄을 서서 기다려야 합니다.

2. 핵심 함수: lock()과 unlock()

C++에서 std::mutex를 사용할 때 가장 핵심이 되는 두 가지 함수입니다.

lock() : 열쇠 획득 (잠그기)

  • 이 함수를 호출한 스레드는 뮤텍스의 소유권을 가져오려고 시도합니다.

  • 만약 다른 스레드가 이미 lock()을 해서 사용 중이라면, 그 스레드가 unlock()을 할 때까지 현재 스레드는 이 라인에서 멈춰(Blocking) 기다립니다.

unlock() : 열쇠 반납 (풀기)

  • 공유 자원 사용이 끝났음을 알리고 열쇠를 제자리에 둡니다.

  • 이제 줄 서서 기다리던 다른 스레드 중 하나가 열쇠를 집어 들고 lock()을 통과할 수 있게 됩니다.

3. 코드 예시: std::deque 보호하기

commandQueue – commandQueueMutex 는 실질적으로 연관관계는 존재하지 않습니다.

commandQueueMutex.lock(); 부분을 락을 걸어서 다른 스래드에서 commandQueueMutex.lock(); 를 실행하려 할 때 멈추게 합니다.

최대한 빠르게 unlock() 을 하지 않으면, 데드락이 됩니다.

#include <iostream>
#include <deque>
#include <mutex>
#include <thread>

// 공유 자원과 이를 보호할 뮤텍스
std::deque<int> commandQueue;
std::mutex commandQueueMutex;

void producer() {
    for (int i = 0; i < 5; ++i) {
        // 1. 접근 전 잠금
        commandQueueMutex.lock();

        std::cout << "Pushing: " << i << std::endl;
        commandQueue.push_back(i);

        // 2. 작업 후 반드시 잠금 해제
        commandQueueMutex.unlock();

        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}

void consumer() {
    while (true) {
        commandQueueMutex.lock(); // 접근 전 잠금

        if (!commandQueue.empty()) {
            int data = commandQueue.front();
            commandQueue.pop_front();
            std::cout << "Popping: " << data << std::endl;
        }

        commandQueueMutex.unlock(); // 작업 후 해제

        // 데이터가 없으면 잠시 대기 (CPU 점유율 방지)
        std::this_thread::sleep_for(std::chrono::milliseconds(150));
    }
}

4. 주의사항: 뮤텍스 사용 시 지켜야 할 규칙

뮤텍스는 물리적으로 데이터를 잠그는 것이 아니라, 프로그래머들 사이의 약속입니다. 따라서 다음 규칙을 반드시 지켜야 합니다.

  1. 동일한 열쇠 사용: commandQueue를 보호하기로 했다면, 이 큐에 접근하는 모든 곳에서 반드시 동일한 commandQueueMutex 객체를 사용해야 합니다.

  2. 반드시 unlock() 호출: lock()만 하고 unlock()을 하지 않으면, 프로그램은 영원히 멈춰버리는 데드락(Deadlock) 상태에 빠집니다.

  3. 최소한의 영역만 잠그기: lock()unlock() 사이의 코드가 너무 길어지면 다른 스레드들이 너무 오래 기다려야 하므로, 꼭 필요한 공유 자원 접근 부분만 감싸는 것이 좋습니다.

답글 남기기