[C++] 쓰레딩(Threading) 라이브러리

Date:     Updated:

카테고리:

태그:

이 글은 C++의 쓰레딩(Threading) 라이브러리를 공부하고 정리한 글입니다.
쓰레딩 라이브러리는 os마다 다르게 구현했던 멀티쓰레딩을 통합시킨 라이브러리이다.

쓰레딩 라이브러리

  • 표준 C++ 쓰레드
  • os마다 다르게 구현했던 멀티쓰레딩을 통합시킨 라이브러리
  • 이동(move) 가능
  • 복사 불가능

    std::thread 생성자

    thread() noexcept;
    
    template<class Function, class... Args>
    explicit thread(Function&& f, Args&&... args);
    
    thread(thread&& other) noexcept;
    
    • 새 쓰레드 개체를 만든다
    std::thread emptyThread;
    std::thread printThread(PringMessage, 10);        // void PrintMessage(int count);
    
    std::thread movedThread(std::move(printThread));  // OK. printThread는 더 이상 쓰레드가 아님
    

    std::thread::join()

    void join();
    
    • 쓰레드 개체가 끝날 때까지 현재 쓰레드를 멈춰 놓는다
    • 이 함수를 호출한 후 쓰레드 개체를 안전하게 소멸시킬 수 있음
    std::thread thread(PrintMessage);     // void PrintMessage() {};
    thread.join();
    

    std::thread::get_id()

    std::thread::id get_id() const noexcept;
    
    • 쓰레드 ID를 반환한다
    std::thread thread(PrintMessage);     // void PrintMessage() {};
    std::thread::id threadID = thread.get_id();
    

    std::thread::detach()

    void detach();
    
    • 쓰레드 개체에서 쓰레드를 떼어 낸다
    • 떼어진 쓰레드는 메인 쓰레드와 무관하게 독립적으로 실행됨
    std::thread thread(PrintMessage);     // void PrintMessage() {};
    thread.detach();
    

    std::thread::joinable()

    bool joinable() const noexcept;
    
    • 쓰레드가 실행 중인 활성 쓰레드인지 아닌지 확인한다
    std::thread thread(PrintMessage);     // void PrintMessage() {};
    if(therad.joinable())
    {
      // ...
    }
    

    std::ref()

    template<class T>
    std::reference_wrapper<T> ref(T& t) noexcept;
    
    • T의 참조를 내포한 reference_wrapper 개체를 반환한다
    • 헤더에 정의돼 있음
      • 를 인클루드하면, 을 인클루드할 필요 없음
    // void Sum(const std::vector<int>& list, int& result);
    std::thread thread(Sum, list, std::ref(result));
    

    std::this_thread::sleep_for()

    template<class Rep, class Period>
    void sleep_for(const std::chrono::duration<Rep, Period>& sleep_duration);
    
    • 최소 sleep_duration만큼의 시간 동안 현재 쓰레드의 실행을 멈춘다
    std::this_thread::sleep_for(std::chrono::seconds(1));
    

    std::this_thread::yield()

    void yield() noexcept;
    
    • sleep과 비슷하나, sleep이 쓰레드를 일시 정지 상태로 바꾸는 반면, yiled는 계속 실행 대기 상태
    • sleep_for(std::chrono::seconds(0)) 대신 yield()를 사용하는 것이 좋다
      • sleep같은 경우 쓰레드를 멈추고 다시 가동하는 순간에 오버헤드가 생기고 느려진다
    std::this_thread::sleep_for(std::chrono::seconds(1));
    

    std::mutex 생성자

    1. constexpr mutex() noexcept;
    2. mutex(const mutex&) = delete;
    
    1. 뮤텍스를 만든다
    2. 복사 생성자는 delete 처리됨
    std::mutex mutex;
    

    std::mutex::lock()

    void lock();
    
    • 뮤텍스를 잠근다
    • 동일한 쓰레드에서 두 번 잠그면 데드락(deadlock) 발생
      • 꼭 그렇게 해야 된다면, std::recursive_mutex를 사용
    std::mutex mutex;
    
    mutex.lock();
    

    std::mutex::unlock()

    void unlock();
    
    • 뮤텍스 잠금을 푼다
    • 현재 쓰레드에서 잠긴 적이 없을 때의 행동은 정의되지 않음
    std::mutex mutex;
    
    mutex.lock();
    mutex.unlock();
    

    std::scoped_lock()

    template<class... MutexTypes>
    class scoped_lock;
    
    • 매개변수로 전달된 뮤텍스(들)을 내포하는 개체를 만듬
    • 개체 생성시에 뮤텍스를 잠그고 범위(scope)를 벗어나 소멸될 때 품
    • 데드락을 방지
    • C++14의 경우, std::lock_guard를 사용할 수 있으나 이 때는 뮤텍스를 하나만 전달 가능
    std::scoped_lock<std::mutex> lock(mutex);
    std::scoped_lock<std::mutex, std::mutex> locks(mutex1, mutex2);
    std::scoped_lock<std::mutex, std::mutex, std::mutex> locks(mutex1, mutex2, mutex3);
    

    std::condition_variable()

    • 이벤트 개체
    • 신호를 받을 때까지 현재 쓰레드의 실행을 멈춤
    • notify_one(), notify_all()
      • 멈춰 놓은 쓰레드 하나 또는 전부를 다시 실행시킴
    • wait(), wait_for(), wait_until()
      • 조건 변수의 조건을 충족시킬때까지 또는 일정 시간 동안 현재 쓰레드의 실행을 멈춤
    • std::unique_lock을 사용해야 함

    std::unique_lock

    • 기본적으로 scoped lock
    • 생성시에 lock을 잠그지 않을 수도 있음(두 번째 매개변수로 std::defer_lock을 전달할 것)
    • std::recursive_mutex와 함께 써서 재귀적으로 잠글 수 있음
    • 조건 변수에 쓸 수 있는 유일한 lock
    std::scoped_lock<std::mutex> lock(mutex);
    std::scoped_lock<std::mutex, std::mutex> locks(mutex1, mutex2);
    std::scoped_lock<std::mutex, std::mutex, std::mutex> locks(mutex1, mutex2, mutex3);
    

    std::condition_variable::wait()

    1. void wait(std::unique_lock<std::mutex>& lock);
      
    2. template<class Predicate>
    2. void wait(std::unique_lock<std::mutex>& lock, Predicate pred);
    
    • 현재 쓰레드 뮤텍스의 잠금을 풀고 notify_one() 또는 notify_all()을 기다린다
    • 깨어나면, 뮤텍스를 다시 잠근다
    • ntiofy_xxx()가 wait()보다 먼저 호출되면, 해당 쓰레드는 영원히 기다린다

    • 2번째 경우
    • 아래와 같음
      while (!pred())
      {
          wait(lock);
      }
      
    • 두가지 용도로 사용
      • 잘못 깨어날 위험을 줄임
      • pred()는 잠긴 두 쓰레드 모두에서 접근할 수 있는 bool 변수의 역할을 함
    • lock만 쓰는 것은 충분하지 않음
      sEvent.wait(lock);
      
    • 조건변수가 신호(signal)을 받기 전에 대기 상태에 들어가는 것을 보장할 수 없다면 항상 bool 조건과 lock을 같이 사용할 것
      sEvent.wait(lock, [] { return !sQueue.empty(); });
      


참조

포큐아카데미 C++ 언매니지드 프로그래밍



💻 열심히 공부해서 작성 중이니 오류나 틀린 부분이 있을 경우 
  언제든지 댓글 혹은 메일로 알려주시면 감사하겠습니다! 😸

맨 위로 이동하기

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기