본문 바로가기
-----ETC-----/C++ 성능 최적화 및 고급 테크닉 시리즈

[C++ 성능 최적화 및 고급 테크닉] Day 18: 고급 멀티스레딩 기법 (락 프리 프로그래밍)

by cogito21_cpp 2024. 8. 1.
반응형

락 프리 프로그래밍이란?

락 프리(lock-free) 프로그래밍은 상호 배제를 위한 락(뮤텍스)을 사용하지 않고 동시성을 제어하는 기법입니다. 이를 통해 데드락을 방지하고, 스레드 간의 경합을 최소화하여 성능을 향상시킬 수 있습니다.

 

락 프리 프로그래밍의 주요 개념

  • 원자적 연산: 원자적 연산은 중단되지 않고 완료되는 연산입니다. C++11부터 std::atomic 라이브러리를 통해 원자적 연산을 지원합니다.
  • 비차단 알고리즘: 스레드가 다른 스레드에 의해 방해받지 않고 진행할 수 있는 알고리즘입니다.

std::atomic

std::atomic은 원자적 연산을 제공하는 템플릿 클래스입니다. 이를 통해 락 없이 변수의 값을 안전하게 읽고 쓸 수 있습니다.

 

std::atomic 사용 예제

#include <iostream>
#include <thread>
#include <atomic>
#include <vector>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;
    }
}

int main() {
    std::vector<std::thread> threads;

    for (int i = 0; i < 10; ++i) {
        threads.push_back(std::thread(increment));
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

 

위 예제에서 std::atomic<int>을 사용하여 counter 변수를 원자적으로 증가시키고 있습니다. 이를 통해 경합 조건을 방지할 수 있습니다.

 

비차단 알고리즘

비차단 알고리즘은 스레드가 서로를 방해하지 않고 작업을 수행할 수 있도록 설계된 알고리즘입니다. 대표적인 예로 락 프리 스택과 큐가 있습니다.

 

락 프리 스택

락 프리 스택은 락 없이 스택을 구현하는 방법입니다. 이를 위해 std::atomic과 CAS(Compare-and-Swap) 연산을 사용합니다.

 

예제 코드

#include <iostream>
#include <thread>
#include <atomic>

template <typename T>
class LockFreeStack {
private:
    struct Node {
        T data;
        Node* next;
        Node(T value) : data(value), next(nullptr) {}
    };

    std::atomic<Node*> head;

public:
    LockFreeStack() : head(nullptr) {}

    void push(T value) {
        Node* newNode = new Node(value);
        newNode->next = head.load();
        while (!head.compare_exchange_weak(newNode->next, newNode));
    }

    bool pop(T& result) {
        Node* oldHead = head.load();
        while (oldHead && !head.compare_exchange_weak(oldHead, oldHead->next));
        if (oldHead == nullptr) return false;
        result = oldHead->data;
        delete oldHead;
        return true;
    }
};

int main() {
    LockFreeStack<int> stack;
    std::vector<std::thread> threads;

    for (int i = 0; i < 10; ++i) {
        threads.push_back(std::thread([&stack, i]() {
            for (int j = 0; j < 100; ++j) {
                stack.push(i * 100 + j);
            }
        }));
    }

    for (auto& t : threads) {
        t.join();
    }

    int value;
    while (stack.pop(value)) {
        std::cout << value << " ";
    }
    std::cout << std::endl;

    return 0;
}

 

위 예제에서 LockFreeStack 클래스는 원자적 연산과 CAS 연산을 사용하여 스택의 푸시와 팝 연산을 락 없이 구현하고 있습니다.

 

실습 문제

문제 1: 원자적 변수를 사용하여 카운터 구현하기

다음 코드에서 원자적 변수를 사용하여 안전하게 카운터를 증가시키는 프로그램을 작성하세요.

 

main.cpp

#include <iostream>
#include <thread>
#include <vector>

int counter = 0;

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;
    }
}

int main() {
    std::vector<std::thread> threads;

    for (int i = 0; i < 10; ++i) {
        threads.push_back(std::thread(increment));
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

 

해답:

main.cpp (원자적 변수 사용)

#include <iostream>
#include <thread>
#include <atomic>
#include <vector>

std::atomic<int> counter(0);

void increment() {
    for (int i = 0; i < 100000; ++i) {
        ++counter;
    }
}

int main() {
    std::vector<std::thread> threads;

    for (int i = 0; i < 10; ++i) {
        threads.push_back(std::thread(increment));
    }

    for (auto& t : threads) {
        t.join();
    }

    std::cout << "Final counter value: " << counter << std::endl;
    return 0;
}

 

이제 열여덟 번째 날의 학습을 마쳤습니다. 락 프리 프로그래밍의 개념과 std::atomic을 사용하여 원자적 연산을 구현하는 방법을 이해하고, 실습 문제를 통해 이를 적용하는 방법을 학습했습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "병렬 STL 사용법"에 대해 학습하겠습니다.

반응형