SW 그리고 아빠 엔지니어링 중...

아는 만큼 보이고, 경험해봐야 알 수 있고, 자꾸 써야 내 것이 된다.

L C++/Concurrency

[Concurrency] 주요 기능 member type / class / functions 정리

보리남편 김 주부 2023. 8. 1. 10:59
thread 주요 기능을 정리하였다.

member type

native_handle_type native_handle() 멤버 함수의 반환 타입

member class

id thread id를 담는 타입

member functions

 

hardware_concurrency CPU가 지원하는 thread 개수, static
get_id 스레드 ID 반환
native_handle OS의 스레드 핸들 반환
swap 스레드 Object swap
복사는 불가능하고 이동(move)은 가능하다.
joinable join이 가능한지 여부 조사
join 스레드 종료 대기
detach 스레드 떼어 내기

 

native_handle


스레드는 운영체제의 자원을 이용하고 직접적인 접근은 못하게 되어 있기에, native_handle을 통해 접근할 수 있다.

C++ 스레드 라이브러리는 "스레드 우선순위" 지원하지 않지만 각 OS의 시스템 call을 사용 시 핸들 전달을 통해 사용할 수 있다.

#include <iostream>
#include <thread>
#include <windows.h>

void foo()
{
    auto tid = std::this_thread::get_id(); // 스레드 ID 얻기
}

int main()
{
    std::thread t(&foo);

    std::thread::native_handle_type handle = t.native_handle();

    // windows : HANDLE
    // linux   : pthread_t
    SetThreadPriority((HANDLE)handle, THREAD_PRIORITY_HIGHEST);

    t.join();
}

 

get_id


자신의 생성한 스레드의 id를 얻을 수 있다.

#include <iostream>
#include <thread>

void foo()
{
    // 1. 자신의 스레드 ID 얻기
    std::cout << std::this_thread::get_id() << std::endl;
}
int main()
{
    std::thread t(&foo);
    
    std::this_thread::sleep_for(1s);

    // 자신(주스레드)가 생성한 스레드 ID 얻기
    std::thread::id tid = t.get_id();
    
    std::cout << "created thread id : " << tid << std::endl;
    std::cout << std::this_thread::get_id() << std::endl;
    t.join();
}
결과:
2
created thread id : 2
1

 

join()


스레드가 종료할 때까지 대기를 하며, 종료 시 내부 핸들을 close(detach) 한다.

join은 스레드가 생성되었을 때만 호출할 수 있다. detach 된 경우도 호출할 수 없다.

(예제 코드는 아래 joinable 내용과 함께 기재되어 있음)

 

joinable()


join()이 가능한가를 조사한다.

스레드가 살아있음을 확인하는 함수는 아니기에 detach 혹은 join을 하기 전 체크함수로 사용 필요.

#include <iostream>
#include <thread>
#include <chrono>

// join 과 joinable 에 대해서
void foo() {}
void long_time_function() {
    int count = 0;
    while(true){       
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
        std::cout << "count = " << count++ << std::endl;
    };
}
void short_time_function() {}

int main()
{    
    std::thread t1(&foo);
   
    //1-1) join은
    t1.join();  // 1. 스레드 종료 할때 까지 대기
                // 2. 종료되면 t1이 가진 내부 핸들을 close (detach)

    //1-2) join을 여러번 할 수 있는가? 
    //t1.join();  // runtime error. 이미 핸들 닫힘.
    //             // 즉, 2번 join 안됨.

    //2) 스레드를 생성하지 않은 경우 join 안됨
    std::thread t2; // 함수 전달하지 않았으므로 스레드 생성안됨.
    //t2.join();      // runtime error.(예외 발생)

    //3) joinable() : join()이 가능한가를 조사한다.
    if (t2.joinable())
    {
        //thread가 생성되지 않아서 아래 코드는 타지 않음
        std::cout << "t2 is joinable !!" << std::endl;
        t2.join();
    }    
    
    // 3-1) joinable()은 스레드가 살아있는가는 조사하는 것은 아님..
    std::thread t3(&long_time_function);
    std::thread t4(&short_time_function);

    bool b1 = t3.joinable(); // 스레드가 살아있건
    bool b2 = t4.joinable(); // 스레드가 이미 종료 되었건

    // 모두 join 가능결과를 true 로 확인 된다.(join을 하지 않았기 때문)
    std::cout << "b1 = " << b1 << ", b2 = " << b2 << std::endl;
    t3.detach(); // t3는 detach만 해서 별도로 계속 동작하지만 메인 스레드가 종료되면 같이 종료된다.
    t4.join();


    //4) join 이 불가능한 경우는
    std::thread t5; // 스레드가 생성되지 않았거나
    std::thread t6(&foo); // 스레드 생성후
    t6.detach(); // 이미 join 했거나 detach 한경우... 

    std::this_thread::sleep_for(std::chrono::seconds(3));
    std::cout << "=== detach된 t3는 메인 쓰레드가 종료되면 같이 종료됨!!  ==" << std::endl;
    return 0;
}
join을 하지 않거나 join을 여러 번 하면 아래 에러메시지가 뜬다.
terminate called after throwing an instance of 'std::system_error'
what(): Invalid argument
결과 :
b1 = 1, b2 = 1 
count = 0
count = 1
count = 2
count = 3
count = 4
count = 5
count = 6
count = 7
count = 8
count = 9
count = 10
count = 11
count = 12
=== detach 된 t3는 메인 스레드가 종료되면 같이 종료됨!! ==

 

swap()


swap() 은 단어 뜻 그대로 스레드를 교체한다. get_id와 joinable을 통해 swap 되었는지 확인해 보자.

#include <thread>
#include <iostream>

void foo() {}
void goo() {}

int main()
{
    std::thread t1(&foo);
    std::thread t2(&goo);

    std::cout << "thread 1 id: " << t1.get_id() << std::endl
              << "thread 2 id: " << t2.get_id() << std::endl;

    // 1. swap
    t1.swap(t2); // ok

    std::cout << "thread 1 id: " << t1.get_id() << std::endl
              << "thread 2 id: " << t2.get_id() << std::endl;

    t1.join();
    t2.join();
}
결과 : 
thread 1 id: 2
thread 2 id: 3
thread 1 id: 3
thread 2 id: 2

 

기타(thread copy, move)


스레드는 복사생성자를 허용하지 않지만 새로운 스레드를 생성하고 옮겨가기 위한 방법이 있다.

std::move를 이용해서 thread를 옮길 수 있다. move가 rvalue로 옮겨가는 것인데 아래 예제처럼 임시객체로 스레드를 생성하면 move와 똑같이 동작되어 생성할 수 있다.

move가 알고 싶으면 아래 글 참조 : 

2023.07.24 - [언어/C++] - [Thread] 인자와 callable object : 기존 함수를 thread로 실행시킬 수 있을까?

#include <thread>
#include <iostream>

// copy, move

void foo() {}

int main()
{
    std::thread t1(&foo);
    
    // 1. copy 는 안되고 move 는 가능
    //std::thread t3 = t1; // error. 복사 생성자가 삭제됨
    std::thread t4 = std::move(t1); // ok   
                // 이제, t1은 자원이 없으므로 join 안됨.
    std::cout << "joinable t1 = " << t1.joinable() << std::endl
              << "joinable t4 = " << t4.joinable() << std::endl; 


    // 3. 스레드 객체만 먼저 만들고 스레드 자체는 나중에 생성하려면
    std::thread t5; // 아직 스레드 생성 안됨
    std::cout << "joinable t5 = " << t5.joinable() << 2212std::endl; 


    t5 = std::thread(&foo); // ok. 이순간 스레드 생성
        // ^ 위 객체는 임시객체 이므로 rvalue
        //   move 로 대입.
    std::cout << "joinable t5 = " << t5.joinable() << std::endl; 

    t5.join();
    t1.join();
    t4.join();
}
결과:
joinable t1 = 0
joinable t4 = 1
joinable t5 = 0
joinable t5 = 1

 

참고


* 본 글에 예제 코드는 코드누리 교육을 받으면서 제공된 샘플코드를 활용하였습니다.

728x90