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
참고
* 본 글에 예제 코드는 코드누리 교육을 받으면서 제공된 샘플코드를 활용하였습니다.
'L C++ > Concurrency' 카테고리의 다른 글
[Concurrency] Function Templates(함수 템플릿)을 스레드로 실행하기 (0) | 2023.09.05 |
---|---|
[Concurrency] 멀티(복수) 스레드를 보관하는 방법 (0) | 2023.08.15 |
[Concurrency] 인자와 callable object (0) | 2023.07.24 |
[Concurrency] 좀비 스레드 생성될 수 있는가? (0) | 2023.07.18 |
[Concurrency] this_thread 특징 (0) | 2023.07.11 |