c++ std::thread

3 분 소요

std::thread

윈도우와 리눅스에서 각각의 스레드 생성 방법이 있었지만, std::thread가 C++11 부터 등장하였다. thread는 오브젝트에 할당되자마자 즉시 실행된다. (OS의 scheduling delay 이후 실행된다.) thread Syncronization은 std::mutexstd::atomic등을 이용하여 구현 가능하다.

Member classes

  • id: represents the id of a thread

Member functions

  • (constructor)
  • (destructor)
  • operator=
  • 지정한 개체의 스레드를 현재 개체에 연결한다.
  • joinable
  • 스레드가 활성 스레드인지 확인한다.
  • get_id
  • 스레드의 고유 식별자를 반환한다.
  • native_handle
  • 뮤텍스 핸들을 나타내는 구현별 형식을 반환한다. 스레드 핸들은 구현별 방식으로 사용할 수 있다.
  • hardware_concurrency
  • 하드웨어 스레드 컨텍스트 수의 추정치를 반환한다. 정적 매서드다.
  • join
  • 호출 개체와 연결된 실행 스레드가 완료될 때까지 차단한다.
  • detach
  • 스레드 개체에서 스레드를 떼어낸다. (관련 스레드를 분리한다. 운영 체제가 스레드 리소스를 해제한다.)
  • swap
  • 개체 상태를 지정된 스레드 개체의 상태와 바꾼다.

Example

함수 포인터로 스레드 만들기

아래 함수가 하나 있다.
    void counter(int id, int numIterations)
    {
        for (int i = 0; i < numIterations; ++i)
        {
            std::cout << "Counter " << id << " has value " << i << std::endl;
        }
    }
인수를 2개로 가지는 함수임으로 counter를 수행하는 스레드 A를 다음과 같이 만들 수 있다.
    std::thread A(counter, 1, 6);
thread클래스 생성자는 가변 인수 템플릿이기 때문에 인수 개수를 원하는 만큼 지정할 수 있다.
현재 시스템에서 thread객체가 실행 가능한 상태에 있을 때 조인 가능(joinable)하다고 표현한다. 디폴트로 생성된 thread객체는 조인 불가능(unjoinable)하다. 조인 가능한 thread 객체를 제거하려면 먼저 그 객체의 joint()이나 detach()부터 호출해야 한다. join()을 호출하면 그 스레드는 블록되는데, 그 스레드가 작업을 마칠 때까지 기다린다. detach()를 호출하면 thread 객체를 OS 내부의 스레드와 분리한다. 그래서 OS 스레드는 독립적으로 실행된다. joint()과 detach()메서드 모두 스레드를 조인 불가능한 상태로 전환시킨다. 조인 가능한 thread 객체를 제거하면 그 객체의 소멸자는 std::terminate()를 호출해서 모든 스레드뿐만 아니라 애플리케이션마저 종료시킨다.
NOTE_ 스레드 함수에 전달한 인수는 항상 그 스레드의 내부 저장소에 복제된다. 
인수를 레퍼런스로 전달하고 싶다면 <functional> 함수에 정의된 std::ref() 나 cref()를 사용한다.

함수 객체로 스레드 만들기

클래스는 함수 객체로 만들려면 operator() 를 구현해야 한다.
class Counter
{
    public:
        Counter(int id, int numIterations) : mId(id), mNumIterations(numIterations)
        {
        }
        void operator()() const
        {
            for (int i = 0; i < mNumIterations; ++i)
            {
                std::cout << "Counter " << mId << " has value " << i << std::endl;
            }
        }
    
    private:
        int mId;
        int mNumIterations;
};
    
int main(int argc, char *argv[])
{
    // 유니폼 초기화
    std::thread t1{Counter{1, 20}};
    // 네임드 인스턴스 초기화
    Counter c(2, 12);
    std::thread t2(c);
    // 임시 객체사용
    std::thread t3(Counter(3, 10));

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

    return 1;
}
  • 첫번째 방법은 유니폼 초기화로 처리한다. Counter생성자에 인수를 지정해서 인스턴스를 생성하면 그 값이 중괄호로 묶인 thread생성자 인수로 전달된다.
  • 두번째 방법은 Counter 인스턴스를 일반 변수처럼 네임드 인스턴스로 정의하고, 이를 thread 클래스의 생성자로 전달한다.
  • 세번째 방법은 Counter 인스턴스를 생성해서 이를 thread 클래스 생성자로 전달하는 점에서 첫 번째와 비슷하지만, 중괄호가 아닌 소괄호로 묶는 점이 다르다.
함수 객체 생성자가 매개변수를 받지 않을때 소괄호를 사용하여 객체로 만들면 오류가 발생한다. 이럴 땐 다음과 같이 유니폼 초기화를 사용하는 것이 좋다.
thread t4{ Counter{} };
NOTE_ 함수 객체는 항상 스레드의 내부 저장소에 복제된다. 
함수 객체의 인스턴스를 복제하지 않고 그 인스턴스에 대해 operator()를 호출하려면 <functional> 헤더에 정의된 std::ref()나 cref()를 사용해서 인스턴스를 레퍼런스로 전달해야 한다.
예를들면 다음과 같다.
Counter c(2, 12);
thread t2(std::ref(c));

람다 표현식, 멤버 함수로 스레드 만들기

class Request
{
  public:
    Request(int id) : mId(id)
    {
    }
    void process()
    {
        std::cout << "Processing request " << mId << std::endl;
    }

  private:
    int mId;
};

int main(int argc, char *argv[])
{
    int id = 1;
    int numIterations = 5;
    std::thread t1([id, numIterations] {
        for(int i = 0; i < numIterations; ++i)
        {
            std::cout << "Counter " << id << " has value " << i << std::endl;
        }
    });
    t1.join();

    Request req(100);
    std::thread t2{ &Request::process, &req };
    
    t2.join();
    return 1;
}
람다 표현식은 스레드 라이브러리와 궁합이 잘 맞는다. t1이 예이다. 멤버 함수로 스레드를 만들기 위해서는 t2와 같이 생성하면 된다. 기본 Request클래스에 process() 매서드를 정의하고 있다. 그리고 나서 Request클래스의 인스턴스를 생성하고, Request 인스턴스인 req의 process() 매서드를 실행하는 스레드를 생성한다. 이렇게 하면 특정한 객체에 있는 매서드를 스레드로 분리해서 실행할 수 있다. 똑같은객체를 여러 스레드가 접근할 때 데이터 경쟁이 발생하지 않도록 스레드에 안전하게 작성해야 한다.

이런식으로도 스레드를 만들 수 있다.
class Publish
{
  public:
    Publish()
    {
        _th = std::thread([this] { pub(); });
        _th.join();
    }
    void pub()
    {
        for (int i = 0; i < 10; i++)
            std::cout << "Publish.." << std::endl;
    }
    std::thread _th;
};

참고

태그:

카테고리:

업데이트: