728x90
반응형
728x90
반응형
반응형

C++26의 경량 연관 컨테이너: std::flat_mapstd::flat_set


1. 개요

  • std::flat_mapstd::flat_setC++26에 새롭게 도입된 연관 컨테이너입니다.
  • 기존의 std::map, std::set과 달리 내부적으로 정렬된 std::vector를 기반 으로 하여 구현되며, 메모리 효율성과 캐시 친화성이 뛰어난 것이 특징입니다.

  • 컨테이너 내부 구조 정렬 여부 중복 허용 삽입/삭제 성능 검색 성능
    std::flat_map std::vector<std::pair<Key, T>> 있음 느림 (O(n)) 빠름 (O(log n))
    std::flat_set std::vector<Key> 있음 느림 (O(n)) 빠름 (O(log n))


2. 사용 예시

2.1 std::flat_map 예시

  • cpp

     #include <flat_map>
     #include <iostream>
     
     int main() {
         std::flat_map<int, std::string> fmap = {
             {3, "세"}, {1, "일"}, {2, "이"}
         };
     
         for (auto& [k, v] : fmap)
             std::cout << k << ": " << v << '\n';
     }
    
  • 출력 결과:

     1: 일
     2: 이
     3: 세
    

자동 정렬된 상태로 출력됩니다.


2.2 std::flat_set 예시

  • cpp

     #include <flat_set>
     #include <iostream>
     
     int main() {
         std::flat_set<int> fset = {5, 2, 4, 1, 3};
     
         for (int x : fset)
             std::cout << x << ' ';
     }
    
  • 출력 결과:

     1 2 3 4 5
    

입력 순서와 무관하게 자동 정렬됩니다.



3. 주요 특징

  • 내부는 정렬된 벡터 구조 (std::vector)로 되어 있어 순차 접근 성능이 뛰어납니다.
  • 검색 은 이진 탐색으로 빠르게 처리되며, 복잡도는 O(log n)입니다.
  • 삽입/삭제는 재정렬이 필요하므로 비효율적일 수 있습니다.
  • 삽입이 적고 검색/순회가 빈번한 경우에 성능 이점을 얻습니다.
  • C++ 표준 라이브러리 컨테이너와 호환성이 좋습니다.


4. 도입 배경

  • 실무에서는 이미 boost 라이브러리의 boost::container::flat_map, flat_set이 널리 사용되었습니다.
  • 이들 컨테이너의 효율성과 실용성이 입증되었고, 이를 기반으로 C++ 표준화가 진행되어 C++26에 공식 채택되었습니다.


5. 기존 컨테이너와의 비교

  • 비교 항목 std::map std::flat_map
    내부 구조 레드-블랙 트리 정렬된 벡터
    삽입 성능 O(log n) O(n)
    삭제 성능 O(log n) O(n)
    검색 성능 O(log n) O(log n)
    순차 접근 성능 느림 빠름 (메모리 연속)
    메모리 사용 높음 (포인터 기반) 낮음 (연속 메모리)

flat_ 계열은 읽기 중심의 고정된 데이터셋에 적합합니다.



6. 마무리

  • std::flat_mapstd::flat_set은 고성능이 필요한 읽기 중심의 애플리케이션에서 유용하게 사용할 수 있는 컨테이너입니다.
  • 만약 데이터의 삽입과 삭제가 빈번하다면 기존의 std::map, std::set이 더 적합할 수 있으므로 용도에 따라 적절히 선택하는 것이 중요합니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

C++에서 제안된 고성능 컨테이너 std::hive


1. std::hive란 무엇인가?

  • std::hiveC++ 표준 라이브러리에 아직 포함되지 않았지만 , 표준화 제안서 P0447R15 에서 소개된 새로운 컨테이너입니다.
  • 과거에는 colony라는 이름으로 알려졌으며, 최종 표준에 포함될 경우 std::hive 또는 std::pmr::hive로 명명될 가능성이 있습니다.

글 작성 시점 2025년



2. 주요 특징

  • 항목 설명
    삽입/삭제 최적화 중간 삽입과 삭제가 빠르며, 다른 요소에 영향을 거의 주지 않음
    반복자 안정성 대부분의 삭제/삽입 작업에서도 반복자가 무효화되지 않음
    순차 접근 유지 노드 기반이지만 순차 접근 성능이 우수함
    메모리 단편화 최소화 메모리를 블록 단위로 할당하여 단편화가 적고 캐시 효율이 좋음


3. std::hive의 필요성

  • 기존 C++ 컨테이너의 한계를 보완하기 위해 다음과 같은 목적에서 제안되었습니다:
    • std::vector는 중간 삽입/삭제 성능이 낮고 반복자 무효화가 심각함.
    • std::list는 캐시 친화적이지 않으며 메모리 오버헤드가 큼.
    • std::hive는 반복자 안정성과 삽입/삭제 성능을 모두 확보한 컨테이너로서 양쪽의 장점을 결합.


4. 현재(2025년) C++ 표준에서의 상태

  • std::hive아직 C++ 표준에 포함되지 않았음
  • 하지만 해당 컨셉은 외부 라이브러리인 plf::colony 에서 구현되어 실제 사용 가능
  • 향후 C++26 이후 표준에 채택될 가능성 있음


5. 대안: plf::colony 사용 예시

  • cpp

     #include "plf_colony.h"
     #include <iostream>
     
     int main() {
         plf::colony<int> c;
         c.insert(10);
         c.insert(20);
         c.insert(30);
     
         for (auto it : c)
             std::cout << it << " ";
     }
    
  • 위 코드는 std::vector처럼 순차 접근이 가능하면서도, 삽입/삭제 시 반복자 무효화가 거의 없음



6. 요약

  • std::hive는 반복자 안정성과 효율적 메모리 사용을 위한 새로운 컨테이너.
  • 현재는 plf::colony로 구현되어 실제 사용 가능.
  • C++26 또는 이후 표준에 포함될 수 있음.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

C++ auto 자동 타입 추론의 이해


1. auto란 무엇인가?

  • C++에서 auto는 변수 선언 시 초기화 식(expression)의 타입을 컴파일러가 자동으로 추론 하게 해주는 키워드입니다.

  • C++11부터 도입되었으며, 이후 C++14, C++17에서 기능이 더욱 확장되었습니다.

  • cpp

     auto x = 10;        // int로 추론
     auto y = 3.14;      // double로 추론
     auto s = "hello";   // const char*로 추론
    

  • auto는 반드시 초기화 값이 필요합니다.

  • 타입 추론이 가능하려면 컴파일러가 보고 판단할 대상이 있어야 하기 때문입니다.

  • cpp

     auto x; // ❌ 오류: 초기화 없이는 타입 추론 불가
    


2. auto의 동작 방식

  • 컴파일러는 auto 오른쪽에 있는 초기화 식의 타입을 그대로 추론 합니다.

  • 예시:

  • cpp

     int a = 42;
     auto b = a; // b는 int
     
     std::vector<int> vec;
     auto it = vec.begin(); // it는 vector<int>::iterator
    
  • 함수 반환값이나 표현식의 결과가 복잡할수록 auto는 가독성과 코드 작성의 편의를 더해줍니다.



3. auto의 장점

3.1 복잡한 타입 간소화

  • 복잡한 템플릿 타입, 반복자(iterator) 등을 직접 명시하지 않아도 되어 코드가 훨씬 간결해집니다.

  • cpp

     std::unordered_map<std::string, std::vector<int>>::iterator it = m.begin();
     // ↓
     auto it = m.begin();
    

3.2 유지보수 용이성

  • 코드 내부 타입이 변경되더라도 auto는 자동으로 추론하므로, 변경에 따른 수정이 줄어듭니다.

3.3 템플릿과의 궁합

  • 템플릿 함수나 클래스 내에서는 auto를 사용하면 코드 재사용성과 유연성이 향상됩니다.

  • cpp

     template<typename T>
     void printAll(const T& container) {
         for (auto it = container.begin(); it != container.end(); ++it)
             std::cout << *it << "\n";
     }
    

3.4 명시 불가능한 타입 사용

  • 람다(lambda) 함수의 타입처럼 사용자가 명시할 수 없는 타입auto 없이는 다룰 수 없습니다.

  • cpp

     auto f = [](int x) { return x * 2; };
    


4. auto의 단점과 주의점

4.1 타입 명확성이 떨어질 수 있음

  • auto만으로는 변수의 정확한 타입이 무엇인지 코드만 보고 알기 어려울 수 있습니다.

  • cpp

     auto result = 1 + 2.5; // result는 double
    

4.2 복사와 참조 실수 가능성

  • auto는 기본적으로 값 복사 를 수행합니다.

  • 참조를 원할 경우 auto&, const auto&를 명시해야 합니다.

  • cpp

     std::string s = "hello";
     auto x        = s; // 값 복사 (std::string)
     auto& y       = s; // 참조 (std::string&)
     const auto& z = s; // 상수 참조 (const std::string&)
    

4.3 일관성 저하 가능

  • 모든 변수에 auto를 쓰면 코드 일관성이 떨어지고, 다른 개발자가 코드를 읽기 어려울 수 있습니다.
  • 특히 공개 API에서는 명시적 타입이 바람직 합니다.


5. 정적 타입 명시의 장점

  • 타입이 명확하게 드러나 코드 가독성이 좋음

  • API 문서화나 라이브러리에서 의미 있는 정보를 제공

  • 오타, 복사/참조 실수 방지에 유리

  • cpp

     int count = 0;              // 명확한 기본 타입
     std::vector<int> values;    // 컨테이너 타입 명시
    


6. auto와 정적 타입 비교 정리

  • 항목 auto 사용 명시적 타입 사용
    코드 가독성 간결함 명확함
    복잡한 타입 매우 유리 불편함
    유지보수 타입 변경에 강함 변경 시 수정 필요
    실수 가능성 복사/참조 주의 필요 명확하게 구분됨
    문서 역할 약함 강함


7. 권장 사용 가이드

  • 상황 추천 방식
    반복자, 람다, 복잡한 템플릿 타입 auto 권장
    기본 타입, 문서화를 위한 선언 정적 타입 권장
    참조/복사 명확하게 구분할 때 auto&, const auto& 사용
    템플릿 내부 또는 범용 컨테이너 순회 auto 적극 활용
  • cpp

     // 예시
     auto it = vec.begin(); // ✅ good
     const auto& entry = map["key"]; // ✅ 복사 방지
     int total = 0; // ✅ 명확한 타입
    


8. 마무리

  • autoC++에서 타입을 더 유연하고 간결하게 다룰 수 있도록 도와주는 강력한 도구 입니다.

  • 하지만 모든 상황에서 무조건 사용하기보다는, 코드의 목적과 문맥에 따라 선택적으로 활용 하는 것이 좋습니다.

  • 가독성 vs 명확성 의 균형을 잘 맞추는 것이 auto 사용의 핵심입니다.

  • 필요하다면 이후 decltype, decltype(auto), auto&&, range-based for, structured binding 등 관련 개념도 함께 살펴보시는 것을 추천드립니다.




  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

윈도우(Windows)에서 ObsidianGitHub을 연동하여 협업하는 방법

  • Obsidian은 로컬 마크다운 파일 기반의 지식 관리 도구이며, GitHub을 이용하면 여러 대의 PC에서 버전 관리와 데이터 동기화를 안전하게 할 수 있습니다.
  • 본 글에서는 윈도우 환경에서 ObsidianGitHub을 연동하는 방법과 주의사항을 단계별로 설명합니다.


1. 준비물

1.1 Git for Windows

  • https://git-scm.com 에서 설치합니다.
  • 설치 시 Git Bash 옵션을 선택하면 명령어 입력이 편리합니다.

1.2 GitHub 계정 및 저장소

  • GitHub 계정을 생성하고 private 저장소를 하나 생성합니다.
    • 예: obsidian-vault

1.3 Obsidian

  • Obsidian을 설치합니다.
  • 저장소(vault) 폴더를 C:\Users\USERNAME\Documents\obsidian-vault 와 같이 지정합니다.


2. Git 초기화 및 연결

  • Git Bash를 실행하고 Obsidian 볼트 폴더로 이동합니다.

  •  cd "C:/Users/USERNAME/Documents/obsidian-vault"
     git init
     git remote add origin https://github.com/USERNAME/obsidian-vault.git
    


3. .gitignore 작성

  • Obsidian의 내부 파일 중 버전 관리가 불필요한 항목을 제외하기 위해 볼트 폴더 최상단에 .gitignore 파일을 생성합니다. 아래 내용을 작성합니다.

  •  .obsidian/cache
     .obsidian/workspace
     .DS_Store
     Thumbs.db
    


4. 첫 커밋과 푸시

  • 아래 명령어로 최초 파일을 GitHub에 업로드합니다.

  •  git add .
     git commit -m "초기 커밋"
     git branch -M main
     git push -u origin main
    
  • GitHub 인증이 필요하며, 비밀번호 대신 SSH 키 또는 Personal Access Token 사용을 권장합니다.



5. 다른 PC에서 클론

  • 다른 PC에서 GitHub 저장소를 클론합니다.

  •  cd "C:/Users/USERNAME/Documents"
     git clone https://github.com/USERNAME/obsidian-vault.git
    
  • Obsidian에서 이 폴더를 볼트로 열어 사용합니다.



6. Obsidian Git 플러그인 (선택)

  • ObsidianCommunity Plugins에서 Obsidian Git 을 설치할 수 있습니다.

  • 설정 예시:

    • Pull on startup 활성화
    • Auto push on save 활성화 (선택 사항)
    • 커밋 메시지: Update: {{date}}
  • 이 플러그인을 사용하면 Git 명령어 없이 Obsidian 내에서 pull, commit, push를 클릭으로 처리할 수 있습니다.



7. 주의사항

7.1 작업 규칙

  • 작업을 시작하기 전 반드시 git pull을 실행하고, 작업이 끝나면 git commitgit push를 실행해야 충돌을 방지할 수 있습니다.

7.2 파일 충돌

  • 같은 파일을 여러 PC에서 동시에 수정하면 충돌이 발생할 수 있습니다.
  • 충돌 시 Git의 병합 기능을 사용하거나 수동으로 파일을 수정해야 합니다.

7.3 GitHub 보안

  • GitHub는 데이터를 인터넷 상에 저장하기 때문에 중요한 데이터는 암호화하거나 별도의 보안을 고려해야 합니다.
  • private 저장소 사용과 SSH 키 또는 Personal Access Token을 권장합니다.

7.4 윈도우 경로와 숨김파일

  • Git Bash에서는 경로를 C:/Users/... 또는 /c/Users/... 형태로 표기합니다. 탐색기에서 숨김 파일 표시를 활성화하면 .gitignore 같은 파일을 쉽게 관리할 수 있습니다.


8. 작업 흐름 예시

  • 작업 흐름은 다음과 같습니다.
  •  작업 시작 → git pull → Obsidian 편집 → git commit → git push
    

  • 또는 Obsidian Git 플러그인을 사용하는 경우:
  •  작업 시작 → Obsidian Git 플러그인에서 Pull → 편집 → 플러그인으로 Push
    


9. 정리

  • 윈도우 환경에서도 ObsidianGitHub을 연동하면 안전하고 체계적인 지식 관리를 할 수 있습니다.
  • GitGitHub의 기본 사용법을 숙지하고, 충돌을 방지하기 위한 작업 습관을 들이는 것이 중요합니다.
  • Obsidian Git 플러그인을 활용하면 Git 작업이 더욱 편리해집니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

Qt Bridges : 다양한 언어에서 Qt UI를 연결하는 새로운 기술

Qt World Summit 2025에서 Qt QuickQML의 소프트웨어 설계 및 개발 기능을 확장하여 Python, .NET(C#), Kotlin(Java), Swift, Rust 등 더 많은 언어에서 사용할 수 있도록 하는 새로운 이니셔티브(initiative)인 Qt Bridges를 발표했습니다.



1. Qt Bridges

  • Qt BridgesC++ 기반 Qt UI (QML/Qt Quick) 를 다양한 언어의 백엔드와 쉽게 연결 할 수 있도록 설계된 기술입니다.
  • 기존 Qt가 주로 C++ 또는 Python 바인딩(Binding)을 통해 UI와 백엔드를 연결했다면, Qt BridgesJava, C#, Rust, Swift, Kotlin 같은 언어에서도 Qt UI를 사용할 수 있도록 지원합니다.


2. Qt Bridges의 주요 특징

2.1 UI와 비즈니스 로직의 분리

  • Qt BridgesUI (QML/Qt Quick)와 비즈니스 로직을 완전히 분리하며, 개발자가 백엔드를 선호하는 언어로 구현할 수 있게 합니다.
  • UI와 로직 간 데이터 교환은 고수준 인터페이스로 처리되므로, Qt API를 직접 다룰 필요가 없습니다.

2.2 다양한 언어 지원

  • Qt Bridges는 초기 버전에서 C#, Kotlin/Java, Python, Rust, Swift 를 공식 지원하며, 다른 언어용 브리지도 사용자가 직접 구현할 수 있도록 문서화가 예정되어 있습니다.

2.3 바인딩과는 다른 접근

  • 기존 바인딩(PySide, CXX-Qt)과 달리, Qt BridgesQt 전체 API를 노출하지 않고 QML 프론트엔드와 백엔드 간 연결에 집중합니다.
  • 예를 들어 Rust의 경우 CXX-QtC++ 호출을 허용하지만, Qt BridgesUI 연결에만 초점을 맞춥니다.

2.4 현재 개발 단계

  • Qt Bridges기술 프리뷰(Technology Preview) 단계로, 내부 프로토타입은 존재하며 예제 코드 및 공식 문서화가 진행 중입니다.


3. Qt Bridges가 주목받는 이유

  • 이유 설명
    언어 선택의 자유 안전한 언어로 백엔드를 작성하면서 Qt UI의 강점을 그대로 활용
    생산성 향상 C++의 복잡한 API를 다루지 않고 브리지 인터페이스만으로 UI 연결
    확장성 공식 지원 언어 외에도 추가 브리지 작성 가능 (문서 제공 예정)


4. 커뮤니티 반응

  • Rust, Kotlin, C# 개발자 커뮤니티에서는 Qt Bridges생산성과 안전성 모두를 고려한 새로운 대안 으로 긍정적으로 평가하고 있습니다.
  • 특히 Slint와 같은 Rust UI 툴킷과의 비교에서 Qt의 성숙도와 플랫폼 호환성이 강점으로 꼽히고 있습니다.
  • 또한 기존 CXX-Qt와는 목표가 다르다는 점에서 별도의 기술로 받아들여지고 있습니다.


5. 앞으로의 전망

  • Qt BridgesUI와 로직을 분리하고, C++에 대한 부담을 줄이며 , Qt UI를 폭넓은 언어 생태계에 제공하는 것을 목표로 합니다.
  • 정식 출시 및 문서화가 진행되면 다양한 언어 기반 애플리케이션에서 Qt UI를 더 쉽게 사용할 수 있을 것으로 예상됩니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

C++ promiseshared_future를 활용한 멀티쓰레드 동기화

  • 본 글에서는 C++ 표준 라이브러리의 promise, future, shared_future, async를 활용하여 두 개의 쓰레드를 안전하게 동기화하고 동일한 신호값을 전달하는 방법을 설명합니다.
  • 예제 코드를 중심으로 각 요소의 역할과 동작을 단계적으로 소개합니다.


1. 코드 개요

  • 아래 코드는 두 개의 쓰레드를 비동기로 실행하고, 두 쓰레드가 준비될 때까지 메인 쓰레드가 기다린 뒤 특정 신호값(42)을 전달하는 구조입니다.

  • cpp

     import std;
     
     using namespace std;
     
     int main()
     {
         promise<void> thread1Started, thread2Started;
     
         promise<int> signalPromise;
         auto signalFuture{ signalPromise.get_future().share() };
     
         auto function1{ [&thread1Started, signalFuture] {
             thread1Started.set_value();
             // 파라미터 값이 전달될 때까지 대기
             int parameter{ signalFuture.get() };
             // 전달받은 값(parameter)을 활용한 후속 작업 수행 (생략)
         } };
     
         auto function2{ [&thread2Started, signalFuture] {
             thread2Started.set_value();
             // 파라미터 값이 전달될 때까지 대기
             int parameter{ signalFuture.get() };
             // 전달받은 값(parameter)을 활용한 후속 작업 수행 (생략)
         } };
     
         // 두 함수를 비동기 쓰레드로 실행
         auto result1{ async(launch::async, function1) };
         auto result2{ async(launch::async, function2) };
     
         // 두 쓰레드가 모두 시작될 때까지 대기
         thread1Started.get_future().wait();
         thread2Started.get_future().wait();
     
         // 두 쓰레드가 모두 준비되면 신호값 전달
         signalPromise.set_value(42);
     }
    


2. 주요 구성 요소

2.1 promisefuture

  • promise<void> thread1Started, thread2Started

    • 각 쓰레드가 시작되었음을 메인 쓰레드에 알리기 위해 사용됩니다.
  • promise<int> signalPromise

    • 메인 쓰레드가 두 쓰레드에 전달할 신호값을 약속합니다.
  • shared_future<int> signalFuture

    • signalPromise에서 생성된 future를 공유 가능한 형태로 변환하여 두 쓰레드가 동일한 값을 받을 수 있도록 합니다.


2.2 람다 함수

  • function1, function2
    • 각 함수는 자신의 시작을 set_value()로 알리고, signalFuture.get()을 호출하여 신호값을 기다립니다. 신호값이 도착하면 그 값을 읽어 후속 작업을 진행합니다(// ... 부분).


2.3 비동기 실행

  • async(launch::async, function1), async(launch::async, function2)
    • 두 함수는 별도의 쓰레드에서 즉시 실행되며, 메인 쓰레드는 두 쓰레드의 시작을 기다립니다.


2.4 메인 쓰레드의 동작

  • thread1Started.get_future().wait();, thread2Started.get_future().wait();

    • 두 쓰레드가 준비될 때까지 메인 쓰레드는 대기합니다.
  • signalPromise.set_value(42);

    • 두 쓰레드에 42라는 값을 전달하여 대기 상태를 해제합니다.


3. 동작 흐름 요약

  • 메인 쓰레드는 두 쓰레드가 모두 준비될 때까지 대기합니다.
  • 두 쓰레드는 signalFuture.get()으로 동일한 신호값을 기다립니다.
  • 메인 쓰레드가 42를 전달하면 두 쓰레드는 값을 받고 대기에서 깨어납니다.
  • shared_future를 사용하여 두 쓰레드가 동일한 값에 안전하게 접근할 수 있습니다.


4. 특징 및 활용 포인트

  • shared_future를 사용하여 동일한 값을 여러 쓰레드에 안전하게 제공합니다.
  • promisefuture로 명확한 쓰레드 시작, 대기, 신호 전달 흐름을 구현할 수 있습니다.
  • asynclaunch::async로 즉시 실행되는 비동기 쓰레드를 생성할 수 있습니다.

  • 이 구조는 쓰레드 간 초기화 동기화, 동일 신호 전달, 단일 신호 발생기 구현 등에 활용될 수 있습니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

Modern C++에서 std::asyncstd::future를 활용한 비동기 예외 처리

  • C++ 표준 라이브러리는 std::asyncstd::future를 이용하여 비동기 프로그래밍을 지원합니다.
  • 특히 비동기 함수에서 발생한 예외를 안전하게 메인 스레드로 전달하는 기능 은 복잡한 프로그램에서도 오류를 효과적으로 처리할 수 있도록 돕습니다.
  • 이 글에서는 std::async를 이용한 비동기 실행과 std::future를 통한 예외 처리 방법을 코드와 함께 설명합니다.

  • cpp

     import std;
     
     using namespace std;
     
     int calculate()
     {
     	throw runtime_error{ "Exception thrown from calculate()." };
     }
     
     int main()
     {
     	// Use the launch::async policy to force asynchronous execution.
     	auto myFuture{ async(launch::async, calculate) };
     
     	// Do some more work...
     
     	// Get the result.
     	try {
     		int result{ myFuture.get() };
     		println("Result: {}", result);
     	} catch (const exception& ex) {
     		println("Caught exception: {}", ex.what());
     	}
     }
    


1. 예외를 발생시키는 함수

  • cpp

     int calculate()
     {
    	 // 예외를 강제로 던짐
    	 throw runtime_error{ "Exception thrown from calculate()." };
     }
    

  • calculate() 함수는 std::runtime_error 예외를 발생시킵니다.
  • 이 함수는 나중에 비동기적으로 실행됩니다.


2. 비동기 실행과 std::future 객체

  • cpp

      auto myFuture{ async(launch::async, calculate) };
    

  • std::asynccalculate 함수를 비동기적으로 실행하며, 실행 결과를 반환받기 위한 std::future 객체를 생성합니다.
  • launch::async 정책을 사용하면 해당 함수는 즉시 새로운 스레드에서 실행됩니다.


3. 결과 가져오기와 예외 처리

  • cpp

     try {
     	int result{ myFuture.get() };
     	println("Result: {}", result);
     } catch (const exception& ex) {
     	println("Caught exception: {}", ex.what());
     }
    

  • future.get()을 호출하면 비동기 함수의 실행 결과를 가져오거나 예외를 전달받습니다.
  • 만약 비동기 함수에서 예외가 발생했다면, 해당 예외는 .get() 호출 시점에 다시 던져집니다.
  • 이 예제를 통해 메인 스레드에서 안전하게 예외를 처리할 수 있습니다.


4. 요약

  • (1) launch::async 정책을 사용하면 함수가 즉시 별도 스레드에서 실행됩니다.
  • (2) std::future 객체는 결과뿐 아니라 예외도 전달받는 통로입니다.
  • (3) future.get()을 호출할 때 예외가 다시 발생하며, 반드시 try-catch로 감싸야 안전합니다.
  • (4) 예외 처리를 하지 않거나 get()을 호출하지 않으면 예외는 무시됩니다.

  • 이 구조는 병렬 처리 상황에서도 안전한 예외 전파를 가능하게 하며, 안정적인 멀티스레드 프로그래밍의 기반이 됩니다.



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

Modern C++에서 복수 쓰레드(thread)를 동일 시간에 동시 실행하는 방법

  • 복수 개의 쓰레드를 거의 동일한 시점에 동시에 실행 시키는 것은 병렬 작업에서 매우 중요합니다.
  • Modern C++에서는 이를 위해 다양한 동기화 기법들을 제공합니다.
  • 본 문서에서는 C++11부터 C++20까지 사용 가능한 표준 기능들과 외부 라이브러리를 포함한 6가지 대표적인 방법 을 예제와 함께 소개합니다.


1. std::barrier 사용 (C++20 이상)

  • C++20에서 도입된 std::barrier는 지정된 수의 쓰레드가 모두 도달할 때까지 기다린 후, 동시에 일괄 실행 할 수 있도록 하는 가장 정밀한 동기화 도구입니다.

  • cpp

     #include <iostream>
     #include <vector>
     #include <thread>
     #include <barrier> // C++20
     
     constexpr int THREAD_COUNT = 4;
     std::barrier sync_point(THREAD_COUNT);
     
     void worker(int id) {
         std::cout << "Thread " << id << " ready.\n";
         sync_point.arrive_and_wait();  // barrier에 도달한 후 대기
         std::cout << "Thread " << id << " started work!\n";
     }
     
     int main() {
         std::vector<std::thread> threads;
         for (int i = 0; i < THREAD_COUNT; ++i)
             threads.emplace_back(worker, i);
     
         for (auto& t : threads)
             t.join();
     }
    
  • 특징: 모든 쓰레드가 arrive_and_wait()에 도달해야만 진행되므로 시작 시점이 매우 정확합니다.



2. std::condition_variable 활용 (C++11 이상)

  • 모든 쓰레드를 조건 변수에 대기시키고, 한 번의 notify_all() 호출로 일제히 시작 하게 할 수 있습니다.

  • cpp

     #include <iostream>
     #include <thread>
     #include <mutex>
     #include <vector>
     #include <condition_variable> // C++11	
     
     constexpr int THREAD_COUNT = 4;
     std::mutex mtx;
     std::condition_variable cv;
     bool start = false;
     
     void worker(int id) {
         std::unique_lock<std::mutex> lock(mtx);
         cv.wait(lock, [] { return start; });  // start 신호를 기다림
         std::cout << "Thread " << id << " started work!\n";
     }
     
     int main() {
         std::vector<std::thread> threads;
         for (int i = 0; i < THREAD_COUNT; ++i)
             threads.emplace_back(worker, i);
     
         std::this_thread::sleep_for(std::chrono::milliseconds(100));
     	// 메인 스레드가 다른 쓰레드들이 준비할 시간을 주기 위해 삽입
     
         {
             std::lock_guard<std::mutex> lock(mtx);
             start = true;
         }
         cv.notify_all();  // 전체 시작 신호 전송
     
         for (auto& t : threads)
             t.join();
     }
    
  • 특징: 유연성이 높으나 락과 조건 변수 구조가 필요하여 상대적으로 복잡합니다.



3. std::promise / std::future 기반 방식 (C++11 이상)

  • 공통 std::shared_future를 이용해 동시에 신호를 전달 할 수 있습니다.

  • cpp

     #include <iostream>
     #include <thread>
     #include <future>
     #include <vector>
     
     constexpr int THREAD_COUNT = 4;
     
     void worker(int id, std::shared_future<void> ready_future) {
         ready_future.wait();  // 동시 시작 신호 대기
         std::cout << "Thread " << id << " started work!\n";
     }
     
     int main() {
         std::promise<void> starter;
         std::shared_future<void> ready_future = starter.get_future();
     
         std::vector<std::thread> threads;
         for (int i = 0; i < THREAD_COUNT; ++i)
             threads.emplace_back(worker, i, ready_future);
     
         std::this_thread::sleep_for(std::chrono::milliseconds(100));
     	// 메인 스레드가 다른 쓰레드들이 준비할 시간을 주기 위해 삽입
     	
         starter.set_value();  // 모든 쓰레드 시작
     
         for (auto& t : threads)
             t.join();
     }
    
  • 특징: 구조가 깔끔하고 미래 값을 활용한 동기화에 적합합니다.



4. std::atomic 기반 스핀락 방식 (C++11 이상)

  • 원자 변수 ready가 설정될 때까지 계속 반복 확인(busy-wait) 하며 대기하는 방식입니다.

  • cpp

     #include <iostream>
     #include <thread>
     #include <atomic>
     #include <vector>
     
     constexpr int THREAD_COUNT = 4;
     std::atomic<bool> ready{false};
     
     void worker(int id) {
         while (!ready.load(std::memory_order_acquire)) {
             std::this_thread::yield();  // CPU 점유 최소화
         }
         std::cout << "Thread " << id << " started work!\n";
     }
     
     int main() {
         std::vector<std::thread> threads;
         for (int i = 0; i < THREAD_COUNT; ++i)
             threads.emplace_back(worker, i);
     
         std::this_thread::sleep_for(std::chrono::milliseconds(100));
         ready.store(true, std::memory_order_release);
     
         for (auto& t : threads)
             t.join();
     }
    
  • 특징: 단순하지만 CPU를 많이 사용할 수 있음 (yield로 완화 가능).



5. 외부 라이브러리: Boost Barrier

  • boost::barrierC++20 이전에도 barrier 기능을 사용할 수 있도록 해줍니다.

  • cpp

     #include <boost/thread.hpp>
     #include <boost/thread/barrier.hpp>
     #include <iostream>
     
     boost::barrier sync_barrier(4);
     
     void worker(int id) {
         std::cout << "Thread " << id << " ready.\n";
         sync_barrier.wait();  // 모든 쓰레드가 도달하면 통과
         std::cout << "Thread " << id << " started work!\n";
     }
    
  • 특징: C++11 환경에서도 barrier처럼 쓰레드를 동시에 시작 가능



6. 기타 대안: std::latch, Intel TBB 등

  • std::latch (C++20) : 한 번만 사용할 수 있는 barrier
  • Intel TBB : task_arena, task_group 기반 병렬 실행 가능
  • OpenMP : 병렬 루프 처리에 특화

  • 이들은 특수한 환경이나 고성능 컴퓨팅에서 주로 사용됩니다.


7. 요약 비교

  • 방법 표준/라이브러리 특징 C++ 버전
    std::barrier 표준 정확하고 구조적 동기화 C++20
    std::condition_variable 표준 유연하나 복잡 C++11
    std::promise/future 표준 직관적인 동기화 C++11
    std::atomic 표준 매우 단순, CPU 점유 높음 C++11
    Boost Barrier Boost C++20 이전에 barrier 구현 C++03~
    std::latch, TBB 표준 및 외부 특수 목적, 고성능 병렬처리 C++20+, 외부


8. 정리

  • 복수 개의 쓰레드를 동시에 실행시키기 위해선 작업 준비 단계에서 미리 생성하고, 특정 지점에서 일제히 실행하는 동기화가 필요합니다.
  • 작업의 특성과 정밀도 요구에 따라 적절한 방법을 선택하십시오.

  • 정밀한 동기화가 필요하다면std::barrier, std::latch
  • C++11만 가능하다면condition_variable, promise/future
  • CPU 점유가 문제가 안 된다면atomic 기반
  • 외부 라이브러리 가능하다면 → Boost, TBB 고려



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

C++20 비동기 연산 std::async

1. 개요

  • C++20 이상에서 제공하는 std::async를 사용하여 함수 calculateSum을 비동기적으로 실행하고, 그 결과를 std::future 객체를 통해 받아오는 간단한 예제입니다.

  • cpp

     import std;
     
     using namespace std;
     
     int calculateSum(int a, int b)
     {
     	return a + b;
     }
     
     int main()
     {
     	auto myFuture{ async(calculateSum, 39, 3) };
     	//auto myFuture{ async(launch::async, calculateSum, 39, 3) };
     	//auto myFuture{ async(launch::deferred, calculateSum, 39, 3) };
     
     	// Do some more work...
     
     	// Get the result.
     	int result{ myFuture.get() };
     	println("Result: {}", result);
     }
    


2. 헤더 임포트 및 네임스페이스 사용

  • cpp

     import std;
     using namespace std;
    

  • import std;C++20 모듈 시스템을 사용하여 표준 라이브러리를 가져옵니다.
  • using namespace std;를 통해 이후 코드에서 std:: 생략 가능하게 합니다.


3. 합을 계산하는 함수 정의

  • cpp

     int calculateSum(int a, int b)
     {
     	return a + b;
     }
    
  • 두 정수 ab를 받아 합계를 반환하는 단순한 함수입니다.



4. main 함수: 비동기 실행

  • cpp

     auto myFuture{ async(calculateSum, 39, 3) };
    

  • std::asynccalculateSum(39, 3) 호출을 비동기적으로 실행하고, 그 결과를 담은 std::future<int>를 반환합니다.

  • 실행 정책을 명시하지 않으면 컴파일러는 launch::async(새 쓰레드에서 즉시 실행) 또는 launch::deferred(지연 실행) 중 선택합니다.


4.1. 참고: 실행 정책 직접 지정 예시

  • cpp

     // 즉시 새 쓰레드에서 실행
     auto myFuture{ async(launch::async, calculateSum, 39, 3) };
     
     // 호출 시점까지 지연됨
     auto myFuture{ async(launch::deferred, calculateSum, 39, 3) };
    


5. 결과 대기 및 출력

  • cpp

     int result{ myFuture.get() };
     println("Result: {}", result);
    

  • myFuture.get()을 호출하면 계산 결과가 준비될 때까지 기다리고 값을 받아옵니다.
  • printlnstd::print 모듈(C++20)에서 제공되며, 포매팅된 문자열을 출력합니다. 결과는 다음과 같이 출력됩니다:
    • Result: 42


6. 요약

  • 요소 설명
    std::async 함수를 비동기적으로 실행
    std::future 비동기 결과를 받기 위한 객체
    get() 결과가 준비될 때까지 대기 후 값을 반환
    println() 포맷 문자열을 사용하는 출력 함수



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

C++20 std::packaged_taskstd::jthread를 이용한 비동기 처리


1. 개요

  • C++20 이후에서 사용할 수 있는 std::packaged_task, std::future, std::jthread를 활용하여 두 수의 합을 비동기적으로 계산하는 예제입니다.
    • std::packaged_task는 특정 함수 호출을 지연시켜 나중에 실행할 수 있게 해주며, 결과는 std::future를 통해 받아올 수 있습니다.
    • std::jthreadC++20에서 도입된 join 자동 관리형 쓰레드(thread) 입니다.


2. 코드

  • cpp

     #include <iostream>
     #include <thread>
     #include <future>
     #include <utility>
     #include <format> // C++20부터 사용 가능
     
     // 2.1. 두 정수를 더하는 함수 정의
     int calculateSum(int a, int b)
     {
         return a + b;
     }
     
     int main()
     {
         // 2.2. calculateSum 함수를 감싸는 packaged_task 생성
         std::packaged_task<int(int, int)> task{ calculateSum };
     
         // 2.3. packaged_task의 실행 결과를 받을 future 객체 생성
         std::future<int> theFuture = task.get_future();
     
         // 2.4. std::jthread를 통해 별도 쓰레드에서 task 실행
         //      jthread는 자동으로 join() 호출되므로 편리함
         std::jthread theThread{ std::move(task), 39, 3 };
     
         // 2.5. 메인 쓰레드는 여기서 다른 작업을 수행할 수 있음...
     
         // 2.6. 별도 쓰레드에서 실행된 결과를 future로부터 가져옴
         int result = theFuture.get();
     
         // 2.7. 결과 출력 (C++20 이상에서 std::format 사용 가능)
         std::cout << std::format("Result: {}\n", result);
     
         // 2.8. jthread는 소멸 시 자동으로 join되므로 별도 처리 불필요
     }
    


3. 주요 구성 요소 설명

3.1 std::packaged_task

  • 특정 함수 호출을 패키지화 하여 나중에 실행할 수 있도록 도와주는 템플릿 클래스입니다.
  • 실행 시 반환값을 std::future를 통해 받을 수 있습니다.

3.2 std::future

  • 비동기 작업의 결과를 나중에 받아오는 통로 입니다.
  • get() 호출 시 작업이 완료되지 않았다면 대기 하게 됩니다.

3.3 std::jthread

  • C++20에서 새롭게 도입된 자동 join 쓰레드 입니다.
  • std::thread와 달리 소멸자에서 join() 또는 detach()를 자동으로 수행하여 예외 안정성을 높입니다.


4. 실행 결과

  •  Result: 42
    
  • calculateSum(39, 3) 함수가 별도 쓰레드에서 실행되고, 그 결과인 42가 출력됩니다.



5. 정리

  • 이 예제는 다음을 학습하는 데 도움이 됩니다:

    • std::packaged_task를 통한 비동기 실행 구조 구성
    • std::future를 사용한 결과 수신
    • std::jthread를 이용한 간단하고 안전한 쓰레드 관리
  • 이러한 구성은 복잡한 비동기 로직을 간결하고 안전하게 관리할 수 있는 기초를 제공합니다.




  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

Modern C++에서 std::promisestd::future를 분리 설계한 이유


1. 서론

  • C++ 표준 라이브러리는 멀티스레드 환경에서의 안전한 값 전달과 동기화를 위해 std::promisestd::future를 별도로 제공합니다.
  • 이 둘은 단순히 값 설정과 조회의 기능뿐 아니라, 구조적, 철학적으로 명확한 이유에 의해 나누어 설계되었습니다.


2. std::promisestd::future의 기본 역할

  • std::promise<T> : 값을 설정(set_value) 하는 쪽 → 생산자 역할
  • std::future<T> : 값을 수신(get) 하는 쪽 → 소비자 역할

이러한 역할 분리는 단방향 통신 구조에서 각각의 책임을 명확히 구분하는 데 도움이 됩니다.



3. 분리 설계의 이유


3.1. 역할 분리로 인한 설계 명확성

  • 값을 생성하는 쪽과 소비하는 쪽을 분리함으로써, 멀티스레드 환경에서의 책임과 데이터 흐름을 명확하게 합니다.

3.2. 스레드 간 안전한 통신

  • promise는 단 한 번만 값을 설정할 수 있습니다.

  • future는 단 한 번만 그 값을 받을 수 있습니다.

  • 이 구조는 race condition(경쟁 조건) 을 방지하며, 명시적이고 안전한 동기화 방식을 제공합니다.


3.3. 비동기 동기화에 적합한 구조

  • future.get()은 결과가 설정될 때까지 자동으로 블로킹됩니다.
  • 이는 필요할 때까지 기다릴 수 있는 소비자자유롭게 결과를 설정하는 생산자 간의 이상적인 비동기 협력 구조를 만듭니다.

3.4. 낮은 결합도와 유연성 확보

  • promise는 스레드에 전달(move)되어 다른 문맥에서 사용 가능합니다.

  • future는 메인 스레드나 호출자 스레드에 남아 기다릴 수 있습니다.

  • 이로 인해 생산자와 소비자의 결합도가 낮아지고, 유연한 스레드 설계가 가능해집니다.



4. 요약

  • 목적 설명
    역할 분리 생산자(promise)와 소비자(future)를 나누어 책임 명확화
    스레드 안전성 단일 설정·단일 수신 구조로 경쟁 조건 방지
    비동기 동기화 결과를 기다리는 안전한 구조 제공
    낮은 결합도 구성 요소를 독립적으로 관리 가능


5. 예제 코드

  • cpp

    #include <iostream>
    #include <thread>
    #include <future>
    
    // 비동기 작업을 수행할 함수
    void doWork(std::promise<int> thePromise)
    {
        // 작업 수행 (예시로 42를 결과로 설정)
        thePromise.set_value(42);
    }
    
    int main()
    {
        // promise 객체 생성: 결과를 저장할 용도
        std::promise<int> myPromise;
    
        // future 객체를 얻음: 나중에 결과를 받을 수 있음
        std::future<int> theFuture = myPromise.get_future();
    
        // 스레드를 생성하고, promise를 이동시켜 전달
        std::jthread theThread{ doWork, std::move(myPromise) };
    
        // 메인 스레드에서 다른 작업 가능 (생략)
    
        // 스레드에서 설정한 결과값을 future를 통해 받음
        int result = theFuture.get();
        std::println("Result: {}", result);
    
        // jthread는 자동으로 join() 호출됨 → 명시적 join 불필요
    }
    

6. 정리

  • std::promisestd::future의 분리 설계는 단순한 기술적 결정이 아니라, 현대 C++에서의 안전하고 효율적인 병렬 프로그래밍을 위한 구조적 설계 철학 입니다.

  • 이를 통해 개발자는 명확하고 예측 가능한 비동기 로직을 작성할 수 있습니다.




  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형
반응형

C++20 counting_semaphore 동시 실행 스레드(thread) 제한


1. 예제 코드

  • C++20std::counting_semaphore를 사용하여 최대 n개의 스레드(thread)만 동시에 임계 영역(ciritical section)에 접근하도록 제한합니다.

  • cpp

      #include <iostream>
      #include <vector>
      #include <thread>
      #include <semaphore>
      #include <chrono>
      #include <print>  // C++23에서 std::print 사용 시 필요
      
      int main()
      {
          // 동시에 4개까지 허용되는 counting_semaphore 생성
          std::counting_semaphore<4> semaphore{ 4 };
      
          std::vector<std::jthread> threads;
      
          for (int i{ 0 }; i < 10; ++i) {
     	 
              threads.emplace_back([&semaphore, i]() {
     		 
                  semaphore.acquire(); // 세마포어 슬롯 하나 획득
      
                  // ▼ 임계 영역 시작 ▼
                  std::print("Thread {} entered critical section.\n", i);
                  std::this_thread::sleep_for(std::chrono::seconds(5)); // 5초 대기
                  std::print("Thread {} exiting.\n", i);
                  // ▲ 임계 영역 종료 ▲
      
                  semaphore.release(); // 세마포어 슬롯 반환
              });
          }
      
          // std::jthread 사용 시 소멸자에서 자동 join 수행
      }
    


2. 설명

  • std::counting_semaphore<4> semaphore{4};
    • 최대 4개의 스레드만 acquire()를 통해 임계 영역에 진입할 수 있습니다.
  • acquire()
    • 현재 세마포어 자원이 없으면 대기합니다. 자원이 있을 경우 감소하고 통과합니다.
  • release()
    • 세마포어 자원을 하나 반환하여 대기 중인 다른 스레드가 진입할 수 있도록 합니다.
  • std::jthread
    • C++20jthread는 자동으로 join()을 호출하므로 수동 정리가 필요 없습니다.


3. 실행 결과

  • 출력은 4개씩 묶여 순차적으로 다음과 같이 나타날 수 있습니다:

  •  Thread 0 entered critical section.
     Thread 1 entered critical section.
     Thread 2 entered critical section.
     Thread 3 entered critical section.
     
     ... 5초 후 ...
     
     Thread 0 exiting.
     Thread 1 exiting.
     Thread 2 exiting.
     Thread 3 exiting.
     
     Thread 4 entered critical section.
     ...
    



  • 도움이 되셨으면 하단의 ❤️ 공감 버튼 부탁 드립니다. 감사합니다! 😄
  • 일부 모바일 환경에서는 ❤️ 버튼이 보이지 않습니다.

728x90
반응형

+ Recent posts