
원래 C++에서는 윈도우용 리눅스용 스레드를 만드는 방식이 따로 나누어져 있었지만 최근 업데이트 되면서 c++ 자체에서 thread라이브러리를 제공하게 되었다.
#include<thread>
void HelloThread() {
cout << "HelloThread" << endl;
}
int main() {
thread t(HelloThread);
cout << "HelloMain" << endl;
t.join();
}
join()함수를 사용하면 이 스레드가 작업을 완료하기를 기다리게 되는 것이다. (블로킹)Join을 사용하지 않게 되면 스레드 객체가 소멸할 때std::terminate()가 호출이 되어 비정상 종료가 되게 된다.이 프로그램이 동시에 돌릴 수 있는 스레드의 개수를 알려주는 힌트값을 반환하는 정적 함수이다. 근데 무조건 확률이 100%는 아니여서 약간 힌트용으로 사용한다.
#include<iostream>
#include<thread>
using namespace std;
void HelloThread() {
cout << "HelloThread" << endl;
}
int main() {
thread t(HelloThread);
cout << "HelloMain" << endl;
int a = t.hardware_concurrency();
cout << "Hardware Concurrency: " << a << endl;
t.join();
}
스레드 마다 생성이 되면 각자 id를 가지고 있는데 이 값을 리턴하는 함수이다. 근데 값이 1,2,3 이렇게 순차적으로 오르는 것은 아니고 들쑥날쑥 가지게 된다.
int main() {
thread t(HelloThread);
cout << "HelloMain" << endl;
int a = t.hardware_concurrency();
cout << "Hardware Concurrency: " << a << endl;
auto b = t.get_id();
cout << "Thread ID: " << b << endl;
t.join();
}
HelloMain
HelloThread
Hardware Concurrency: 8
Thread ID: 0x16b197000
detach는 중요한 개념인데. 이는 메인 스레드에서의 연결을 끊어서 별도의 스레드로 OS 백그라운드에서 동작시키게 하는 것이다. 이렇게 되면 main이 종료가 되어도 새로 생긴 thread는 종료가 되지 않는다.
void HelloThread() {
cout << "HelloThread" << endl;
}
int main() {
thread t(HelloThread);
cout << "HelloMain" << endl;
t.detach();
this_thread::sleep_for(100ms);
}
join이 가능한 상태인지를 확인하는 함수이다.
void HelloThread() {
cout << "HelloThread" << endl;
}
int main() {
thread t;
auto id = t.get_id();
cout << "Thread ID: " << id << endl;
t = thread(HelloThread);
if (t.joinable()) {
t.join();
}
else {
cout << "Not Joinable" << endl;
}
}
Thread ID: 0x0
HelloThread
lvalue: 메모리 위치를 참조하는 식, 이름 있고 주소(&)가 있는 값.rvalue: 이름 없고 주소 없는 임시값.int val = 90;
val = val + 10;
val + 10은 그 자체로 임시값이 되면서 R-Value가 되는 것이다.cout << function() << '\n';
function()이라는 함수도 R-Value가 된다. 왜냐하면 이 식이 끝나면 더이상 참조할 수 없는 임시 값이기 때문이다.int val = 90;
int& ref = val;
&을 이용하면 L-value 참조자를 만들 수 있었다.int function () {
return 1;
}
int&& ref = function();
cout << ref << endl;
ref = 100;
cout << ref << endl;
1
100
&&을 이용하면 R-Value에 직접 바인딩 할 수 있다.지금까지는 그냥 인자가 없는 단순한 함수였는데, 만약에 인자를 넣어보려면 어떻게 하면 될까?
template <class _Fp, class... _Args,
__enable_if_t<!is_same<__remove_cvref_t<_Fp>, thread>::value, int>>
thread::thread(_Fp&& __f, _Args&&... __args) {
_Start(_STD forward<_Fn>(__f), _STD forward<_Args>(_-args))
}
Ivalue와 rvalue가 뭔지 알아야 한다._FP&&를 받는다. 이러면 특정 함수를 R-Value로 받는다는 의미다. _Args&&... __args 이렇게 되어있는데, 이는 인자를 여러개를 받을 수 있다는 뜻이다.void HelloThread(int num) {
cout << "HelloThread: " << num << endl;
}
int main() {
thread t;
t = thread(HelloThread, 1);
t.join();
}
TLS라고 하는 것은 각 스레드가 독립적인 데이터를 저장하는 것이다.
그런데 각 스레드마다 이미 별도의 스택 공간이 있을텐데 왜 TLS가 따로 있는것일까? 그것은 스택은 함수를 위한 메모리 공간에 가깝고 함수가 정리가 되면서 거기서 사용한 메모리를 정리하는 식으로 어떻게 보면 굉장히 불안정한 메모리 공간이라 영구적으로 저장할 데이터를 넣기에는 바람직 하지 않다.
따라서 TLS를 만들어서 전역 메모리 처럼 사용하게 되는데. 나만 접근하는 전역메모리 라고 생각하면 쉬울 것 같다.
Rokiss님의 비유에 따르면 동물원에서 사자가 중앙에 있는 고기를 일부 가져가서 자기 자리에서 먹는 것을 생각하면 된다고 했는데 너무 적절한 비유라 가져와봤다.
thread_local int32 LThreadId = 0;
void ThreadMain(int32 threadId)
{
LThreadId = threadId;
while (true)
{
cout << "Thread " << LThreadId << " is working." << endl;
this_thread::sleep_for(1s);
}
}
int main()
{
vector<thread> threads;
for (int32 i = 0; i < 5; i++)
{
threads.emplace_back(ThreadMain, i + 1);
}
for (thread& t : threads)
{
t.join();
}
}