콘텐츠로 이동

동시성 디자인 패턴

스레드 생성/삭제 비용을 줄이기 위해 스레드를 미리 생성해두고 재사용합니다.

class ThreadPool {
vector<thread> workers;
queue<function<void()>> tasks;
mutex mtx;
condition_variable cv;
bool stop = false;
public:
ThreadPool(size_t n) {
for (size_t i = 0; i < n; i++) {
workers.emplace_back([this] {
while (true) {
function<void()> task;
{
unique_lock lock(mtx);
cv.wait(lock, [this] { return stop || !tasks.empty(); });
if (stop && tasks.empty()) return;
task = move(tasks.front());
tasks.pop();
}
task();
}
});
}
}
template<class F>
auto submit(F&& f) -> future<invoke_result_t<F>> {
auto pkg = make_shared<packaged_task<invoke_result_t<F>()>>(forward<F>(f));
future result = pkg->get_future();
{
lock_guard lock(mtx);
tasks.emplace([pkg] { (*pkg)(); });
}
cv.notify_one();
return result;
}
~ThreadPool() {
{ lock_guard lock(mtx); stop = true; }
cv.notify_all();
for (auto& w : workers) w.join();
}
};
// 사용
ThreadPool pool(4);
auto fut = pool.submit([] { return 42; });
cout << fut.get();

생산자와 소비자를 분리해 작업 속도 차이를 버퍼로 흡수합니다.

template<typename T>
class BoundedChannel {
queue<T> buf;
const size_t cap;
mutex mtx;
condition_variable not_full, not_empty;
public:
BoundedChannel(size_t cap) : cap(cap) {}
void send(T item) {
unique_lock lock(mtx);
not_full.wait(lock, [this] { return buf.size() < cap; });
buf.push(move(item));
not_empty.notify_one();
}
T recv() {
unique_lock lock(mtx);
not_empty.wait(lock, [this] { return !buf.empty(); });
T item = move(buf.front());
buf.pop();
not_full.notify_one();
return item;
}
};
// 사용
BoundedChannel<int> ch(10);
thread producer([&]{ for(int i=0;i<100;i++) ch.send(i); });
thread consumer([&]{ for(int i=0;i<100;i++) cout << ch.recv() << '\n'; });

객체 내부 상태를 뮤텍스로 보호하고, 조건 변수로 대기/신호를 처리합니다.

class SharedCounter {
int value = 0;
mutable mutex mtx;
condition_variable cv;
public:
void increment() {
lock_guard lock(mtx);
++value;
cv.notify_all();
}
// value가 threshold 이상이 될 때까지 블록
void waitUntil(int threshold) {
unique_lock lock(mtx);
cv.wait(lock, [&] { return value >= threshold; });
}
int get() const {
lock_guard lock(mtx);
return value;
}
};

읽기는 동시에 허용하고, 쓰기는 독점적으로 처리합니다.

#include <shared_mutex>
class RWCache {
unordered_map<string, string> data;
mutable shared_mutex mtx;
public:
// 여러 스레드가 동시에 읽기 가능
optional<string> get(const string& key) const {
shared_lock lock(mtx);
auto it = data.find(key);
return it != data.end() ? optional{it->second} : nullopt;
}
// 쓰기는 독점
void set(const string& key, string value) {
unique_lock lock(mtx);
data[key] = move(value);
}
};

초기화 비용을 줄이기 위한 지연 초기화 패턴입니다.

class Singleton {
static atomic<Singleton*> instance;
static mutex mtx;
Singleton() = default;
public:
static Singleton* getInstance() {
Singleton* p = instance.load(memory_order_acquire);
if (!p) {
lock_guard lock(mtx);
p = instance.load(memory_order_relaxed);
if (!p) {
p = new Singleton();
instance.store(p, memory_order_release);
}
}
return p;
}
};
// 더 간단한 C++11 방식 (권장)
Singleton& getInstance() {
static Singleton inst; // 스레드 안전 초기화 보장 (C++11)
return inst;
}

비동기 메서드 호출을 큐에 쌓고 별도 스레드에서 순차 실행합니다.

class ActiveLogger {
BoundedChannel<string> queue_{100};
thread worker_;
public:
ActiveLogger() : worker_([this] {
while (true) {
string msg = queue_.recv();
if (msg == "__STOP__") break;
cout << "[LOG] " << msg << '\n';
}
}) {}
void log(string msg) { queue_.send(move(msg)); }
~ActiveLogger() {
queue_.send("__STOP__");
worker_.join();
}
};
패턴문제핵심 아이디어
Thread Pool스레드 생성 비용스레드 재사용
Producer-Consumer속도 불일치버퍼로 분리
Monitor Object공유 상태 보호뮤텍스 + 조건변수
Read-Write Lock읽기 병렬성shared_mutex
Active Object비동기 처리명령 큐 + 전용 스레드
  • Thread Pool: 고정 스레드 수로 작업 처리, future로 결과 수신
  • Producer-Consumer: BoundedChannel로 백프레셔 구현
  • Monitor Object: 뮤텍스 + 조건변수 캡슐화로 안전한 공유 상태
  • Read-Write Lock: 읽기 다수/쓰기 희소 시 shared_mutex 활용
  • C++11 지역 정적 변수는 스레드 안전 초기화 보장 — 싱글턴에 활용