C ++ 11은 표준화 된 메모리 모델을 도입했습니다. 무슨 뜻이에요? 그리고 C ++ 프로그래밍에 어떤 영향을 미치나요?


1894

C ++ 11은 표준화 된 메모리 모델을 도입했지만 정확히 무엇을 의미합니까? 그리고 C ++ 프로그래밍에 어떤 영향을 미치나요?

Herb Sutter 를 인용 한 Gavin Clarke 의이 기사 는 다음과 같이 말합니다.

메모리 모델은 C ++ 코드에 컴파일러를 만든 사람과 실행중인 플랫폼에 관계없이 호출 할 표준화 된 라이브러리가 있음을 의미합니다. 다른 스레드가 프로세서의 메모리와 통신하는 방식을 제어하는 ​​표준 방법이 있습니다.

Sutter 는 "표준에있는 다른 코어에 [코드]를 분할하는 것에 대해 이야기 할 때 메모리 모델에 대해 이야기하고있다. 사람들이 코드에서 할 다음 가정을 어 기지 않고이를 최적화 할 것"이라고 Sutter 는 말했다.

글쎄, 나는 온라인에서 사용할 수있는이 단락과 유사한 단락을 기억할 수 있으며 (출생 후 내 자신의 메모리 모델을 가지고 있기 때문에 : P) 심지어 다른 사람들이 질문 한 질문에 대한 답변으로 게시 할 수도 있지만 정직하게 말하면 정확히 이해하지 못한다 이.

C ++ 프로그래머는 이전에도 멀티 스레드 응용 프로그램을 개발하는 데 사용되었으므로 POSIX 스레드, Windows 스레드 또는 C ++ 11 스레드의 경우 어떻게 중요합니까? 장점은 무엇입니까? 저수준 세부 사항을 이해하고 싶습니다.

또한 C ++ 11 메모리 모델이 C ++ 11 멀티 스레딩 지원과 관련이 있다는 느낌을 얻습니다. 그렇다면 얼마나 정확합니까? 왜 관련되어야합니까?

멀티 스레딩의 내부 작동 방식과 일반적인 메모리 모델의 의미를 모르므로 이러한 개념을 이해하도록 도와주십시오. :-)


3
@curiousguy : 정교한 ...
Nawaz

4
@ curiousguy : 블로그를 작성하고 수정 사항을 제안하십시오. 포인트를 유효하고 합리적으로 만드는 다른 방법은 없습니다.
Nawaz

2
Q를 요청하고 아이디어를 교환 할 수있는 장소로 그 사이트를 착각했습니다. 내 잘못이야; Herb Sutter가 던지기 사양에 대해 명백히 모순되는 경우에도 동의하지 않는 곳입니다.
curiousguy

5
@ curiousguy : C ++은 인터넷의 임의의 사람이 아니라 표준이 말하는 것입니다. 따라서 표준에 부합 해야합니다. C ++은 표준에 맞지 않는 것에 대해 이야기 할 수있는 열린 철학이 아닙니다 .
Nawaz

3
"저는 C ++ 프로그램이 제대로 정의 된 동작을 가질 수 없음을 증명했습니다." . 아무런 증거없이 키 큰 주장!
Nawaz

답변:


2204

먼저 언어 변호사처럼 생각하는 법을 배워야합니다.

C ++ 사양은 특정 컴파일러, 운영 체제 또는 CPU를 참조하지 않습니다. 실제 시스템의 일반화 인 추상 기계 를 참조 합니다. 언어 변호사 세계에서 프로그래머의 임무는 추상 기계의 코드를 작성하는 것입니다. 컴파일러의 임무는 콘크리트 기계에서 해당 코드를 실현하는 것입니다. 사양을 엄격하게 코딩하면 코드는 오늘날이든 50 년이든 호환 C ++ 컴파일러를 사용하는 시스템에서 코드를 수정하지 않고 컴파일하고 실행할 수 있습니다.

C ++ 98 / C ++ 03 사양의 추상 머신은 기본적으로 단일 스레드입니다. 따라서 스펙과 관련하여 "완전히 이식 가능한"다중 스레드 C ++ 코드를 작성할 수 없습니다. 스펙은 메모리로드 및 저장 의 원 자성 또는 로드 및 저장이 발생할 수 있는 순서 에 대해 아무 것도 말하지 않으며 뮤텍스와 같은 것을 신경 쓰지 마십시오.

물론 pthread 또는 Windows와 같은 특정 콘크리트 시스템에 실제로 다중 스레드 코드를 작성할 수 있습니다. 그러나 C ++ 98 / C ++ 03을위한 멀티 스레드 코드를 작성하는 표준 방법 은 없습니다 .

C ++ 11의 추상 머신은 설계 상 다중 스레드입니다. 또한 잘 정의 된 메모리 모델을 가지고 있습니다 . 즉, 메모리에 액세스 할 때 컴파일러에서 수행 할 수있는 작업과 수행 할 수없는 작업을 나타냅니다.

두 개의 스레드가 한 쌍의 전역 변수에 동시에 액세스하는 다음 예를 고려하십시오.

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

스레드 2가 무엇을 출력 할 수 있습니까?

C ++ 98 / C ++ 03에서 이것은 정의되지 않은 동작도 아닙니다. 표준은 "스레드"라고 불리는 것을 고려하지 않기 때문에 질문 자체는 의미 가 없습니다.

C ++ 11에서는로드 및 저장이 일반적으로 원자 일 필요가 없으므로 결과는 정의되지 않은 동작입니다. 어느 정도 개선되지 않은 것처럼 보일 수 있습니다 ... 그리고 그 자체로는 그렇지 않습니다.

그러나 C ++ 11을 사용하면 다음과 같이 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

이제 상황이 훨씬 더 흥미로워집니다. 우선, 여기에 동작이 정의되어 있습니다. 스레드 2는 이제 0 0스레드 1 이전에 실행되는 경우 (스레드 1 37 17이후에 실행되는 경우) 또는 0 17스레드 1이 x에 할당되었지만 y에 할당되기 전에 실행 된 경우 인쇄 할 수 있습니다.

37 0C ++ 11의 원자 적로드 / 스토어에 대한 기본 모드는 순차적 일관성을 유지 하기 때문에 인쇄 할 수 없습니다 . 이는 모든로드와 저장소가 각 스레드 내에서 작성한 순서대로 "있는 것처럼"발생해야한다는 것을 의미하지만 스레드 간의 작업은 인터리브 될 수 있지만 시스템은 좋아합니다. 그래서 아토의 기본 동작은 모두 제공 자성주문 로드 및 저장에 대한합니다.

이제 최신 CPU에서는 순차 일관성을 보장하는 데 많은 비용이들 수 있습니다. 특히, 컴파일러는 여기에서 모든 액세스 사이에 완전한 메모리 장벽을 생성 할 수 있습니다. 그러나 알고리즘이 비 순차적로드 및 저장을 허용 할 수있는 경우 즉, 원 자성이 필요하지만 순서는 필요하지 않은 경우; 즉, 37 0이 프로그램의 출력으로 허용 할 수 있으면 다음과 같이 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

최신 CPU 일수록 이전 예보다 빠를 가능성이 높습니다.

마지막으로 특정로드 및 스토어를 순서대로 유지해야하는 경우 다음과 같이 작성할 수 있습니다.

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

이로 인해 주문 된로드 및 저장으로 되돌아 가서 37 0더 이상 가능한 출력은 아니지만 최소한의 오버 헤드로 그렇게됩니다. (이 사소한 예에서 결과는 완전한 순차 일관성과 동일하지만 더 큰 프로그램에서는 그렇지 않습니다.)

물론보고자하는 유일한 출력이 0 0or 37 17인 경우 원래 코드를 뮤텍스로 감쌀 수 있습니다. 그러나 당신이 이것을 멀리 읽었다면, 나는 그것이 어떻게 작동하는지 이미 알고 있으며,이 대답은 이미 의도했던 것보다 깁니다 :-).

결론입니다. 뮤텍스는 훌륭하고 C ++ 11은 그것들을 표준화합니다. 그러나 때때로 성능상의 이유로 하위 레벨 기본 요소 (예 : 클래식 이중 검사 잠금 패턴 ) 를 원합니다 . 새로운 표준은 뮤텍스 및 조건 변수와 같은 고급 장치를 제공하며 원자 유형 및 다양한 메모리 장벽과 같은 하위 장치를 제공합니다. 이제 표준에 지정된 언어 내에서 정교한 고성능 동시 루틴을 작성할 수 있으며 오늘날 시스템과 내일의 코드에서 코드가 컴파일되고 변경되지 않을 것입니다.

솔직하지만 전문가가 아니고 심각한 저수준 코드를 작성하지 않는 한 뮤텍스와 조건 변수를 고수해야합니다. 그것이 내가하려는 일입니다.

이 내용에 대한 자세한 내용은 이 블로그 게시물을 참조하십시오 .


37
좋은 대답이지만, 이것은 실제로 새로운 프리미티브의 실제 예를 구걸하고 있습니다. 또한 프리미티브가없는 메모리 순서는 C ++ 0x 이전과 동일하다고 생각합니다. 보장은 없습니다.
존 리플리

5
@ 존 : 알아요,하지만 여전히 원시 요소를 배우고 있습니다 :-). 또한 바이트 액세스가 원자 적 (순서는 아니지만)임을 보증한다고 생각합니다. 왜냐하면 나는 예를 들어 "char"와 함께갔습니다 ...하지만 100 % 확신 할 수는 없습니다 ... 좋은 제안을하고 싶다면 " tutorial "참고 문헌을 답변에 추가하겠습니다
Nemo

48
@ 나와 즈 : 예! 메모리 액세스는 컴파일러 또는 CPU에 의해 재정렬 될 수 있습니다. 캐시 및 추론 적로드에 대해 생각해보십시오. 시스템 메모리가 적중되는 순서는 코딩 한 것과 다를 수 있습니다. 컴파일러와 CPU는 이러한 재정렬이 단일 스레드 코드를 손상시키지 않도록 합니다. 멀티 스레드 코드의 경우, "메모리 모델"은 가능한 재주문을 특징으로하며, 두 스레드가 동시에 같은 위치를 읽고 쓰는 경우에 발생하는 상황과 두 가지에 대한 제어를 수행하는 방법을 나타냅니다. 단일 스레드 코드의 경우 메모리 모델은 관련이 없습니다.
Nemo

26
@Nawaz, @Nemo-사소한 세부 사항 : 새 메모리 모델은와 같은 특정 표현식의 정의되지 않은 것을 지정하는 한 단일 스레드 코드와 관련이 있습니다 i = i++. 시퀀스 포인트 의 오래된 개념은 폐기되었습니다. 새로운 표준 은보다 일반적인 스레드 간 발생 개념 의 특수한 경우 인 시퀀스 전 관계를 사용하여 동일한 것을 지정합니다 .
JohannesD

17
@ AJG85 : C ++ 0x 사양 초안 3.6.2 절에 "정적 저장 기간 (3.7.1) 또는 스레드 저장 기간 (3.7.2)을 가진 변수는 다른 초기화를 수행하기 전에 0으로 초기화 (8.5)되어야합니다. 장소." 이 예제에서 x, y는 전역이므로 정적 저장 기간이 있으므로 0으로 초기화됩니다.
니모

345

메모리 일관성 모델 (또는 메모리 모델)을 이해하는 것과 유사합니다. Leslie Lamport의 주요 논문 인 "분산 시스템에서 시간, 시계 및 이벤트 순서" 에서 영감을 얻었습니다 . 비유는 적절하고 근본적인 의미가 있지만 많은 사람들에게 과잉 일 수 있습니다. 그러나 메모리 일관성 모델에 대한 추론을 용이하게하는 정신적 이미지 (그림 표현)를 제공하기를 바랍니다.

가로축이 주소 공간을 나타내는 (즉, 각 메모리 위치는 해당 축의 한 점으로 표시됨) 세로축은 시간을 나타내는 시공간 다이어그램에서 모든 메모리 위치의 이력을 보도록하겠습니다. 일반적으로 보편적 인 시간 개념은 없습니다). 따라서 각 메모리 위치에 보유 된 값의 히스토리는 해당 메모리 주소의 세로 열로 표시됩니다. 각 값 변경은 스레드 중 하나가 해당 위치에 새 값을 작성하기 때문입니다. (A)에 의해 메모리 이미지 , 우리가 관찰 할 수있는 모든 메모리 위치의 값의 합계 / 조합을 의미합니다 특정 시간에 의해 특정 스레드를 .

"메모리 일관성 및 캐시 일관성에 대한 입문서" 에서 인용

직관적이고 가장 제한적인 메모리 모델은 멀티 스레드 실행이 마치 단일 코어 프로세서에서 스레드가 시간 다중화되는 것처럼 각 구성 스레드의 순차적 실행 인터리빙처럼 보이는 순차 일관성 (SC)입니다.

이 전역 메모리 순서는 프로그램 실행마다 다를 수 있으며 미리 알 수 없습니다. SC의 특징 은 동시 - 평면 (즉, 메모리 이미지)을 나타내는 어드레스-스페이스-시간 다이어그램에서 수평 슬라이스 세트이다 . 지정된 평면에서 모든 이벤트 (또는 메모리 값)가 동시에 발생합니다. Absolute Time 개념은 모든 스레드가 어떤 메모리 값이 동시에 일치하는지 동의합니다. SC에서는 매 순간마다 모든 스레드가 공유하는 메모리 이미지가 하나만 있습니다. 즉, 모든 순간에 모든 프로세서가 메모리 이미지 (즉, 총 메모리 내용)에 동의합니다. 이것은 모든 스레드가 모든 메모리 위치에 대해 동일한 순서의 값을 볼뿐만 아니라 모든 프로세서가 동일한 것을 관찰한다는 것을 의미합니다모든 변수 의 값 조합 . 이것은 모든 메모리 위치 (모든 메모리 위치에서)가 모든 스레드에 의해 동일한 총 순서로 관찰된다는 것과 같습니다.

완화 된 메모리 모델에서 각 스레드는 고유 한 방식으로 주소 공간 시간을 분할합니다. 모든 스레드가 모든 개별 메모리 위치의 히스토리에 동의해야하기 때문에 각 스레드의 슬라이스가 서로 교차하지 않아야한다는 제한 사항이 있습니다 (물론 다른 스레드의 조각은 서로 교차 할 수 있습니다. 그것을 분리하는 보편적 인 방법은 없습니다 (주소 공간 시간의 특권 한 잎이 없음). 슬라이스는 평면 (또는 선형) 일 필요는 없습니다. 그것들은 구부러 질 수 있으며 이것은 쓰레드가 쓰인 순서대로 다른 쓰레드에 의해 쓰인 값을 읽도록 만들 수있는 것입니다. 다른 메모리 위치의 히스토리는 특정 쓰레드에 의해 볼 때 서로에 대해 임의로 미끄러지거나 늘어날 수 있습니다.. 각 스레드는 서로 다른 이벤트 (또는 동등하게 메모리 값)가 동시에 발생한다는 의미를 갖습니다. 한 스레드와 동시에 발생하는 이벤트 세트 (또는 메모리 값)는 다른 스레드와 동시에 발생하지 않습니다. 따라서, 완화 된 메모리 모델에서, 모든 스레드는 여전히 각 메모리 위치에 대해 동일한 이력 (즉, 일련의 값)을 관찰합니다. 그러나 서로 다른 메모리 이미지 (즉, 모든 메모리 위치의 값 조합)를 관찰 할 수 있습니다. 두 개의 서로 다른 메모리 위치가 동일한 스레드에 의해 순차적으로 쓰여지더라도, 새로 작성된 두 개의 값은 다른 스레드에 의해 다른 순서로 관찰 될 수 있습니다.

[위키 백과 사진] Wikipedia의 그림

아인슈타인의 특수 상대성 이론에 익숙한 독자 들은 내가 암시하는 것을 알아 차릴 것입니다. Minkowski의 단어를 메모리 모델 영역으로 변환 : 주소 공간 및 시간은 주소 공간 시간의 그림자입니다. 이 경우 각 관찰자 (즉, 스레드)는 이벤트의 그림자 (예 : 메모리 저장소 /로드)를 자신의 월드 라인 (예 : 시간 축) 및 자체 동시 평면 (주소 공간 축)에 투영합니다. . C ++ 11 메모리 모델의 스레드는 특수 상대성 관계에서 서로에 대해 이동 하는 관찰자에 해당합니다 . 순차적 일관성은 갈릴리 언 시공간에 해당합니다 (즉, 모든 관찰자들은 하나의 절대적 순서의 이벤트와 세계적인 동시성에 동의합니다).

메모리 모델과 특수 상대성 이론의 유사성은 둘 다 부분적으로 정렬 된 이벤트 세트를 종종 인과 세트라고 정의한다는 사실에서 비롯됩니다. 일부 이벤트 (예 : 메모리 저장소)는 다른 이벤트에 영향을 줄 수 있지만 영향을받지는 않습니다. C ++ 11 스레드 (또는 물리 관찰자)는 이벤트 (예 : 메모리로드 및 가능한 다른 주소에 저장)의 체인 (즉, 완전히 정렬 된 집합)에 지나지 않습니다.

상대성 이론에서, 모든 관찰자들이 동의하는 유일한 시간적 순서는 "시간과 같은"사건들 (즉, 원칙적으로 입자가 느리게 연결될 수있는 사건들)의 순서이기 때문에, 일부 순서는 부분적으로 정렬 된 사건들의 겉보기에 혼란스러운 그림으로 복원된다 진공에서 빛의 속도보다). 시간 관련 이벤트 만 일정하게 정렬됩니다. 물리학, 크레이그 캘린더의 시간 .

C ++ 11 메모리 모델에서 이와 유사한 메커니즘 (구매 릴리스 일관성 모델)을 사용하여 이러한 로컬 인과 관계 를 설정합니다 .

메모리 일관성에 대한 정의와 SC를 포기하는 동기를 제공하기 위해 "메모리 일관성 및 캐시 일관성에 대한 입문서" 에서 인용하겠습니다.

공유 메모리 시스템의 경우 메모리 일관성 모델은 메모리 시스템의 구조적으로 보이는 동작을 정의합니다. 단일 프로세서 코어 파티션에 대한 정확성 기준은“ 하나의 올바른 결과 ”와“ 많은 잘못된 대안 ” 사이의 동작을 구분합니다 . 프로세서의 아키텍처는 스레드의 실행이 특정 입력 상태를 비정규 코어에서도 단일의 잘 정의 된 출력 상태로 변환하도록 요구하기 때문입니다. 그러나 공유 메모리 일관성 모델은 여러 스레드의로드 및 저장과 관련이 있으며 일반적으로 많은 올바른 실행을 허용 합니다.많은 (더 많은) 잘못된 것들을 허용하지 않습니다. 여러 번의 올바른 실행 가능성은 ISA가 여러 스레드를 동시에 실행할 수 있도록 허용하기 때문입니다.

완화 되거나 약한 메모리 일관성 모델은 강력한 모델에서 대부분의 메모리 순서가 불필요하다는 사실에 의해 동기가 부여됩니다. 스레드가 10 개의 데이터 항목을 업데이트 한 다음 동기화 플래그를 업데이트하는 경우 프로그래머는 일반적으로 데이터 항목이 서로 순서대로 업데이트되는지는 신경 쓰지 않고 플래그가 업데이트되기 전에 모든 데이터 항목 만 업데이트되도록합니다 (일반적으로 FENCE 명령어를 사용하여 구현 됨) ). 편안한 모델은 이러한 증가 된 주문 유연성을 포착하고 프로그래머가 요구 하는 주문 만 보존하려고 합니다.SC의 성능과 정확성을 모두 향상시킵니다. 예를 들어, 특정 아키텍처에서 FIFO 쓰기 버퍼는 각 코어에서 결과를 캐시에 기록하기 전에 커밋 된 (중지 된) 저장소의 결과를 보유하기 위해 사용됩니다. 이 최적화는 성능을 향상 시키지만 SC를 위반합니다. 쓰기 버퍼는 상점 누락을 처리하는 대기 시간을 숨 깁니다. 상점은 일반적이므로 대부분의 상점에서 실속을 피할 수있는 것이 중요한 이점입니다. 단일 코어 프로세서의 경우, A에 대한 하나 이상의 저장소가 쓰기 버퍼에 있더라도 주소 A에 대한로드가 최신 저장소의 값을 A로 리턴하도록하여 쓰기 버퍼를 구조적으로 보이지 않게 할 수 있습니다. 이는 일반적으로 가장 최근 상점의 값을 A로 우회하여 A에서로드로 우회하여 수행합니다. 여기서 "가장 최근"은 프로그램 순서에 따라 결정됩니다. 또는 A에 대한 저장소가 쓰기 버퍼에있는 경우 A의로드를 중단합니다. 여러 개의 코어를 사용하는 경우 각 코어에는 자체 바이 패스 쓰기 버퍼가 있습니다. 쓰기 버퍼가 없으면 하드웨어는 SC이지만 쓰기 버퍼가 있으면 멀티 코어 프로세서에서 쓰기 버퍼를 구조적으로 볼 수 있습니다.

코어에 비 FIFO 쓰기 버퍼가있어 저장소가 입력 한 순서와 다른 순서로 출발 할 수 있도록하면 저장소 저장소 순서가 바뀔 수 있습니다. 이는 두 번째 적중시 첫 번째 상점이 캐시에서 누락되거나 두 번째 상점이 이전 상점과 통합 될 수있는 경우 (즉, 첫 번째 상점 이전)에 발생할 수 있습니다. 로드 순서 재정렬은 프로그램 순서를 벗어난 명령을 실행하는 동적으로 예약 된 코어에서도 발생할 수 있습니다. 그것은 다른 코어에서 상점을 재정렬하는 것과 동일하게 작동 할 수 있습니다 (두 스레드 사이에 인터리빙 예제가있을 수 있습니까?). 이후의 저장소 (로드 저장소 순서 변경)로 이전로드를 다시 정렬하면 잠금을 해제 한 후 값을로드하는 등의 잘못된 동작 (예 : 저장소가 잠금 해제 작업 인 경우)이 발생할 수 있습니다.

캐시 일관성과 메모리 일관성이 때때로 혼동되기 때문에 다음 인용문을 사용하는 것이 좋습니다.

일관성과 달리 캐시 일관성 은 소프트웨어에 표시되거나 필요하지 않습니다. 일관성은 공유 메모리 시스템의 캐시를 단일 코어 시스템의 캐시처럼 기능적으로 보이지 않게하려고합니다. 올바른 일관성은 프로그래머가로드 및 저장 결과를 분석하여 시스템에 캐시가 있는지 여부를 판별 할 수 없도록합니다. 정확한 일관성은 캐시가 새로운 기능 이나 다른 기능적 동작을 가능하게하지 않기 때문입니다 (프로그래머는 여전히 타이밍을 사용하여 캐시 구조를 유추 할 수 있음)정보). 캐시 일관성 프로토콜의 주요 목적은 모든 메모리 위치에 대해 SWMR (Single-Writer-Multiple-Reader)을 유지하는 것입니다. 일관성과 일관성 사이의 중요한 차이점은 일관성은 메모리 당 위치별로 지정되는 반면 일관성은 모든 메모리 위치 와 관련하여 지정된다는 입니다.

우리의 정신적 그림으로 계속해서, SWMR 불변은 어느 한 위치에 최대 하나의 입자가 있어야하지만 어느 위치 에나 무제한의 관찰자가있을 수있는 물리적 요구 사항에 해당합니다.


52
특별한 상대성이있는 유추에 대해 +1, 나는 같은 유추를 직접 만들려고 노력했습니다. 너무 자주 나는 스레드 코드를 조사하는 프로그래머가 특정 순서로 서로 인터리브되는 다른 스레드의 작업으로 동작을 해석하려고 시도하는 것을 보았습니다. 멀티 프로세서 시스템을 사용하면 서로 다른 <s 사이의 동시성 개념을 말해야합니다. > 참조 프레임 </ s> 스레드는 이제 의미가 없습니다. 특수 상대성 이론을 비교하면 문제의 복잡성을 존중할 수있는 좋은 방법입니다.
Pierre Lebeaupin

71
우주가 멀티 코어라고 결론 내려야합니까?
Peter K

6
@PeterK : 정확히 :) 그리고 여기 물리학 자 Brian Greene이이 그림을 아주 멋지게 시각화 한 것입니다 : youtube.com/watch?v=4BjGWLJNPcA&t=22m12s 22 분의 "시간의 환상 [풀 다큐멘터리]"입니다. 12 초
Ahmed Nassar

2
그것은 단지 나입니까 아니면 1D 메모리 모델 (수평 축)에서 2D 메모리 모델 (동시 평면)로 전환하고 있습니까? 다소 혼란 스럽지만 어쩌면 내가 원어민이 아니기 때문일 것입니다.
안녕 안녕

정확한 타이밍 정보를 사용하지 않고 " 부하 및 저장 결과를 분석하여 "필수 요소를 잊었습니다 .
curiousguy

115

이것은 이제 몇 년 된 질문이지만 매우 인기가 있으므로 C ++ 11 메모리 모델에 대해 배울 수있는 환상적인 리소스를 언급 할 가치가 있습니다. 나는 이것이 또 다른 완전한 답을 만들기 위해 그의 대화를 요약 할 필요는 없지만 이것이 실제로 표준을 작성한 사람이라면 대화를 볼 가치가 있다고 생각합니다.

Herb Sutter는 Channel9 사이트 (1 2 부) 에있는 "atomic <> Weapons"라는 C ++ 11 메모리 모델에 대해 3 시간 동안 긴 대화를 나누었습니다 . 이 대화는 매우 기술적이며 다음 주제를 다룹니다.

  1. 최적화, 레이스 및 메모리 모델
  2. 주문 – 무엇 : 획득 및 출시
  3. 주문 – 방법 : 뮤텍스, 원자 및 / 또는 울타리
  4. 컴파일러 및 하드웨어에 대한 기타 제한
  5. 코드 생성 및 성능 : x86 / x64, IA64, POWER, ARM
  6. 편안한 원자

이 강의는 API에 대한 설명이 아니라 오히려 추론, 배경, 배경 및 배경 뒤에서 진행됩니다 (POWER 및 ARM이 동기화 된로드를 효율적으로 지원하지 않기 때문에 완화 된 의미가 표준에 추가되었다는 것을 알고 계셨습니까?).


10
그 이야기는 정말 환상적이며, 당신이 그것을 보는데 소비 할 3 시간의 가치가 있습니다.
ZunTzu

5
@ZunTzu : 대부분의 비디오 플레이어에서 속도를 원본의 1.25, 1.5 또는 2 배로 설정할 수 있습니다.
Christian Severin

4
@eran 당신은 슬라이드가 발생합니까? 채널 9 대화 페이지의 링크가 작동하지 않습니다.
athos

2
@athos 나는 그들을 가지고 있지 않습니다, 죄송합니다. 채널 9에 연락해보십시오. 제거가 의도적이라고 생각하지 않습니다 (제 생각에는 허브 Sutter에서 링크를 가져 와서 게시 한 다음 나중에 파일을 제거했다고 생각합니다. 그러나 그것은 단지 추측 일뿐입니다 ...)
eran

75

이는 표준이 이제 멀티 스레딩을 정의하고 여러 스레드의 상황에서 발생하는 사항을 정의 함을 의미합니다. 물론 사람들은 다양한 구현을 사용했지만 std::string모두 홈 롤링 string클래스를 사용할 수 있는 시기를 묻는 이유와 같습니다 .

POSIX 스레드 또는 Windows 스레드에 대해 이야기 할 때 이는 실제로 실행되는 하드웨어 기능이기 때문에 실제로 x86 스레드에 대해 이야기하는 것처럼 약간의 환상입니다. C ++ 0x 메모리 모델은 x86, ARM 또는 MIPS 또는 기타 모든 것을 보장합니다 .


28
Posix 스레드는 x86으로 제한되지 않습니다. 실제로 처음 구현 된 시스템은 x86 시스템이 아닐 수 있습니다. Posix 스레드는 시스템 독립적이며 모든 Posix 플랫폼에서 유효합니다. Posix 스레드가 협력적인 멀티 태스킹을 통해 구현 될 수 있기 때문에 이것이 하드웨어 속성이라는 것도 사실이 아닙니다. 그러나 물론 대부분의 스레딩 문제는 하드웨어 스레딩 구현 및 일부는 멀티 프로세서 / 멀티 코어 시스템에서만 나타납니다.
celtschk

57

메모리 모델을 지정하지 않은 언어의 경우 프로세서 아키텍처에서 지정한 언어 메모리 모델에 대한 코드를 작성합니다 . 프로세서는 성능을 위해 메모리 액세스를 재정렬하도록 선택할 수 있습니다. 따라서 프로그램 에 데이터 레이스가있는 경우 (데이터 레이스가 여러 코어 / 하이 스레드가 동일한 메모리에 동시에 액세스 할 수있는 경우) 프로세서 메모리 모델에 의존하기 때문에 프로그램이 크로스 플랫폼이 아닙니다. 프로세서가 메모리 액세스 순서를 다시 지정할 수있는 방법을 알아 보려면 인텔 또는 AMD 소프트웨어 설명서를 참조하십시오.

매우 중요한 것은 잠금 (및 잠금 기능이있는 동시성 의미론)은 일반적으로 크로스 플랫폼 방식으로 구현됩니다. 따라서 데이터 레이스가없는 다중 스레드 프로그램에서 표준 잠금을 사용하는 경우 크로스 플랫폼 메모리 모델에 대해 걱정할 필요가 없습니다. .

흥미롭게도 C ++ 용 Microsoft 컴파일러는 C ++에서 메모리 모델 부족을 처리하기위한 C ++ 확장 인 휘발성에 대한 의미를 획득 / 릴리스합니다. http://msdn.microsoft.com/ko-kr/library/12a04hfd(v=vs .80) .aspx . 그러나 Windows가 x86 / x64에서만 실행되므로 별다른 의미가 없습니다 (Intel 및 AMD 메모리 모델을 사용하면 언어로 획득 / 릴리스 의미론을 쉽고 효율적으로 구현할 수 있습니다).


2
답을 쓸 때 Windows는 x86 / x64에서만 실행되지만 Windows는 특정 시점에 IA64, MIPS, Alpha AXP64, PowerPC 및 ARM에서 실행됩니다. 오늘날에는 다양한 버전의 ARM에서 실행되며 x86과는 전혀 다른 메모리이며 거의 용서할 수 없습니다.
Lorenzo Dematté

이 링크는 다소 손상되었습니다 (예 : "Visual Studio 2005 Retired documentation" ). 업데이트 하시겠습니까?
피터 Mortensen

3
답을 쓰더라도 사실이 아니었다.
Ben

" 같은 메모리에 동시에 액세스하려면 " 충돌하는 방식으로 액세스
curiousguy

27

뮤텍스를 사용하여 모든 데이터를 보호하는 경우 실제로 걱정할 필요가 없습니다. 뮤텍스는 항상 충분한 순서와 가시성을 보장합니다.

이제 원자 또는 잠금없는 알고리즘을 사용했다면 메모리 모델에 대해 생각해야합니다. 메모리 모델은 원자가 주문 및 가시성 보증을 제공하는시기를 정확하게 설명하고 수작업으로 코드화 된 보증을위한 휴대용 펜스를 제공합니다.

이전에는 컴파일러 내장 함수 또는 일부 상위 레벨 라이브러리를 사용하여 원자를 수행했습니다. 펜스는 CPU 관련 명령 (메모리 장벽)을 사용하여 수행되었을 것입니다.


19
이전의 문제는 뮤텍스 (C ++ 표준의 관점에서)와 같은 것이 없다는 것입니다. 따라서 귀하가 제공 한 유일한 보증은 뮤텍스 제조업체가 제공 한 것입니다. 이제 우리는 플랫폼간에 이식 할 수있는 표준에 의해 제공되는 보증을받습니다.
Martin York

4
@Martin : 어쨌든 하나는 메모리 모델이고 다른 하나는 해당 메모리 모델 위에서 실행되는 원자 및 스레딩 프리미티브입니다.
ninjalj

4
또한 내 요점은 주로 언어 수준에서 대부분 메모리 모델이 없었기 때문에 기본 CPU의 메모리 모델이었습니다. 이제 핵심 언어의 일부인 메모리 모델이 있습니다. OTOH, 뮤텍스 등은 항상 라이브러리로 수행 할 수 있습니다.
ninjalj

3
또한 뮤텍스 라이브러리 를 작성 하려는 사람들에게는 실제 문제가 될 수 있습니다 . CPU, 메모리 컨트롤러, 커널, 컴파일러 및 "C 라이브러리"가 모두 서로 다른 팀에 의해 구현 될 때이 중 일부가 작동하는 방식에 대한 격렬한 의견 차이가있을 수 있습니다. 우리 시스템 프로그래머는 응용 프로그램 수준에 예쁜 외관을 나타 내기 위해해야합니다.
zwol

11
불행히도 언어에 일관된 메모리 모델이 없으면 간단한 뮤텍스로 데이터 구조를 보호하는 것만으로는 충분하지 않습니다. 단일 스레드 컨텍스트에서 의미가있는 다양한 컴파일러 최적화가 있지만 여러 스레드 및 CPU 코어가 작동 할 때 메모리 액세스 및 기타 최적화 순서를 재정렬하면 정의되지 않은 동작이 발생할 수 있습니다. 자세한 내용은 Hans Boehm의 "스레드를 라이브러리로 구현할 수 없음"을 참조하십시오. citeseer.ist.psu.edu/viewdoc/…
exDM69

0

위의 답변은 C ++ 메모리 모델의 가장 기본적인 측면을 보여줍니다. 실제로, std::atomic<>최소한 프로그래머가 과도하게 최적화 될 때까지 (예 : 너무 많은 것들을 이완하려고 할 때 ) 대부분의 "정상 작동".

실수가 여전히 흔한 곳은 sequence locks 입니다. https://www.hpl.hp.com/techreports/2012/HPL-2012-68.pdf 에서 문제에 대한 훌륭하고 읽기 쉬운 토론이 있습니다 . 독자가 잠금 단어에 쓰지 않기 때문에 시퀀스 잠금이 매력적입니다. 다음 코드는 위 기술 보고서의 그림 1을 기반으로하며 C ++에서 시퀀스 잠금을 구현할 때의 문제를 강조합니다.

atomic<uint64_t> seq; // seqlock representation
int data1, data2;     // this data will be protected by seq

T reader() {
    int r1, r2;
    unsigned seq0, seq1;
    while (true) {
        seq0 = seq;
        r1 = data1; // INCORRECT! Data Race!
        r2 = data2; // INCORRECT!
        seq1 = seq;

        // if the lock didn't change while I was reading, and
        // the lock wasn't held while I was reading, then my
        // reads should be valid
        if (seq0 == seq1 && !(seq0 & 1))
            break;
    }
    use(r1, r2);
}

void writer(int new_data1, int new_data2) {
    unsigned seq0 = seq;
    while (true) {
        if ((!(seq0 & 1)) && seq.compare_exchange_weak(seq0, seq0 + 1))
            break; // atomically moving the lock from even to odd is an acquire
    }
    data1 = new_data1;
    data2 = new_data2;
    seq = seq0 + 2; // release the lock by increasing its value to even
}

처음에 솔기로 직관적으로 data1하고 data2필요 할 수 있습니다 atomic<>. 그것들이 원자 적이 지 않다면, reader()그것들이 쓰여진 것과 정확히 동시에 읽을 수 있습니다 writer(). C ++ 메모리 모델에 따르면 실제로 데이터를 사용하지 않는 경우에도reader() 경쟁 입니다. 또한 원자가 아닌 경우 컴파일러는 레지스터에서 각 값의 첫 번째 읽기를 캐시 할 수 있습니다. 분명히 당신은 그것을 원하지 않을 것입니다 ...에서 while루프의 각 반복에서 다시 읽고 싶습니다 reader().

그것들을 만들어서 atomic<>액세스하는 것만으로는 충분하지 않습니다 memory_order_relaxed. 그 이유는 seq (in reader()) 의 읽기가 의미를 획득하기 때문 입니다. 간단히 말해서, X 및 Y가 메모리 액세스 인 경우 X가 Y보다 우선하고, X는 획득 또는 릴리스가 아니며, Y는 획득 인 경우 컴파일러는 X보다 먼저 Y를 다시 정렬 할 수 있습니다. Y가 seq의 두 번째 읽기 인 경우 X 데이터를 읽었으므로 이러한 재정렬은 잠금 구현을 중단시킵니다.

이 논문은 몇 가지 해결책을 제시합니다. 오늘날 최고의 성능을 가진 것은 아마도 seqlock의 두 번째 읽기 전에atomic_thread_fence with 를 사용하는 것일 것입니다 . 이 문서에서는 그림 6입니다.이 코드를 지금까지 읽은 사람은 실제로이 문서를 읽어야하기 때문에 코드를 재생산하지 않습니다. 이 게시물보다 더 정확하고 완벽합니다.memory_order_relaxed

마지막 문제는 data변수를 원자 적 으로 만드는 것이 부자연 스럽다는 것 입니다. 코드를 사용할 수 없으면 원자가 아닌 원자에서 원자로 캐스트하는 것은 기본 유형에만 적용되므로 매우주의해야합니다. C ++ 20은를 추가해야하므로이 atomic_ref<>문제를보다 쉽게 ​​해결할 수 있습니다.

요약하자면 C ++ 메모리 모델을 이해한다고 생각하더라도 자체 시퀀스 잠금을 롤링하기 전에 매우주의해야합니다.


-2

C와 C ++는 잘 구성된 프로그램의 실행 추적에 의해 정의되었습니다.

이제 절반은 프로그램의 실행 추적에 의해 정의되고 절반은 동기화 개체에 대한 많은 순서로 정의됩니다.

이 두 가지 접근 방식을 혼합하는 논리적 방법이 없기 때문에 이러한 언어 정의는 전혀 의미가 없습니다. 특히, 뮤텍스 또는 원자 변수의 파괴는 잘 정의되어 있지 않습니다.


언어 설계 개선에 대한 귀하의 열망은 공유하지만, 간단한 사례에 중점을 둔다면 귀하의 답변이 더 가치가있을 것이라고 생각합니다. 당신이 저를 허용하는 경우가 C ++ 디자인에 의해 감지되는 inmense 생산성 혜택의 관련성에 대해 대조되기 때문에 후에 내가 강력하게 추천 것, 그 대답은 각각의 포인트의 관련성에 대한 아주 좋은 논증을 제공합니다
마티아스에게 Haeussler

1
@MatiasHaeussler 내 대답을 잘못 읽은 것 같습니다. 나는 여기에 특정 C ++ 기능의 정의에 반대하지 않습니다 (또한 많은 지적 된 비판이 있지만 여기서는 아닙니다). 여기서는 C ++ (또는 C)에 잘 정의 된 구문이 없다고 주장합니다. 더 이상 순차적 의미론이 없기 때문에 전체 MT 의미론은 완전한 혼란입니다. (저는 Java MT가 망가 졌다고 생각합니다.) "간단한 예"는 거의 모든 MT 프로그램입니다. 동의하지 않으면 MT C ++ 프로그램의 정확성을 증명하는 방법에 대한 내 질문에 대답 할 수 있습니다 .
curiousguy

흥미롭게도, 당신의 질문을 읽은 후 당신이 의미하는 바를 더 잘 이해하고 있다고 생각합니다. 내가 옳다면 C ++ MT 프로그램의 정확성을위한 증명을 개발할 수 없다는 것을 말하는 것이다 . 그런 경우에 나는 컴퓨터 프로그래밍의 미래, 특히 인공 지능의 도착에있어 매우 중요한 무언가라고 말할 것이다. 그러나 많은 사람들이 스택 오버플로에 대해 질문하는 것은 심지어 그들이 알고있는 것이 아니며, 의미하는 바를 이해하고 관심을
얻은 후에도

1
"컴퓨터 프로그램의 데모 가능성에 대한 질문이 stackoverflow 또는 stackexchange에 게시되어야 하는가?" 이것은 메타 스택 오버 플로우를위한 것 같습니다. 그렇지 않습니까?
Matias Haeussler

1
@MatiasHaeussler 1) C와 C ++는 기본적으로 원자 변수, 뮤텍스 및 멀티 스레딩의 "메모리 모델"을 공유합니다. 2) 이것에 대한 관련성은 "메모리 모델"의 이점에 관한 것입니다. 나는 모델이 소리가 나지 않기 때문에 이익이 0이라고 생각합니다.
curiousguy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.