락 프리 프로그래밍이란?
락 프리(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 사용법"에 대해 학습하겠습니다.
'-----ETC----- > C++ 성능 최적화 및 고급 테크닉 시리즈' 카테고리의 다른 글
[C++ 성능 최적화 및 고급 테크닉] Day 20: OpenMP를 이용한 병렬 프로그래밍 (0) | 2024.08.01 |
---|---|
[C++ 성능 최적화 및 고급 테크닉] Day 21: CUDA를 이용한 GPU 프로그래밍 (0) | 2024.08.01 |
[C++ 성능 최적화 및 고급 테크닉] Day 19: 병렬 STL 사용법 (0) | 2024.08.01 |
[C++ 성능 최적화 및 고급 테크닉] Day 16: std::thread와 동기화 기법 (0) | 2024.08.01 |
[C++ 성능 최적화 및 고급 테크닉] Day 17: 병렬 알고리즘과 std::async (0) | 2024.08.01 |