본문 바로가기
Language & Tool/C++

06 STL 스레딩 API

by Orangetasteboy 2023. 7. 14.

실질적인 스레딩 API 자체는 STL에 구현되어 있음.

스레드를 사용하기 위해서는 관련 STL 헤더 포함 필수

 

<thread> 헤더

std::thread 클래스

std::this_thread 이름 공간 아래의 메소드

  • yield
  • get_id
  • sleep_for
  • sleep_until

 

스레드 클래스

전체 스레딩 API의 핵심이 되는 부분

이 클래스는 하부의 운영체제 스레드를 감싸고 스레드를 시작하고 중지시키는 데에 필요한 기능 제공

 

 

선언 방법

함수를 이용한 생성

std::thread 변수(함수명, 전달인자)

 

클래스 멤버 함수를 이용한 생성

std::thread 변수(클래스명::thread 수행함수, 클래스생성자, 전달인자)

 

클래스 Static 함수를 이용한 생성

std::thread 변수(클래스명::thread 수행함수, 전달인자)

 

 

기본 사용

스레드는 생성과 동시에 즉시 시작

#include <thread>

void worker() {
	// Business logic.
}

int main() {
	std::thread t(worker);
	return 0;
}

위의 코드는 스레드를 시작하고 시작한 스레드의 실행 종류를 대기하지 않음.

따라서 애플리케이션은 즉시 종료

올바르게 사용하기 위해서는 아래와 같이 스레드의 종료를 대기하거나 합류를 대기해야 함.

 

#include <thread>

void worker() {
	// Business logic.
}

int main() {
	std::thread t(worker);
	t.join();
	return 0;
}

위의 코드는 새로운 스레드가 종료하기를 대기한 다음에 합류

 

 

인자 전달

새로운 스레드에 인자 전달 가능

인자 값은 이동 가능해야 함. → 이동 또는 복사 생성자를 가지는 유형

#include <thread>
#include <string>

void worker(int n, std::string t) {
	// Bunsiness logic.
}

int main () {
	std::string s = "Test";
	int i = 1;
	std::thread t(worker, i, s);
	t.join();
	return 0;
}

하나의 정수와 문자열을 스레드 함수에 전달

스레드 함수는 이들 두 변수에 대한 복사본을 받음.

 

 

반환값

스레드 클래스 생성자에 전달된 함수에 의해 반환된 값 무시

새로운 스레드를 생성한 스레드에 대한 정보를 반환하기 위해 스레드 간의 동기화 매커니즘과 일종의 공유 변수 사용 필요

 

 

스레드 이동하기

<utility> 헤더의 std::move 템플릿 메소드를 사용하여 객체 간에 자원 이동 가능

스레드 인스턴스는 이동 가능

#include <thread>
#include <string>

void worker(int n, std::string t) {
	// Bunsiness logic.
}

int main () {
	std::string s = "Test";
	std::thread t(worker, 1, s);
	std::thread t1(std::move(t0));
	t.join();
	return 0;
}

위의 코드에서 다른 스레드로 스레드를 이동하기 전에 한 스레드 생성

즉시 종료되기 때문에 스레드 0은 존재하지 않으며 스레드 실행은 생성된 새로운 스레드에서 재개됨.

첫 번째 스레드가 재합류하기를 대기할 필요가 없고 두 번째 스레드만 대기하면 됨.

 

 

스레드 ID

스레드는 자신과 관련된 식별자를 가짐.

이 ID는 STL 구현에 의해 제공되는 고유 식별자

thread 클래스 인스턴스의 get_id() 함수를 호출해 스레드 ID를 구할 수 있음.

std:: this_thread::get_id()를 호출해 함수를 호출하는 스레드의 ID를 구할 수 있음.

 

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

std::mutex display_mutex

void worker() {
	std::thread::id this_id = std::this_thread::get_id();
   
	display_mutex.lock();
	std::cout << "thread " << this_id << " sleeping...\n";
	display_mutex.unlock();

	std::this_thread::sleep_for(std::chrono::seconds(1));
}

int main() {
	std::thread t1(worker);
	std::thread::id t1_id = t1.get_id();
    
	std::thread t2(worker);
	std::thread::id t2_id = t2.get_id();
    
	display_mutex.lock();
	std::cout << "t1's id: " << t1_id << "\n";
	std::cout << "t2's id: " << t2_id << "\n";
	display_mutex.unlock();
    
	t1.join();
	t2.join();
    
	return 0;
}

 

결과는 아래와 같음.

t1's id: 2
t2's id: 3
thread 2 sleeping...
thread 3 sleeping...

여기서 내부 스레드 ID는 최초 스레드(ID 1)에 상대적인 값을 갖는 정수(std::thread::id 유형)임을 확인 가능

 

 

슬립(Sleep)

두 메소드 가운데 하나를 사용하여 스레드의 실행 지연 가능

sleep_for()는 최소 지정된 기간 동안 실행 지연

 

#include <iostream> 
#include <chrono> 
#include <thread> 
	using namespace std::chrono_literals; 

	typedef std::chrono::time_point<std::chrono::high_resolution_clock> timepoint; 

int main() { 
         std::cout << "Starting sleep.\n"; 
 
         timepoint start = std::chrono::high_resolution_clock::now(); 
 
         std::this_thread::sleep_for(1); 
 
         timepoint end = std::chrono::high_resolution_clock::now(); 
         std::chrono::duration<double, std::milli> elapsed = end - start; 
         std::cout << "Slept for: " << elapsed.count() << " ms\n"; 
}

위의 코드는 현재 OS에서 가능한 최고 정확도의 카운터를 사용하여 정확한 기간을 측정함으로 대략 2초 동안 슬립하는 방법

 

초 단위의 수에 s를 붙여서 슬립 시간 직접 지정 가능

<chrono> 헤더의 기능

 

 

양보(Yield)

 현재 스레드가 재스케줄될 수 있음을 OS에게 알림으로 다른 스레드가 대신 실행되게 함.

std::this_thread::yield() 함수 사용

 

 

분리(Detach)

스레드를 시작한 이후에 이 스레드 객체에 대해 detach() 함수 호출 가능

효과적으로 새 스레드를 호출 스레드로부터 분리

새로운 스레드는 호출 스레드가 종료된 이후에도 계속 실행

 

 

스왑(Swap)

독립적 메소드 또는 스레드 인스턴스의 함수로서 swap()을 사용하여 스레드 객체의 내부 스레드 핸들 교환 가능

#include <iostream> 
#include <thread> 
#include <chrono> 
  
void worker() { 
   std::this_thread::sleep_for(std::chrono::seconds(1)); 
} 
  
int main() { 
         std::thread t1(worker); 
         std::thread t2(worker); 
  
         std::cout << "thread 1 id: " << t1.get_id() << "\n"; 
         std::cout << "thread 2 id: " << t2.get_id() << "\n"; 
  
         std::swap(t1, t2); 
  
         std::cout << "Swapping threads..." << "\n"; 
 
         std::cout << "thread 1 id: " << t1.get_id() << "\n"; 
         std::cout << "thread 2 id: " << t2.get_id() << "\n"; 
  
         t1.swap(t2); 
  
         std::cout << "Swapping threads..." << "\n"; 
 
         std::cout << "thread 1 id: " << t1.get_id() << "\n"; 
         std::cout << "thread 2 id: " << t2.get_id() << "\n"; 
  
         t1.join(); 
         t2.join(); 
}

 

결과는 아래와 같음.

thread 1 id: 2
thread 2 id: 3
Swapping threads...
thread 1 id: 3
thread 2 id: 2
Swapping threads...
thread 1 id: 2
thread 2 id: 3

댓글