병렬 프로그래밍

멀티스레딩에서의 경쟁 상태: 발생 원인과 해결 방법

ROBL 2025. 2. 4.
728x90
반응형

멀티스레딩 환경에서는 여러 스레드가 동시에 공유 자원에 접근하면서 경쟁 상태(Race Condition)가 발생할 수 있습니다. 이는 프로그램이 예측할 수 없는 동작을 하거나 비정상적인 결과를 초래하는 주요 원인이 됩니다. 특히, 파일 시스템, 네트워크 리소스, 전역 변수, 동적 메모리 할당 등을 공유할 때 경쟁 상태가 발생할 가능성이 높습니다. 이번 글에서는 경쟁 상태가 발생하는 원인과 이를 방지하는 기법에 대해 설명하겠습니다.

1. 경쟁 상태란?

경쟁 상태는 여러 개의 스레드가 동일한 공유 자원에 동시 접근하여 예기치 않은 결과가 발생하는 현상을 의미합니다. 예를 들어, 다음과 같은 코드에서 경쟁 상태가 발생할 수 있습니다.

#include <iostream>
#include <thread>

int counter = 0;

void increment() {
    for (int i = 0; i < 1000000; ++i) {
        ++counter;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter << std::endl;
    return 0;
}

위 코드는 두 개의 스레드가 동시에 counter 값을 증가시키도록 설계되었습니다. 하지만 실행할 때마다 counter 값이 예상한 값(2000000)이 아니라 더 작은 값으로 출력될 수 있습니다. 이는 ++counter 연산이 원자적(atomic)이지 않기 때문에 발생하는 문제입니다.

2. 경쟁 상태를 방지하는 기법

경쟁 상태를 방지하기 위해 여러 가지 동기화 기법을 사용할 수 있습니다.

2.1 뮤텍스(Mutex) 사용

뮤텍스(Mutex, Mutual Exclusion)한 번에 하나의 스레드만 공유 자원에 접근하도록 제한하는 방식입니다.

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

int counter = 0;
std::mutex mtx;

void increment() {
    for (int i = 0; i < 1000000; ++i) {
        std::lock_guard<std::mutex> lock(mtx);
        ++counter;
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter << std::endl;
    return 0;
}

위 코드에서는 std::mutex를 사용하여 스레드 간 경쟁 상태를 방지합니다. std::lock_guard는 RAII(Resource Acquisition Is Initialization) 기법을 활용하여 자동으로 뮤텍스를 해제해줍니다.

2.2 원자적 연산(Atomic Operations) 활용

C++11부터 제공하는 std::atomic을 사용하면 경쟁 상태를 피할 수 있습니다. 이는 내부적으로 하드웨어 수준에서 원자적 연산을 보장합니다.

#include <iostream>
#include <thread>
#include <atomic>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 1000000; ++i) {
        counter.fetch_add(1, std::memory_order_relaxed);
    }
}

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Counter: " << counter.load() << std::endl;
    return 0;
}

2.3 조건 변수(Condition Variable) 활용

조건 변수를 이용하면 특정 조건이 만족될 때까지 스레드의 실행을 제어하고 동기화할 수 있습니다. 주로 생산자-소비자 패턴에서 활용됩니다.

#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>

std::mutex mtx;
std::condition_variable cv;
bool ready = false;

void worker() {
    std::unique_lock<std::mutex> lock(mtx);
    cv.wait(lock, []{ return ready; });
    std::cout << "Worker thread is running!" << std::endl;
}

int main() {
    std::thread t(worker);
    
    {
        std::lock_guard<std::mutex> lock(mtx);
        ready = true;
    }
    cv.notify_one();
    
    t.join();
    return 0;
}

3. 결론

멀티스레딩 환경에서는 경쟁 상태가 발생할 가능성이 매우 높으며, 이를 방지하지 않으면 비정상적인 프로그램 동작이 일어날 수 있습니다. 뮤텍스, 원자적 연산, 조건 변수 등의 동기화 기법을 적절히 활용하면 안정적인 동작을 보장할 수 있습니다.

다음 포스팅에서는 데드락(Deadlock)의 개념과 이를 방지하는 기법에 대해 다뤄보겠습니다.

728x90
반응형

댓글

💲 추천 글