-
파이프라인과 멀티스레드C++/C++ 기타 2022. 10. 25. 22:25728x90
오늘은 파이프라인의 개념에 대해서 간단히 알아보겠습니다.
컴퓨터구조에서 파이프라인 개념의 핵심은
코드의 실행순서가 임의로 바뀔 수 있다는 것입니다.
CPU에서는
- Fetch
- Decode
- Excute
- Write-Back
의 순서로 명령이 실행 됩니다.
그런데,
- 실행순서가 바뀌어도 서로 연관이 없는 명령들이라면
- 순서를 바꿈으로써 속도가 더 빨라질 수 있다면
CPU는 임의로 실행순서를 바꾸기도 합니다.
예를 들어서
int x=5;
y=y+1;
과같은 코드는 두 순서를 바꾸어도 아무런 문제가 없습니다.
그래서 필요하다면 순서가 바뀌기도 하는거구요.
이게 싱글스레드 상황에서는 아무런 문제가 발생하지 않습니다.
그런데 멀티스레드 상황이라면 이야기가 달라집니다.
int x = 0; int y = 0; int r1 = 0; int r2 = 0; volatile bool ready; void Thread_1() { while (ready == false) ; y = 1; //store y r1 = x; //load x } void Thread_2() { while (ready == false) ; x = 1;//store x r2 = y; //loady } int main() { int count = 0; while (true) { ready = false; count++; x = y = r1 = r2 = 0; std::thread t1(Thread_1); std::thread t2(Thread_2); ready = true; t1.join(); t2.join(); if (r1 == 0 && r2 == 0) break; } }
위와 같은 코드가 있다고 가정해보겠습니다.
- 이 경우 x, y, r1, r2를 모두 0으로 초기화 합니다.
- 스레드 1은 y=1 → r1=x를 실행합니다. 이 둘은 전혀 연관없는 코드입니다.
- 스레드 2는 x=1 → r2=y를 실행합니다. 이 둘은 전혀 연관없는 코드입니다.
- 스레드 1과, 2는 ready가 true로 바뀌는 동시에 작업을 시작합니다
- 만약 r1과 r2가 모두 0이 된다면 실행을 종료합니다.
논리적으로 봤을 때는 둘 모두 0이 되는 일이 없어야 합니다.
r1에 x를 대입하기 위해서는 y에 1을 대입한 후에 실행할 수 있고
r2에 y를 대입하기 위해서는 x에 1을 대입한 후에 실행할 수 있기 때문입니다.
하지만, 파이프라인의 특성에 의해서 x와 y가 둘 모두 0이 되는 상황이 발생하게 됩니다.
만약 스레드 1에서 코드의 실행순서가 파이프라인에 의해서
r1=x → y=1로 바뀌었다고 가정해봅시다.
그러면,
- 스레드 1은 x가 0일 때 r1에 x를 대입
- 스레드 2는 x=1을 대입
- 스레드 2는 y가 0일 때 r2에 y를 ㄷ대입
- 스레드 1은 y=1을 대입
이러한 시나리오가 가능해집니다.
참고로 가시성이라는 개념이 있는데
메모리에 있는 데이터와 캐시에 있는 데이터가 일치하지 않는 상황을 말합니다.
CPU는 작업의 편의를 위해서 캐시를 이용합니다.
메모리에서 캐시로 데이터를 읽어와서 캐시에대해서 작업을 수행한 후
작업을 마치면 (혹은 주기적으로) 캐시의 값을 메모리에 씁니다(write)
그런데 멀티스레드 환경에서
- 스레드 1이 메모리에서 x를 읽어와서 x를 1로 변경하고 메모리에 write하기 전에
- 스레드 2가 x를 읽어오려고 할 때 그 값의 불일치가 발생할 수도 있습니다.
희박하나 가능한 시나리오입니다.
728x90'C++ > C++ 기타' 카테고리의 다른 글
캐시(Cache)(feat. Hit Rate, 메모리 계층구조, Locality) (0) 2022.10.24 C++, volatile (0) 2022.10.09