CPU 코어가 직접 할당되어 구동되는 흐름의 단위.
모든 스레드는 Heap, Data영역에 접근할 수 있다.
멀티 스레드는 프로그램을 병렬로 실행시키는 것과 같다.
CPU 코어 갯수보다 많은 스레드를 생성하고 할당하면 이론대로 작동하지 않는다.
// 주로 사용하는 함수
t.join();
t.joinable();
#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
void HelloThread() {
cout << "Hello Thread" << endl;
}
void HelloThread2(int32 num) {
cout << num << endl;
}
void HelloThread3(int32 num, int32 num2) {
cout << num << " " << num2 << endl;
}
int main() // 메인 스레드
{
//thread t(HelloThread);
thread t;
// t.get_id(); == 0
t = thread(HelloThread); // 실제 스레드 생성. id 배정
cout << "Hello Main" << endl;
int32 count = t.hardware_concurrency(); // CPU 코어 개수 추측 함수.
auto id = t.get_id(); // 스레드마다 고유하게 부여되는 id
if (t.joinable()) // 실제 스레드가 연결이 되어있는지 확인.
t.join(); // t 스레드가 종료될 때까지 메인 스레드를 종료하지 않고 기다린다.
t.detach(); // 스레드 객체 t에서 실제 스레드를 분리한다. -> 이후 t의 상태나 정보를 활용할 수 없게된다. 거의 사용하지 않음.
// 스레드는 병렬로 실행되기때문에 순서를 보장하지 않는다.
vector<thread> v;
for (int32 i = 0; i < 5; i++) {
v.push_back(thread(HelloThread2, i));
}
for (int32 i = 0; i < 5; i++) {
if (v[i].joinable()) v[i].join();
}
thread t3(HelloThread3, 3, 4);
t3.join();
}
멀티스레드 병렬처리의 문제
멀티스레드는 Heap 또는 Data 영역에 위치한 하나의 데이터를 공유할 수 있는 장점이 있다.
하지만 멀티스레드간 순서가 보장되지 않으므로 원하는 결과값이 나올 수 없는 단점이 있다.
아래는 전역변수 sum을 데이터영역에 선언하고 두개의 다른 스레드에서 공동 작업을 하는 코드이다.
#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
int32 sum = 0;
void Add() {
for (int32 i = 0; i < 1'000'000; i++) {
sum++;
}
}
void Sub() {
for (int32 i = 0; i < 1'000'000; i++) {
sum--;
}
}
int main() // 메인 스레드
{
Add();
Sub();
cout << sum << endl;
thread t1(Add);
thread t2(Sub);
t1.join();
t2.join();
cout << sum << endl;
}
하나의 메인스레드에서 실행하면 sum의 결과는 항상 0이지만, 멀티스레드로 동시작업을 진행하면 값이 항상 다르다.
sum을 증가시키는 위치에 break point를 잡고 디스어셈블리언어로 확인해보았다.
sum++; 작업은 3단계로 이루어진다.
1. 메모리에서 sum의 주소를 eax 레지스터로 이동한다.
2. eax의 값을 증가시킨다.
3. eax에 저장된 sum을 원래 주소로 이동시킨다.
위 단계를 코드로 표현하면 다음과 같다.
eax = sum;
sum++;
sum = eax;
멀티스레드 환경에서 i가 0일 경우 Add, Sub 함수가 한 번씩 실행된다고 가정했을 경우 위 코드와 같이 순차적으로 작동한다는 보장은 없다.
즉 i가 0을 순회하고 1이 되었을 때, sum의 값은 1 또는 -1 또는 0일 것이다.
sum = 0
Add() -> eax = sum
Add() -> eax = 1
Add() -> sum = eax
sum = 1
Sub() -> eax = sum
Sub() -> eax = 0
Sub() -> sum = eax -> sum = 0
sum = 0
-----------------------------------
sum = 0
Add() -> eax = sum
Add() -> eax = 1
Sub() -> eax = sum
Sub() -> eax = -1
Add() -> sum = eax -> sum = 1
Sub() -> sum = eax -> sum = -1
sum = -1
-----------------------------------
sum = 0
Sub() -> eax = sum
Sub() -> eax = -1
Add() -> eax = sum
Add() -> eax = 1
Sub() -> sum = eax -> sum = -1
Add() -> sum = eax -> sum = 1
sum = 1
이론상 위 문제를 해결하려면 코드간 순서가 보장되거나 하나의 코드에서 실행되어야 한다.
실질적으로 이를 해결하기 위한 방법으로 동기화 시키는 작업이 있다.
atomic 연산은 모두 실행되거나 아예 실행이 되지 않는 상황만 존재할 수 있는 표현이다.
Atomic 동기화 방법
#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
#include <atomic>
atomic<int32> sum = 0;
void Add() {
for (int32 i = 0; i < 1'000'000; i++) {
sum.fetch_add(1);
}
}
void Sub() {
for (int32 i = 0; i < 1'000'000; i++) {
sum.fetch_sub(1);
}
}
int main() // 메인 스레드
{
Add();
Sub();
cout << sum << endl;
thread t1(Add);
thread t2(Sub);
t1.join();
t2.join();
cout << sum << endl;
}
atomic 템플릿은 공유 데이터를 하나의 스레드에서 작업이 발생할 시 해당 작업시 완료될 때까지 기다린 후 다음 스레드에서 공유 데이터 작업을 실행하도록 CPU가 작동한다.
atomic 연산은 멀티스레드 환경에서 용이하게 사용할 수 있지만, 연산속도가 느리므로 모든 상황에서 사용하지는 않아야한다.
'Server > C++' 카테고리의 다른 글
데드락 ( Dead Lock ) (0) | 2024.07.29 |
---|---|
Lock (0) | 2024.07.29 |
STL) Map과 Set (0) | 2024.07.28 |
STL) Vector와 List - iterator (0) | 2024.07.27 |
STL) Vector와 List (0) | 2024.07.27 |