반응형

std::optional이 필요한가요?

C++에서는 전통적으로 "값이 없음을 나타내는" 방법이 마땅치 않았어요.

  • 포인터일 경우는 nullptr로 표현할 수 있었지만,
  • 값 타입(int, double, struct 등)에서는 특별한 값을 정하거나 별도 플래그를 둬야 했죠.

이런 문제를 해결하기 위해 optional이 등장했어요.

std::optional 이란?

std::optional<T>는 T 타입의 값을 가질 수도 있고, 안 가질 수도 있는 컨테이너입니다.

#include <optional>

std::optional<int> maybeGetValue(bool condition) {
    if (condition)
        return 42;
    else
        return std::nullopt;  // 아무 값도 없음
}

 


 주요 특징 정리

기능설명
std::optional<T> T 타입의 optional 객체 생성
std::nullopt 값이 없음을 표현하는 상수
.has_value() or if (opt) 값이 있는지 확인
.value() 값을 가져옴 (값이 없으면 예외 발생)
.value_or(default) 값이 있으면 그 값, 없으면 기본값 반환
*opt or opt.value() 값에 접근 (값이 있어야 안전함)

사용 예제

#include <iostream>
#include <optional>
#include <string>

std::optional<std::string> findNameById(int id) {
    if (id == 1)
        return "Alice";
    else
        return std::nullopt;
}

int main() {
    auto name = findNameById(2);

    if (name.has_value()) {
        std::cout << "이름: " << *name << "\n";
    } else {
        std::cout << "이름을 찾을 수 없습니다.\n";
    }

    return 0;
}

언제 쓰면 좋을까?

  • 함수에서 값을 반환할 수도, 안 할 수도 있는 경우 (null 대신)
  • 포인터를 쓰지 않고도 값 없음(null) 상태 표현 가능
  • std::optional<T>는 T가 복사/이동 가능한 타입일 때 사용 가능

C++에서 Optional은 어떤 점이 좋을까?

  • 명시적이고 타입 안정성 있음 (null pointer 같은 오류 줄임)
  • 함수 인터페이스가 더 직관적이고 안전해짐
  • null 체크나 특별한 에러코드를 리턴하는 방식보다 표현력이 풍부함

'Develop > C&CPP' 카테고리의 다른 글

[cpp] thread  (0) 2025.04.20
[cpp] atomic  (0) 2025.04.20
[cpp] mutex, lock  (0) 2025.04.20
[cpp] cpp17에서 달라진 점  (0) 2025.04.20
[cpp] cpp14에서 추가된 것  (0) 2025.04.20
반응형

std::thread 사용법 정리 (C++17 기준)

1. 기본 개념

  • C++11부터 도입된 std::thread는 병렬 실행을 위한 클래스입니다.
  • 쓰레드를 생성하면 자동으로 시작되며, 작업이 끝날 때까지 join() 또는 detach() 호출로 관리해야 합니다.

2. 기본 예제

#include <iostream>
#include <thread>

void say_hello() {
    std::cout << "Hello from thread!" << std::endl;
}

int main() {
    std::thread t(say_hello); // 쓰레드 시작
    t.join();                 // 메인 쓰레드가 t를 기다림
}
  • join()이 없으면 프로그램이 끝나기 전에 쓰레드가 종료되지 않을 수 있어 런타임 오류 발생.

3. 인자 전달

void print_sum(int a, int b) {
    std::cout << a + b << std::endl;
}

int main() {
    std::thread t(print_sum, 3, 4);
    t.join();
}
  • 값은 복사되어 전달됩니다.
  • 참조를 넘기려면 std::ref() 사용
void modify(int& x) {
    x += 10;
}

int main() {
    int value = 5;
    std::thread t(modify, std::ref(value));
    t.join();
    std::cout << value << std::endl; // 15
}

4. 람다 함수 사용

std::thread t([](){
    std::cout << "Running in a lambda thread!" << std::endl;
});
t.join();

5. 멤버 함수 호출

class Worker {
public:
    void run() {
        std::cout << "Worker running!" << std::endl;
    }
};

int main() {
    Worker w;
    std::thread t(&Worker::run, &w); // 객체 포인터 전달
    t.join();
}

6. join() vs detach()

  • join(): 쓰레드가 끝날 때까지 기다림
  • detach(): 쓰레드를 백그라운드에서 실행시키고 제어를 넘김
std::thread t([]() {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    std::cout << "Detached thread done" << std::endl;
});
t.detach(); // join하지 않아도 됨

주의: detach한 쓰레드는 더 이상 제어할 수 없으며, 메인 함수가 먼저 종료되면 문제가 생길 수 있음.


7. 쓰레드 ID 확인

 
std::cout << std::this_thread::get_id() << std::endl;

8. 쓰레드 관련 함수들

  • std::this_thread::sleep_for(duration): 일정 시간 동안 sleep
  • std::this_thread::yield(): 다른 쓰레드에게 CPU 양보
  • std::thread::hardware_concurrency(): 시스템의 논리 코어 수 반환

9. 예외 처리

std::thread 자체는 예외를 전달하지 않기 때문에, 예외는 쓰레드 내부에서 처리해야 합니다.

std::thread t([]() {
    try {
        throw std::runtime_error("error!");
    } catch (const std::exception& e) {
        std::cout << "Caught exception: " << e.what() << std::endl;
    }
});
t.join();

10. C++17에서 유용한 추가 기능

  • std::scoped_lock을 쓰레드와 뮤텍스에 함께 사용 가능
  • std::shared_mutex (읽기/쓰기 락)과 함께 사용 시 성능 최적화 가능
  • 쓰레드 안전한 std::atomic 변수와 병행 사용

 

std::shared_mutex란?

  • 동시에 여러 개의 쓰레드가 읽을 수 있도록 허용하지만,
  • 쓰기(수정) 시에는 단 하나의 쓰레드만 접근 가능하게 만드는 락입니다.

읽기: 병렬 허용
쓰기: 단독만 허용

예: std::mutex와 std::shared_mutex의 차이

일반 std::mutex 사용 시

cpp
std::mutex mtx; void read() {
std::lock_guard<std::mutex> lock(mtx); // 읽기 작업
}

읽기조차 직렬화됨 (읽기끼리도 동시에 접근 불가)


std::shared_mutex 사용 시

#include <shared_mutex>

std::shared_mutex shared_mtx;

void read() {
    std::shared_lock<std::shared_mutex> lock(shared_mtx); // 여러 쓰레드 동시에 가능
    // 읽기 작업
}

void write() {
    std::unique_lock<std::shared_mutex> lock(shared_mtx); // 단독 접근
    // 쓰기 작업
}

여러 쓰레드가 동시에 읽기 가능
쓰기 쓰레드는 단독 접근만 허용 (읽기 중인 쓰레드가 끝날 때까지 대기)

성능 최적화의 이유

쓰기보다 읽기가 많은 상황 (예: 캐시, 설정 조회 등)에서는:

  • std::mutex: 모든 작업이 직렬 → 비효율적
  • std::shared_mutex: 읽기 병렬화 가능 → 성능 향상
#include <iostream>
#include <thread>
#include <shared_mutex>
#include <vector>

std::shared_mutex shared_mtx;
int shared_data = 0;

void reader(int id) {
    std::shared_lock<std::shared_mutex> lock(shared_mtx);
    std::cout << "Reader " << id << " read value: " << shared_data << std::endl;
}

void writer(int value) {
    std::unique_lock<std::shared_mutex> lock(shared_mtx);
    shared_data = value;
    std::cout << "Writer wrote value: " << value << std::endl;
}

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

    // 읽기 쓰레드 여러 개
    for (int i = 0; i < 5; ++i)
        threads.emplace_back(reader, i);

    // 쓰기 쓰레드 하나
    threads.emplace_back(writer, 100);

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

'Develop > C&CPP' 카테고리의 다른 글

[cpp] optional  (0) 2025.04.25
[cpp] atomic  (0) 2025.04.20
[cpp] mutex, lock  (0) 2025.04.20
[cpp] cpp17에서 달라진 점  (0) 2025.04.20
[cpp] cpp14에서 추가된 것  (0) 2025.04.20
반응형

std::atomic 개념과 사용법 (C++17 기준)

1. std::atomic이란?

  • 동기화된 접근을 보장하는 데이터 타입 템플릿입니다.
  • 멀티스레드에서 **뮤텍스 없이도 변수 접근의 원자성(atomicity)**을 보장합니다.
  • std::atomic<int>, std::atomic<bool>, std::atomic<T> 등 다양한 타입으로 사용 가능.

2. 기본 사용법

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

std::atomic<int> counter{0};

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

int main() {
    std::thread t1(increment);
    std::thread t2(increment);

    t1.join();
    t2.join();

    std::cout << "Final counter: " << counter << std::endl;
}
  • counter++는 원자적으로 수행됨 → 경쟁 조건(race condition) 없이 동작

3. 주요 연산

  • store(val): 값 설정
  • load(): 현재 값 반환
  • exchange(val): 기존 값을 새로운 값으로 바꾸고, 이전 값을 반환
  • compare_exchange_strong(expected, desired)
  • compare_exchange_weak(expected, desired): CAS(compare-and-swap) 연산
std::atomic<int> value{5};
int expected = 5;
bool success = value.compare_exchange_strong(expected, 10);
// 성공 시 value는 10이 되고 true 반환
 

4. 메모리 순서 (memory ordering)

  • 기본은 memory_order_seq_cst (가장 강력하고 안전한 순서)
  • 필요에 따라 memory_order_relaxed, acquire, release 등 지정 가능
counter.fetch_add(1, std::memory_order_relaxed); // 약한 메모리 순서

하지만 대부분의 경우 기본 순서를 사용하는 것으로 충분하며, 성능 최적화가 필요한 경우만 조절합니다.


5. std::atomic_flag (가장 가벼운 락)

  • 간단한 락 구현이나 spin lock 등에 사용
  • 초기화는 반드시 ATOMIC_FLAG_INIT로
std::atomic_flag flag = ATOMIC_FLAG_INIT;

void work() {
    while (flag.test_and_set(std::memory_order_acquire)) {
        // busy-wait
    }
    // critical section
    flag.clear(std::memory_order_release);
}

6. 뮤텍스 vs atomic

항목  mutex atomic
동기화 범위 임의의 코드 블록 변수 단위
오버헤드 상대적으로 큼 작음
복잡한 동작 가능 제한적
예외 안전성 RAII로 안전 직접 관리 필요

7. C++17에서의 특징

  • 템플릿 deduction을 사용할 수 없지만, std::atomic<T>의 인터페이스는 보다 풍부해졌습니다.
  • std::atomic<std::shared_ptr<T>>도 제공되어, 참조 카운팅이 원자적으로 가능해졌습니다.

'Develop > C&CPP' 카테고리의 다른 글

[cpp] optional  (0) 2025.04.25
[cpp] thread  (0) 2025.04.20
[cpp] mutex, lock  (0) 2025.04.20
[cpp] cpp17에서 달라진 점  (0) 2025.04.20
[cpp] cpp14에서 추가된 것  (0) 2025.04.20

+ Recent posts