#include <thread>
std::thread th([실행할 함수]);
프로세스 내에서 실제로 작업을 수행하는 주체로, CPU 코어에서 돌아가는 프로그램의 단위이다.
#include <iostream>
#include <thread>
#include <chrono>
using std::cout;
using std::endl;
using namespace std::chrono_literals;
int main() {
cout << "thread ID : " << std::this_thread::get_id() << endl;
std::thread th([] {
cout << "thread ID : " << std::this_thread::get_id() << endl;
std::this_thread::sleep_for(1s);
cout << "thread complete" << endl;
});
th.join();
// 조인이 없으면 에러
std::thread th0 = std::move(th);
// 복사가 안되서 이동시켜주어야 한다.
cout << "Complete" << endl;
}
실행 결과
thread ID : 45796
thread ID : 43432
thread complete
Complete
thread를 생성한 후 th.join()
을 이용해 thread를 실행 시켜주었다.
thread의 ID와 thread complete를 보면 thread가 실행되었다는 것을 알 수 있다.
또한, thread는 복사가 안되어 std::move()
등을 이용해 이동대입을 시켜야 한다.
각각의 thread에 독립적인 변수를 만들어 준다.
정적 변수와 같이 각 thread가 실행될 때 1번 초기화 되고 thread가 종료 될 때 1번 해제된다.
#include <iostream>
#include <thread>
#include <chrono>
using std::cout;
using std::endl;
using namespace std::chrono_literals;
struct Test {
int num;
Test(int num) : num(num) {
cout << "Construct : " << num << endl;
}
~Test() {
cout << "Destruct : " << num << endl;
}
};
thread_local Test test(10);
void foo() {
for (int i = 0; i < 10; i++) {
static int num = 10;
num++;
cout << num << endl;
}
}
int main() {
std::thread(foo).join();
foo();
}
실행 결과
Construct : 10
Construct : 10
11 12 13 14 15 16 17 18 19 20
Destruct : 10
21 22 23 24 25 26 27 28 29 30
Destruct : 10
위와 같이 static 변수는 처음 생성될 때 1번만 초기화 되기 때문에 thread가 실행 되어도 그 값을 유지하는 것을 알 수 있다.
void foo() {
for (int i = 0; i < 10; i++) {
thread_local int num = 10;
num++;
cout << num << ' ';
}
cout << endl;
}
실행 결과
Construct : 10
Construct : 10
11 12 13 14 15 16 17 18 19 20
Destruct : 10
11 12 13 14 15 16 17 18 19 20
Destruct : 10
하지만 for문의 num을 위와 같이 thread_local int로 선언해준다면
thread가 실행 될 때 1번 초기화 하고 종료될 때 해제되기 때문에
각 thread가 실행 될 때만 값을 유지하는 것을 알 수 있다.
한개의 프로세스는 최소 한개 이상의 thread로 구성되기 때문에 여러개의 thread로 구성될 수 있다.
이러한 프로그램을 multithread 프로그램이라고 한다.
같은 프로세스 내의 thread 끼리는 서로 같은 메모리를 공유하기 때문에 문제가 발생할 수 있다.
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using std::cout;
using std::endl;
using namespace std::chrono_literals;
int main() {
std::mutex m;
int num = 0;
auto func = [&num, &m] {
for (int i = 0; i < 1000000; i++) {
num++;
}
};
std::thread th0(func);
std::thread th1(func);
th0.join();
th1.join();
cout << num << endl;
}
실행 결과
1726118
2000000 이 나왔어야 하는데 이상한 값이 나왔다.
그 이유는 num이라는 전역 변술을 2개의 thread가 공유를 하며 num += 1
연산을 하였기 때문이다.
2개의 thread가 동시에 연산을 수행하게 되면
th는 num
#include <mutex>
std::mutex m;
여러 thread의 공유자원에 대한 동시 접근을 차단하는 역할을 한다.
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using std::cout;
using std::endl;
using namespace std::chrono_literals;
int main() {
std::mutex m;
int num = 0;
auto func = [&num, &m] {
for (int i = 0; i < 1000000; i++) {
m.lock();
num++;
m.unlock();
}
};
std::thread th0(func);
std::thread th1(func);
th0.join();
th1.join();
cout << num << endl;
}
실행 결과
2000000
각 thread가 실행 될때마다 m.lock()
을 이용해 다른 thread의 접근을 차단하여 정상적인 결과가 나왔다.
m.unlock()
은 다시 다른 thread의 접근을 허용하게 하는것으로 lock을한 후에는 반드시 unlock을 해주어야 한다.
그렇지 않으면 다른 thread가 실행되지 못하고 교착상태에 빠지게 된다.
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using std::cout;
using std::endl;
using namespace std::chrono_literals;
void foo(int& num) {
num++;
throw std::runtime_error("err");
}
int main() {
std::mutex m;
int num = 0;
auto func = [&num, &m] {
for (int i = 0; i < 1000000; i++) {
try {
m.lock();
foo(num);
m.unlock();
}
catch (...) {
}
}
};
std::thread th0(func);
std::thread th1(func);
th0.join();
th1.join();
cout << num << endl;
}
위 코드를 실행하게 되면 예외가 발생하여 m.unlock
이 실행되지 못하고 교착상태에 빠지게 된다.
이와 같은 문제를 방지하기 위해std::lock_guard<std::mutex>
을 사용할 수 있다.
위 코드의 for문을 아래와 같이 변경해준다면 2000000이 출력되는 것을 볼 수 있다.
for (int i = 0; i < 1000000; i++) {
try {
std::lock_guard<std::mutex> lock(m);
foo(num);
}
catch (...) {
}
}
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
using std::cout;
using std::endl;
using namespace std::chrono_literals;
std::mutex m;
void foo() {
std::lock_guard<std::mutex> lock(m);
}
void goo() {
std::lock_guard<std::mutex> lock(m);
foo();
}
int main() {
cout << "foo" << endl;
goo();
}
위 코드를 실행하게 되면 goo()
를 호출하고 foo()
를 호출하는 과정에서 goo()
lock이 풀리지 않게 되어 교착 상태에 빠지게 된다.
#include <iostream>
#include <thread>
#include <chrono>
#include <mutex>
#include <atomic>
#include <queue>
using std::cout;
using std::endl;
using namespace std::chrono_literals;
std::mutex m;
void produce(std::mutex& m, std::condition_variable& cv, std::queue<int>& jobQueue) {
while (true) {
std::this_thread::sleep_for(100ms);
{
std::lock_guard<std::mutex> lock(m);
jobQueue.push(1);
cout << "produce : " << jobQueue.size() << endl;
}
cv.notify_one(); // waiting 하는 thread에 실행 명령
}
}
void longTimeJob() {
std::this_thread::sleep_for(200ms);
}
void consume(std::mutex& m, std::condition_variable& cv, std::queue<int>& jobQueue) {
while (true) {
{
std::unique_lock<std::mutex> lock(m);
if (jobQueue.empty()) { // lost wakeup
cv.wait(lock);
}
if (jobQueue.empty()) // superious wakeup
continue;
int result = jobQueue.front();
jobQueue.pop();
cout << "consume : " << jobQueue.size() << endl;
}
}
longTimeJob();
}
int main() {
std::mutex m;
std::condition_variable cv;
std::queue<int> jobQueue;
std::thread producer(produce, std::ref(m), std::ref(cv), std::ref(jobQueue));
std::thread consumer(consume, std::ref(m), std::ref(cv), std::ref(jobQueue));
producer.join();
consumer.join();
}
실행 결과
1
0
무한 반복