멀티스레딩 환경에서 작업 순서를 제어하는 것은 중요한 문제입니다. 본 문서에서는 C++을 사용하여 여러 스레드 간의 실행 순서를 보장하는 방법을 조건 변수와 세마포어를 활용하여 설명합니다.
1. 세 개의 스레드 순차 실행
세 개의 스레드(A, B, C)가 각각 first(), second(), third() 메서드를 호출할 때, first → second → third 순서로 출력되도록 구현합니다.
#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;
}