필요한 헤더 파일들은 다음과 같습니다.
#include "pch.h"
#include <iostream>
#include "CorePch.h"
#include <thread>
#include <atomic>
#include <mutex>
#include <windows.h>
#include <future>
멀티스레드 환경에서 CPU 파이프라인 최적화로 인한 메모리 순서 재배치 현상에 대해 알아보겠습니다.
int32 x = 0;
int32 y = 0;
int32 r1 = 0;
int32 r2 = 0;
volatile bool ready;
void Thread_1()
{
while (!ready)
;
y = 1;
r1 = x;
}
void Thread_2()
{
while (!ready)
;
x = 1;
r2 = y;
}
int main()
{
int32 count = 0;
while (true)
{
ready = false;
count++;
x = y = r1 = r2 = 0;
thread t1(Thread_1);
thread t2(Thread_2);
ready = true;
t1.join();
t2.join();
if (r1 == 0 && r2 == 0)
{
break;
}
}
cout << count << " 번만에 빠져나옴." << '\n';
}
위 코드는 Thread_1과 Thread_2라는 두 개의 스레드를 생성하고, 각 스레드에서는 공유 변수 x와 y를 변경한 후 다른 스레드에서 변경한 변수의 값을 읽습니다. 이 코드는 CPU 파이프라인의 최적화로 인한 메모리 순서 재배치가 발생할 경우, 두 스레드가 동시에 실행되면 r1과 r2 모두 0의 값을 가질 수 있음을 보여줍니다.
원자적 연산은 그 연산이 실행되는 동안에는 다른 어떤 연산도 동시에 수행되지 않으므로, 해당 연산이 완료된 후에만 다른 연산이 수행됩니다. 이러한 특성 때문에 원자적 연산은 멀티스레드 환경에서 변수에 안전하게 접근하는 방법으로 자주 사용됩니다. C++에서는 std::atomic 라이브러리를 통해 원자적 연산을 지원합니다.
#include <atomic>
#include <thread>
std::atomic<int> x(0), y(0);
int r1, r2;
void Thread_1() {
x.store(1, std::memory_order_relaxed); // 저장
r1 = y.load(std::memory_order_relaxed); // 불러오기
}
void Thread_2() {
y.store(1, std::memory_order_relaxed); // 저장
r2 = x.load(std::memory_order_relaxed); // 불러오기
}
int main() {
std::thread t1(Thread_1);
std::thread t2(Thread_2);
t1.join();
t2.join();
}
이 코드는 std::atomic을 사용해서 x와 y의 순서를 보장하고 있습니다. std::memory_order_relaxed를 사용해서 메모리 순서를 자유롭게 하였습니다.
뮤텍스는 상호 배제(mutual exclusion)를 보장하는 기능입니다. 한 스레드가 뮤텍스를 소유하고 있을 때는 다른 스레드가 그 뮤텍스를 소유할 수 없습니다. 따라서 뮤텍스를 이용하면 여러 스레드가 동시에 동일한 리소스에 접근하는 것을 막을 수 있습니다. C++에서는 std::mutex 클래스를 통해 뮤텍스를 사용할 수 있습니다.
#include <mutex>
#include <thread>
std::mutex mtx;
int x = 0, y = 0;
int r1, r2;
void Thread_1() {
std::lock_guard<std::mutex> lock(mtx);
x = 1;
r1 = y;
}
void Thread_2() {
std::lock_guard<std::mutex> lock(mtx);
y = 1;
r2 = x;
}
int main() {
std::thread t1(Thread_1);
std::thread t2(Thread_2);
t1.join();
t2.join();
}
이 코드는 std::mutex를 사용해서 Thread_1과 Thread_2가 동시에 실행되지 않도록 보장하고 있습니다. std::lock_guard는 생성될 때 std::mutex를 잠그고, 소멸될 때 std::mutex를 풀어줍니다. 따라서, 각 스레드의 Thread_1과 Thread_2가 동시에 실행되지 않음을 보장합니다.