상세 컨텐츠

본문 제목

C++에서의 ThreadPool 활용과 기존 Thread 생성 방식 비교

Study/C++

by mangirl 2024. 4. 30. 17:31

본문

멀티스레딩 프로그래밍에서 효율적인 스레드 관리는 매우 중요한 요소입니다. 기존에 사용하던 **폴링(polling)**과 이벤트(event) 기반의 스레드 생성 방식은 다음과 같은 특징을 가지고 있었습니다.


🔄 기존 Thread 생성 방식

✅ 폴링(Polling) 방식

  • Flag를 활용하여 동기를 맞추는 방식
  • while 문에서 설정한 주기마다 Flag를 체크하여 동작
  • 특정 작업을 주기적으로 확인할 때 유용하지만, 불필요한 CPU 사용률을 증가시킬 수 있음

예제 코드: Polling 방식

INT ThreadFunc() {
    while (true) {
        if (m_Flag) {
            // 동작
        }
        Sleep(10);  // CPU 사용률을 낮추기 위한 지연 시간
    }
}

✅ 이벤트(Event) 방식

  • 이벤트가 발생할 때마다 새로운 스레드를 생성하여 동작
  • 필요한 순간에만 스레드를 생성하므로 불필요한 리소스 사용을 줄일 수 있음

예제 코드: Event 방식

if (조건) {
    AfxBeginThread(ThreadFunc, this, THREAD_PRIORITY_NORMAL, 0);
}

📋 기존 방식의 한계

검사기 시스템에서 데이터 저장, I/O 제어, PLC 신호 체크와 같은 작업은 주기적인 확인이 필요하기 때문에 Polling 방식이 적절합니다. 하지만, 특정 기능에서는 주기적인 체크가 필요 없는 작업도 있습니다. 이런 경우에도 Polling 방식을 사용하면 불필요한 과부하가 발생하게 됩니다.

이로 인해 이벤트 기반 스레드 생성 방식을 일부 활용하였으나, 문제는 스레드 생성 비용이 발생한다는 점이었습니다.

바로 이 문제를 해결할 수 있는 것이 ThreadPool입니다.


🚀 ThreadPool이란?

ThreadPool스레드를 미리 생성해두고, 필요할 때 가져와 사용하는 방식입니다.

✅ 주요 특징

  • 스레드 생성 비용 감소: 필요할 때마다 스레드를 새로 생성하지 않고, 재사용합니다.
  • 리소스 관리 효율성: 스레드 수를 제한하여 시스템 리소스를 효율적으로 사용할 수 있습니다.
  • 병렬 처리 최적화: 작업의 병렬 처리를 효과적으로 관리할 수 있습니다.

🧩 ThreadPool 예제 코드 (C++)

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

class ThreadPool {
public:
    ThreadPool(size_t numThreads);
    ~ThreadPool();

    void enqueue(std::function<void()> task);

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;

    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop;
};

ThreadPool::ThreadPool(size_t numThreads) : stop(false) {
    for (size_t i = 0; i < numThreads; ++i) {
        workers.emplace_back([this]() {
            for (;;) {
                std::function<void()> task;

                {
                    std::unique_lock<std::mutex> lock(this->queueMutex);
                    this->condition.wait(lock, [this]() { return this->stop || !this->tasks.empty(); });
                    if (this->stop && this->tasks.empty())
                        return;
                    task = std::move(this->tasks.front());
                    this->tasks.pop();
                }

                task();
            }
        });
    }
}

ThreadPool::~ThreadPool() {
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        stop = true;
    }
    condition.notify_all();
    for (std::thread &worker : workers)
        worker.join();
}

void ThreadPool::enqueue(std::function<void()> task) {
    {
        std::unique_lock<std::mutex> lock(queueMutex);
        tasks.emplace(std::move(task));
    }
    condition.notify_one();
}

✅ 사용 예제

int main() {
    ThreadPool pool(4);

    for (int i = 0; i < 10; ++i) {
        pool.enqueue([i]() {
            std::cout << "Task " << i << " is running." << std::endl;
        });
    }

    return 0;
}

🔄 기존 방식과 ThreadPool 비교

구분Polling 방식Event 방식ThreadPool 방식

스레드 생성 주기적으로 체크하여 생성 이벤트 발생 시 생성 미리 생성하여 재사용
리소스 사용 과다한 CPU 사용 가능성 필요할 때만 리소스 사용 효율적인 리소스 관리
장점 주기적 작업에 적합 이벤트성 작업에 적합 모든 상황에 유연함
단점 CPU 부하 발생 스레드 생성 비용 발생 초기 설정 복잡성

📝 결론

기존의 PollingEvent 방식은 각각의 장단점이 있지만, ThreadPool을 활용하면 스레드 생성 비용을 줄이고 리소스를 효율적으로 관리할 수 있습니다.

이를 통해 검사기 시스템의 주기적 작업과 이벤트성 작업을 보다 효과적으로 관리할 수 있으며, 멀티스레딩 프로그램의 성능을 최적화할 수 있습니다.

멀티스레딩 프로그램를 개발하고 있다면 ThreadPool 사용을 고려해보세요.

'Study > C++' 카테고리의 다른 글

C++ STL Map 사용법 및 실전 활용  (0) 2025.01.10

관련글 더보기