C++ 멀티스레딩 기법: 조건 변수와 세마포어를 통한 작업 순서 제어

멀티스레딩 환경에서 작업 순서를 제어하는 것은 중요한 문제입니다. 본 문서에서는 C++을 사용하여 여러 스레드 간의 실행 순서를 보장하는 방법을 조건 변수와 세마포어를 활용하여 설명합니다.

1. 세 개의 스레드 순차 실행

세 개의 스레드(A, B, C)가 각각 first(), second(), third() 메서드를 호출할 때, firstsecondthird 순서로 출력되도록 구현합니다.

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

class SequentialPrinter {
private:
    std::mutex mtx;
    std::condition_variable cv_step1, cv_step2;
    bool isFirstDone = false;
    bool isSecondDone = false;

public:
    void stepOne() {
        std::lock_guard<std::mutex> guard(mtx);
        std::cout << "first" << std::endl;
        isFirstDone = true;
        cv_step1.notify_one();
    }

    void stepTwo() {
        std::unique_lock<std::mutex> lock(mtx);
        cv_step1.wait(lock, [this] { return isFirstDone; });
        std::cout << "second" << std::endl;
        isSecondDone = true;
        cv_step2.notify_one();
    }

    void stepThree() {
        std::unique_lock<std::mutex> lock(mtx);
        cv_step2.wait(lock, [this] { return isSecondDone; });
        std::cout << "third" << std::endl;
    }
};

int main() {
    SequentialPrinter printer;

    std::thread t1(&SequentialPrinter::stepOne, &printer);
    std::thread t2(&SequentialPrinter::stepTwo, &printer);
    std::thread t3(&SequentialPrinter::stepThree, &printer);

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

    return 0;
}

2. 두 스레드 교차 출력 (foobar 패턴)

두 스레드가 "foo"와 "bar"를 번갈아가며 n번 출력해야 합니다. 조건 변수와 세마포어를 각각 사용한 두 가지 구현을 소개합니다.

조건 변수 + 뮤텍스 버전

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

class CrossPrinter {
private:
    int limit;
    std::mutex mtx;
    std::condition_variable cv_foo, cv_bar;
    int turnCount = 0;

public:
    CrossPrinter(int n) : limit(n) {}

    void fooAction(std::function<void()> printFoo) {
        for (int i = 0; i < limit; ++i) {
            std::unique_lock<std::mutex> lock(mtx);
            cv_foo.wait(lock, [this] { return turnCount % 2 == 0; });
            printFoo();
            ++turnCount;
            cv_bar.notify_one();
        }
    }

    void barAction(std::function<void()> printBar) {
        for (int i = 0; i < limit; ++i) {
            std::unique_lock<std::mutex> lock(mtx);
            cv_bar.wait(lock, [this] { return turnCount % 2 != 0; });
            printBar();
            ++turnCount;
            cv_foo.notify_one();
        }
    }
};

세마포어 버전

#include <iostream>
#include <thread>
#include <semaphore.h>
#include <functional>

class SemaphorePrinter {
private:
    int limit;
    std::sem_t sem_foo, sem_bar;

public:
    SemaphorePrinter(int n) : limit(n) {
        sem_init(&sem_foo, 0, 1);
        sem_init(&sem_bar, 0, 0);
    }

    void fooAction(std::function<void()> printFoo) {
        for (int i = 0; i < limit; ++i) {
            sem_wait(&sem_foo);
            printFoo();
            sem_post(&sem_bar);
        }
    }

    void barAction(std::function<void()> printBar) {
        for (int i = 0; i < limit; ++i) {
            sem_wait(&sem_bar);
            printBar();
            sem_post(&sem_foo);
        }
    }

    ~SemaphorePrinter() {
        sem_destroy(&sem_foo);
        sem_destroy(&sem_bar);
    }
};

// 사용 예시
void printFoo() { std::cout << "foo"; }
void printBar() { std::cout << "bar"; }

int main() {
    int n = 5;
    SemaphorePrinter printer(n);

    std::thread t1(&SemaphorePrinter::fooAction, &printer, printFoo);
    std::thread t2(&SemaphorePrinter::barAction, &printer, printBar);

    t1.join();
    t2.join();
    return 0;
}

3. 4개 스레드 순환 출력

4개의 스레드가 각각 1, 2, 3, 4를 순서대로 무한 출력하는 예제입니다. 세마포어를 사용하여 각 스레드의 실행 순서를 제어합니다.

#include <iostream>
#include <thread>
#include <semaphore.h>
#include <atomic>

class CircularPrinter {
private:
    std::sem_t signals[4];
    std::atomic<bool> active;

public:
    CircularPrinter() {
        sem_init(&signals[0], 0, 1);
        for (int i = 1; i < 4; ++i)
            sem_init(&signals[i], 0, 0);
        active.store(true);
    }

    void printDigit(int digit) {
        while (active.load()) {
            sem_wait(&signals[digit - 1]);
            std::cout << digit << " ";
            std::flush(std::cout);
            sem_post(&signals[digit % 4]);
        }
    }

    void halt() {
        active.store(false);
    }

    ~CircularPrinter() {
        for (int i = 0; i < 4; ++i)
            sem_destroy(&signals[i]);
    }
};

int main() {
    CircularPrinter printer;

    std::thread t1(&CircularPrinter::printDigit, &printer, 1);
    std::thread t2(&CircularPrinter::printDigit, &printer, 2);
    std::thread t3(&CircularPrinter::printDigit, &printer, 3);
    std::thread t4(&CircularPrinter::printDigit, &printer, 4);

    std::this_thread::sleep_for(std::chrono::seconds(3));
    printer.halt();

    t1.join(); t2.join(); t3.join(); t4.join();
    return 0;
}

태그: C++ Multithreading condition_variable Semaphore thread synchronization

8월 5일 13:21에 게시됨