icon안동민 개발노트

간단한 멀티스레드 프로그램 구현


실습 개요

 이 절에서는 지금까지 학습한 멀티쓰레딩 개념을 실제로 적용하여 간단한 멀티스레드 프로그램을 구현해봅니다. 여러 예제를 통해 멀티쓰레딩의 기본 원리와 주의점을 익힐 수 있습니다.

병렬 벡터 합 계산

 첫 번째 실습으로, 큰 벡터의 모든 요소의 합을 병렬로 계산하는 프로그램을 구현해 봅시다.

 순차적 구현

#include <iostream>
#include <vector>
#include <numeric>
#include <chrono>
 
long long sequential_sum(const std::vector<int>& vec) {
    return std::accumulate(vec.begin(), vec.end(), 0LL);
}
 
int main() {
    const size_t size = 100000000;
    std::vector<int> vec(size, 1);  // 1로 초기화된 큰 벡터
 
    auto start = std::chrono::high_resolution_clock::now();
    long long sum = sequential_sum(vec);
    auto end = std::chrono::high_resolution_clock::now();
 
    std::chrono::duration<double> diff = end - start;
    std::cout << "Sum: " << sum << std::endl;
    std::cout << "Time taken: " << diff.count() << " seconds" << std::endl;
 
    return 0;
}

 병렬 구현

#include <iostream>
#include <vector>
#include <thread>
#include <numeric>
#include <chrono>
 
void partial_sum(const std::vector<int>& vec, size_t start, size_t end, long long& result) {
    result = std::accumulate(vec.begin() + start, vec.begin() + end, 0LL);
}
 
long long parallel_sum(const std::vector<int>& vec, size_t num_threads) {
    std::vector<std::thread> threads;
    std::vector<long long> partial_results(num_threads);
 
    size_t chunk_size = vec.size() / num_threads;
 
    for (size_t i = 0; i < num_threads; ++i) {
        size_t start = i * chunk_size;
        size_t end = (i == num_threads - 1) ? vec.size() : (i + 1) * chunk_size;
        threads.emplace_back(partial_sum, std::ref(vec), start, end, std::ref(partial_results[i]));
    }
 
    for (auto& t : threads) {
        t.join();
    }
 
    return std::accumulate(partial_results.begin(), partial_results.end(), 0LL);
}
 
int main() {
    const size_t size = 100000000;
    std::vector<int> vec(size, 1);  // 1로 초기화된 큰 벡터
 
    auto start = std::chrono::high_resolution_clock::now();
    long long sum = parallel_sum(vec, 4);  // 4개의 스레드 사용
    auto end = std::chrono::high_resolution_clock::now();
 
    std::chrono::duration<double> diff = end - start;
    std::cout << "Sum: " << sum << std::endl;
    std::cout << "Time taken: " << diff.count() << " seconds" << std::endl;
 
    return 0;
}

병렬 행렬 곱셈

 두 번째 실습으로, 두 행렬의 곱셈을 병렬로 수행하는 프로그램을 구현해 봅시다.

 행렬 클래스 정의

#include <vector>
#include <stdexcept>
 
class Matrix {
private:
    std::vector<std::vector<int>> data;
    size_t rows, cols;
 
public:
    Matrix(size_t r, size_t c) : rows(r), cols(c), data(r, std::vector<int>(c, 0)) {}
 
    int& at(size_t r, size_t c) { return data[r][c]; }
    const int& at(size_t r, size_t c) const { return data[r][c]; }
 
    size_t get_rows() const { return rows; }
    size_t get_cols() const { return cols; }
};

 순차적 행렬 곱셈

Matrix sequential_multiply(const Matrix& a, const Matrix& b) {
    if (a.get_cols() != b.get_rows()) {
        throw std::invalid_argument("Matrix dimensions do not match for multiplication");
    }
 
    Matrix result(a.get_rows(), b.get_cols());
 
    for (size_t i = 0; i < a.get_rows(); ++i) {
        for (size_t j = 0; j < b.get_cols(); ++j) {
            for (size_t k = 0; k < a.get_cols(); ++k) {
                result.at(i, j) += a.at(i, k) * b.at(k, j);
            }
        }
    }
 
    return result;
}

 병렬 행렬 곱셈

#include <thread>
 
void multiply_row(const Matrix& a, const Matrix& b, Matrix& result, size_t row) {
    for (size_t j = 0; j < b.get_cols(); ++j) {
        for (size_t k = 0; k < a.get_cols(); ++k) {
            result.at(row, j) += a.at(row, k) * b.at(k, j);
        }
    }
}
 
Matrix parallel_multiply(const Matrix& a, const Matrix& b, size_t num_threads) {
    if (a.get_cols() != b.get_rows()) {
        throw std::invalid_argument("Matrix dimensions do not match for multiplication");
    }
 
    Matrix result(a.get_rows(), b.get_cols());
    std::vector<std::thread> threads;
 
    size_t rows_per_thread = a.get_rows() / num_threads;
 
    for (size_t i = 0; i < num_threads; ++i) {
        size_t start_row = i * rows_per_thread;
        size_t end_row = (i == num_threads - 1) ? a.get_rows() : (i + 1) * rows_per_thread;
 
        threads.emplace_back([&, start_row, end_row]() {
            for (size_t row = start_row; row < end_row; ++row) {
                multiply_row(a, b, result, row);
            }
        });
    }
 
    for (auto& t : threads) {
        t.join();
    }
 
    return result;
}

생산자-소비자 문제

 세 번째 실습으로, 생산자-소비자 패턴을 구현해 봅시다.

스레드 안전 큐 구현

#include <queue>
#include <mutex>
#include <condition_variable>
 
template<typename T>
class ThreadSafeQueue {
private:
    std::queue<T> queue;
    mutable std::mutex mtx;
    std::condition_variable cv;
 
public:
    void push(T value) {
        std::lock_guard<std::mutex> lock(mtx);
        queue.push(std::move(value));
        cv.notify_one();
    }
 
    T pop() {
        std::unique_lock<std::mutex> lock(mtx);
        cv.wait(lock, [this] { return !queue.empty(); });
        T value = std::move(queue.front());
        queue.pop();
        return value;
    }
 
    bool empty() const {
        std::lock_guard<std::mutex> lock(mtx);
        return queue.empty();
    }
};

생산자와 소비자 함수 구현

#include <iostream>
#include <thread>
#include <chrono>
 
void producer(ThreadSafeQueue<int>& queue, int count) {
    for (int i = 0; i < count; ++i) {
        queue.push(i);
        std::cout << "Produced: " << i << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
    }
}
 
void consumer(ThreadSafeQueue<int>& queue, int count) {
    for (int i = 0; i < count; ++i) {
        int value = queue.pop();
        std::cout << "Consumed: " << value << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(150));
    }
}
 
int main() {
    ThreadSafeQueue<int> queue;
    const int item_count = 20;
 
    std::thread prod(producer, std::ref(queue), item_count);
    std::thread cons(consumer, std::ref(queue), item_count);
 
    prod.join();
    cons.join();
 
    return 0;
}

연습 문제

  1. 병렬 벡터 합 계산 프로그램을 std::async를 사용하여 다시 구현해보세요.
  2. 생산자-소비자 문제를 여러 생산자와 여러 소비자가 동작하도록 확장해보세요.
  3. 병렬 정렬 알고리즘(예 : 병렬 머지 정렬)을 구현해보세요.
  4. 스레드 풀(Thread Pool)을 구현하고, 이를 사용하여 작업을 병렬로 처리하는 프로그램을 작성해보세요.


참고 자료

  • "C++ Concurrency in Action" by Anthony Williams
  • "Effective Modern C++" by Scott Meyers (특히 동시성 관련 항목들)
  • C++ 표준 문서의 스레드 및 동시성 관련 섹션
  • Intel Threading Building Blocks (TBB) 문서
  • "The Art of Multiprocessor Programming" by Maurice Herlihy and Nir Shavit