C++에서 쓰레드를 사용하기 위해서, C++11부터 지원하는 thread를 사용할 수 있다. C++11이전의 버전에서는 표준에서 쓰레드를 지원하지 않아 다른 외부 라이브러리나 WinAPI를 사용해서 쓰레드를 관리했어야한다. 하지만 C++11부터 쓰레드관련 기능이 표준으로 합류해 thread헤더를 이용해 플랫폼에 관계없이 쓰레드를 관리할 수 있다.
쓰레드를 사용하는 간단한 방법은 헤더를 추가한 후 thread가 실행할 함수를 지정해주면 된다. 함수대신 람다함수로 지정해주어도 된다.
#include <iostream>
#include <thread>
using namespace std;
void HelloThread() {
cout << "Hello Thread" << endl;
}
int main() {
thread t(HelloThread);
t.join();
return 0;
}
hardware_concurrency()가 반환하는 값이 실제 사용 가능한 코어의 수를 의미하지는 않는다. 그리고 0을 반환할 수도 있는데 '알 수 없음'을 의미한다. std::thread t;
cout << t.hardware_concurrency() << endl;
std::thread t;
cout << t.get_id() << endl;
join()
특정 쓰레드가 종료될 때까지 현재 쓰레드를 Block하는 함수로 보통 메인 쓰레드에서 사용한다.
joinable()
특정 쓰레드가 join가능한지 여부를 확인하는 함수로 join하기전에 joinable로 상태를 확인하는 방식으로 사용한다.
sleep() 계열
쓰레드를 일시 중지하는 함수로 sleep_for와 sleep_until이 있다. sleep_for는 특정 시간동안 일시 중지를 하는 기능이고 sleep_until은 특정 시점까지 일시 중지하는 기능이다.
yield()
쓰레드의 실행을 양보하고 다른 쓰레드에게 CPU자원을 양보하는 함수이다.
Atomic한 연산(기능)은 다음과 같은 의미가 있다고 생각한다
결과가 모두 적용되거나 모두 적용이 안되는 속성을 가진 연산(기능)
Atomic이라는 단어를 가장 많이 들을 수 있는 쪽이 멀티쓰레드와 DB 트랜잭션쪽으로 알고 있다. 동시에 특정 자원에 접근이 가능한 환경에 데이터의 무결성을 보장해주는 개념이라고 할 수 있다.
Atomic을 지키기 않았을 경우에 문제가 발생하는 간단한 예제가 있는데 우선 동시에 실행되지 않는 환경인 아래코드를 한번 보자.
int32 sum = 0;
void Add() {
for (int32 i = 0; i < 1000000; i++) {
sum++;
}
}
void Sub() {
for (int32 i = 0; i < 1000000; i++) {
sum--;
}
}
int main() {
Add();
Sub();
cout << sum << endl;
return 0;
}
간단한 함수로 Add는 전역변수 sum을 백만번 +1하고 Sub는 sum을 백만번 -1한다. 결과는 쉽게 예측가능하다. 0이 출력된다.
그럼 2개의 쓰레드가 각각 Add, Sub을 실행하는 아래 코드를 보자. 쓰레드 t1은 Add를, 쓰레드 t2는 Sub를 실행한다.
int32 sum = 0;
void Add() {
for (int32 i = 0; i < 1000000; i++) {
sum++;
}
}
void Sub() {
for (int32 i = 0; i < 1000000; i++) {
sum--;
}
}
int main() {
Add();
Sub();
cout << sum << endl;
std::thread t1(Add);
std::thread t2(Sub);
t1.join();
t2.join();
cout << sum << endl;
return 0;
}
이제 결과를 예측해보자, 각 Add와 Sub는 sum에 대한 연산을 단 한줄만 하는 것인데 쓰레드 연산 순서를 섞어도 아래와 같은 순서로 적용되지 않을까 싶다.
sum++;
sum--;
sum++;
sum++;
sum--;
sum--;
sum++;
sum++;
sum--;
결국 백만번의 증가와 감소로 sum은 0이 될것이라 예측할 수 있다. 하지만 아래 결과화면을 보면 그렇지 않다.

'멀티쓰레드 환경이어도 코드가 한줄이기 때문에 sum이 0이 나올 것이다.'라고 생각했지만 실제로는 그렇지 않다. sum++이나 sum--는 실제로 실행될 때는 다음과 같은 단계로 연산이 진행된다.
실제 연산단계는 3단계로 이루어지기 때문에 멀티쓰레드 환경에서 아래와 같은 상황이 발생할 수 있고 결국 전역변수인 sum의 값은 0이 될 수 없다.
t1, t2가 연산을 각각 한번 했지만 최종 결과는 0이 되지 않았다.
이를 위해, 변수에 동기화를 적용하는 기능으로 atomic이 있다. atomic은 C++에서 atomic헤더파일에 존재하며 동기화하고자하는 변수를 선언할 때 자료형에 atomic을 표기함으로 동기화를 적용할 수 있다.
atomic<int32> sum = 0;
위 내용을 적용하면 의도했던대로 0이 출력된다.
