C++11에 동시성 지원 라이브러리가 추가되었고 익히 알고 있는 thread와 관련된 라이브러리이다.
std 네임스페이스에 속하며(표준 패키지임) thread 헤더를 추가해야 사용할 수 있다.
main thread가 새로 thread를 생성하게 되면, 이 worker thread를 기다릴지, 기다리지 않을지 두 가지 전략이 존재한다.
main thread가 worker thread를 기다리는 전략
우선은 worker thread가 작업을 종료할 때까지 main thread가 기다리는 전략의 예제를 살펴보자. worker thread는 foreground에서 실행되며 main thread는 worker thread 종료까지 block된다.
#include <iostream>
#include <thread>
using namespace std;
void workerFunction() {
cout << "Worker thread is sleeping for 2 seconds" << endl;
this_thread::sleep_for(chrono::seconds(2));
cout << "Worker thread is done." << endl;
}
int main() {
thread workerThread(workerFunction);
workerThread.join();
cout << "Main thread is done." << endl;
return 0;
}
multiple worker threads
여러 개의 worker thread를 생성하는 간단한 예제이다.
vector에 담아두고 하나씩 join한다.
#include <iostream>
#include <thread>
#include <vector>
using namespace std;
void workerFunction(int id) {
this_thread::sleep_for(chrono::seconds(2));
}
int main() {
const int numThreads = 10;
vector<thread> threads;
for (int i = 0; i < numThreads; ++i) {
threads.emplace_back(thread(workerFunction, i + 1));
}
for (int i = 0; i < numThreads; ++i) {
threads[i].join();
}
cout << "Main thread is done." << endl;
return 0;
}
이 예제 프로그램을 실행하면 거의 2초의 실행 시간을 가지고 작업이 종료된다. 동시성이 10개 thread에 의해 높게 형성되므로 순차 실행과 달리 20초가 아니라 2초 정도면 완료하는 것이다.
- 요즘은 worker thread를 vector에 담아서 관리할 때 push_back() 대신에 emplace_back()을 권장한다고 함
- push_back()은 객체의 복사 작업이 수행되고(copy constructor가 호출), emplace_back()은 객체의 생성 작업이 수행됨(constructor가 호출됨)
- emplace_back()이나 push_back()을 호출하지 않고 바로 join()을 호출하면 block되니 주의해야 함
main thread가 worker thread를 기다리지 않는 전략
- worker thread에 대해 detach()를 호출한다.
- main thread는 sleep_for()를 호출하든가 무한 대기하든가 해서, worker thread가 충분히 작업을 마치고 나서 프로그램이 종료되도록 해야 한다.
main thread는 worker thread를 기다리지 않고 worker thread는 background에서 실행된다. main thread는 block되지 않고 자신의 작업을 계속 수행한다.
#include <iostream>
#include <thread>
using namespace std;
void workerFunction() {
cout << "Worker thread is sleeping for 2 seconds" << endl;
this_thread::sleep_for(chrono::seconds(2));
cout << "Worker thread is done." << endl;
}
int main() {
thread workerThread(workerFunction);
workerThread.detach();
cout << "Main thread is done." << endl;
this_thread::sleep_for(chrono::seconds(3));
return 0;
}
Main thread is done.
Worker thread is sleeping for 2 seconds
Worker thread is done.
만약 main thread가 먼저 종료한다면
만약 main thread가 sleep_for()를 이용해서 기다리지 않는다면 worker thread가 시작하기 전에 프로그램이 종료할 수도 있고, worker thread가 실행되더라도 작업 완료 후 자원이 정상적으로 해제되지 않고 강제 종료된다.
만약 detach를 하지 않는다면
Main thread is done.
terminate called without an active exception
1 2654421 abort (core dumped) ./a.out
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007ffff7bbb859 in __GI_abort () at abort.c:79
#2 0x00007ffff7e448d1 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7e5037c in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7e503e7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00005555555554d7 in std::~thread (this=0x7fffffffda58, __in_chrg=)
at /usr/include/c++/9/thread:139
#6 0x00005555555553af in main () at thread_without_join.cc:14
thread의 소멸자 수행 중에 알 수 없는 런타임 예외가 발생하여 ABORT 시그널을 발생시키고 종료한다.
detach도 하고 join도 하는 경우
thread의 기본적인 지식을 아는 사람이라면 이렇게 코드를 작성할리 없지만, 혹시 이렇게 작성해서 실행하는 경우가 있다면 올바른 컨텍스트가 아니라서 join() 실행 중에 예외가 발생한다.
terminate called after throwing an instance of 'std::system_error'
what(): Invalid argument
1 2653638 abort (core dumped) ./a.out
(gdb) bt
#0 __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:50
#1 0x00007ffff7bbb859 in __GI_abort () at abort.c:79
#2 0x00007ffff7e448d1 in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#3 0x00007ffff7e5037c in ?? () from /lib/x86_64-linux-gnu/libstdc++.so.6
#4 0x00007ffff7e503e7 in std::terminate() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#5 0x00007ffff7e50699 in __cxa_throw () from /lib/x86_64-linux-gnu/libstdc++.so.6
#6 0x00007ffff7e476fd in std::__throw_system_error(int) () from /lib/x86_64-linux-gnu/libstdc++.so.6
#7 0x00007ffff7e7d070 in std::join() () from /lib/x86_64-linux-gnu/libstdc++.so.6
#8 0x00005555555553a8 in main () at thread_without_join.cc:17