문제

C 프로그래밍 언어 및 Pthreads에서는 스레딩 라이브러리로 사용됩니다.스레드 간에 공유되는 변수/구조를 휘발성으로 선언해야 합니까?자물쇠로 보호될 수도 있고 그렇지 않을 수도 있다고 가정합니다(아마도 장벽).

pthread POSIX 표준에 이에 대한 언급이 있습니까? 이것은 컴파일러에 종속됩니까, 아니면 둘 다입니까?

추가하려면 편집하세요:훌륭한 답변에 감사드립니다.하지만 만약 당신이 ~ 아니다 자물쇠 사용;당신이 사용하고 있다면 어떨까요? 장벽 예를 들어?또는 다음과 같은 기본 요소를 사용하는 코드 비교 및 교환 공유 변수를 직접적이고 원자적으로 수정하려면...

도움이 되었습니까?

해결책

휘발성의 매우 중요한 속성 중 하나는 변수가 수정될 때 메모리에 기록되고 액세스할 때마다 메모리에서 다시 읽도록 한다는 것입니다.여기에 있는 다른 답변은 휘발성과 동기화를 혼합하고 있으며 휘발성이 동기화 프리미티브(크레딧이 필요한 크레딧)가 아니라는 것이 다른 답변에서 분명합니다.

그러나 휘발성을 사용하지 않는 한 컴파일러는 공유 데이터를 레지스터에 얼마든지 자유롭게 캐시할 수 있습니다.컴파일러가 재량에 따라 레지스터에 캐시하는 것이 아니라 예측 가능하게 실제 메모리에 기록되도록 데이터를 작성하려면 해당 데이터를 휘발성으로 표시해야 합니다.또는 공유 데이터를 수정하는 기능을 종료한 후에만 공유 데이터에 액세스하는 경우에는 문제가 없을 수 있습니다.그러나 값이 레지스터에서 메모리로 다시 기록되는지 확인하기 위해 맹목적인 행운에 의존하지 않는 것이 좋습니다.

특히 레지스터가 많은 머신(예: x86 아님)에서는 변수가 레지스터에서 상당히 오랜 기간 동안 존재할 수 있으며, 좋은 컴파일러는 구조의 일부 또는 전체 구조를 레지스터에 캐시할 수도 있습니다.따라서 휘발성을 사용해야 하지만 성능을 위해서는 계산을 위해 로컬 변수에 값을 복사한 다음 명시적으로 다시 쓰기를 수행해야 합니다.본질적으로 휘발성을 효율적으로 사용한다는 것은 C 코드에서 약간의 로드 저장 사고를 수행하는 것을 의미합니다.

어떤 경우든 올바른 프로그램을 생성하려면 일종의 OS 수준에서 제공되는 동기화 메커니즘을 사용해야 합니다.

휘발성의 약점에 대한 예는 내 Decker의 알고리즘 예를 참조하세요. http://jakob.engbloms.se/archives/65, 이는 휘발성이 동기화에 작동하지 않는다는 것을 잘 증명합니다.

다른 팁

변수에 대한 액세스를 제어하기 위해 잠금을 사용하는 한 변수에 대한 휘발성은 필요하지 않습니다.실제로 변수에 휘발성을 추가하는 경우 이미 잘못된 것일 수 있습니다.

https://software.intel.com/en-us/blogs/2007/11/30/휘발성-almost-useless-for-multi-threaded-programming/

대답은 절대적으로, 분명하게 '아니요'입니다.적절한 동기화 프리미티브 외에 '휘발성'을 사용할 필요는 없습니다.수행되어야 하는 모든 작업은 이러한 기본 요소에 의해 수행됩니다.

'휘발성'을 사용하는 것은 필요하지도 충분하지도 않습니다.적절한 동기화 프리미티브로 충분하므로 필요하지 않습니다.문제가 될 수 있는 모든 최적화가 아닌 일부 최적화만 비활성화하기 때문에 충분하지 않습니다.예를 들어 다른 CPU에서는 원자성이나 가시성을 보장하지 않습니다.

그러나 휘발성을 사용하지 않는 한 컴파일러는 공유 데이터를 레지스터에 얼마든지 자유롭게 캐시할 수 있습니다.컴파일러가 재량에 따라 레지스터에 캐시하는 것이 아니라 예측 가능하게 실제 메모리에 기록되도록 데이터를 작성하려면 해당 데이터를 휘발성으로 표시해야 합니다.또는 공유 데이터를 수정하는 기능을 종료한 후에만 공유 데이터에 액세스하는 경우에는 문제가 없을 수 있습니다.그러나 값이 레지스터에서 메모리로 다시 기록되는지 확인하기 위해 맹목적인 행운에 의존하지 않는 것이 좋습니다.

맞습니다. 하지만 휘발성을 사용하더라도 CPU는 공유 데이터를 쓰기 게시 버퍼에 얼마든지 자유롭게 캐시할 수 있습니다.당신을 괴롭힐 수 있는 최적화 세트는 '휘발성'이 비활성화하는 최적화 세트와 정확하게 동일하지 않습니다.따라서 '휘발성'을 사용하면 ~이다 맹목적인 행운에 의지합니다.

반면에 정의된 다중 스레드 의미 체계와 함께 동기화 기본 요소를 사용하면 작업이 보장됩니다.게다가 '휘발성'으로 인해 엄청난 성능 저하가 발생하지 않습니다.그렇다면 그렇게 하면 어떨까요?

휘발성 키워드가 멀티스레드 프로그래밍에 적합하다는 개념이 널리 퍼져 있습니다.

한스 보엠 지적 휘발성에는 세 가지 휴대용 용도만 있습니다.

  • 휘발성 물질 값이 longjmp 전체에 걸쳐 보존되어야 하는 setjmp와 동일한 범위에 있는 지역 변수를 표시하는 데 사용될 수 있습니다.문제의 지역 변수를 공유할 수 있는 방법이 없으면 원자성과 순서 제약 조건이 아무런 영향을 미치지 않기 때문에 이러한 사용 중 어느 부분이 느려지는지는 확실하지 않습니다.(longjmp 전체에서 모든 변수를 보존하도록 요구함으로써 이러한 사용 중 어느 부분이 느려지는지는 확실하지 않지만 이는 별도의 문제이므로 여기서는 고려하지 않습니다.)
  • 휘발성 물질 변수가 "외부적으로 수정"될 수 있을 때 사용될 수 있지만 실제로 수정은 스레드 자체에 의해 동기적으로 트리거됩니다.기본 메모리가 여러 위치에 매핑되어 있기 때문입니다.
  • 휘발성 물질 sigatomic_t는 제한된 방식으로 동일한 스레드의 신호 처리기와 통신하는 데 사용될 수 있습니다.sigatomic_t 사례에 대한 요구 사항을 약화시키는 것을 고려할 수 있지만 이는 다소 직관에 어긋나는 것 같습니다.

당신이있는 경우 멀티스레딩 속도를 위해 코드 속도를 늦추는 것은 확실히 원하는 것이 아닙니다.다중 스레드 프로그래밍의 경우 휘발성이 종종 실수로 해결되는 것으로 생각되는 두 가지 주요 문제가 있습니다.

  • 원자성
  • 메모리 일관성, 즉.다른 스레드에서 볼 수 있는 스레드 작업의 순서입니다.

먼저 (1)을 다루겠습니다.Volatile은 원자성 읽기 또는 쓰기를 보장하지 않습니다.예를 들어, 129비트 구조의 일시적인 읽기 또는 쓰기는 대부분의 최신 하드웨어에서 원자적이지 않습니다.32비트 int의 휘발성 읽기 또는 쓰기는 대부분의 최신 하드웨어에서 원자적이지만 휘발성은 그것과 아무 관련이 없습니다.휘발성이 없으면 원자적일 가능성이 높습니다.원자성은 컴파일러의 변덕에 달려 있습니다.C 또는 C++ 표준에는 원자적이어야 한다는 규정이 없습니다.

이제 문제 (2)를 고려하십시오.때때로 프로그래머는 휘발성을 휘발성 액세스의 최적화를 끄는 것으로 생각합니다.실제로는 대체로 그렇습니다.그러나 이는 비휘발성 액세스가 아닌 휘발성 액세스일 뿐입니다.다음 부분을 고려해보세요.

 volatile int Ready;       

    int Message[100];      

    void foo( int i ) {      

        Message[i/10] = 42;      

        Ready = 1;      

    }

다중 스레드 프로그래밍에서 매우 합리적인 작업을 수행하려고 합니다.메시지를 작성한 다음 다른 스레드로 보냅니다.다른 스레드는 Ready가 0이 아닐 때까지 기다린 다음 메시지를 읽습니다.gcc 4.0 또는 icc를 사용하여 "gcc -O2 -S"로 컴파일해 보세요.둘 다 Ready로 먼저 저장하므로 i/10 계산과 겹칠 수 있습니다.재정렬은 컴파일러 버그가 아닙니다.이는 자신의 역할을 수행하는 공격적인 최적화 프로그램입니다.

해결책은 모든 메모리 참조를 휘발성으로 표시하는 것이라고 생각할 수도 있습니다.그건 정말 어리석은 짓이에요.이전 인용문에서 알 수 있듯이 코드 속도가 느려질뿐입니다.최악의 경우 문제가 해결되지 않을 수도 있습니다.컴파일러가 참조 순서를 변경하지 않더라도 하드웨어는 그럴 수 있습니다.이 예에서는 x86 하드웨어가 순서를 바꾸지 않습니다.Itanium(TM) 프로세서도 마찬가지입니다. 왜냐하면 Itanium 컴파일러는 휘발성 저장소에 메모리 펜스를 삽입하기 때문입니다.그것은 영리한 Itanium 확장입니다.그러나 Power(TM)와 같은 칩은 다시 주문될 것입니다.주문 시 꼭 필요한 것은 메모리 울타리,라고도 함 메모리 장벽.메모리 펜스는 펜스 전체에서 메모리 작업의 재정렬을 방지하거나 경우에 따라 한 방향으로의 재정렬을 방지합니다. 휘발성은 메모리 펜스와 아무 관련이 없습니다.

그렇다면 멀티스레드 프로그래밍을 위한 솔루션은 무엇입니까?원자 및 울타리 의미 체계를 구현하는 라이브러리 또는 언어 확장을 사용합니다.의도한 대로 사용하면 라이브러리의 작업이 올바른 울타리를 삽입합니다.몇 가지 예:

  • POSIX 스레드
  • Windows(TM) 스레드
  • 오픈MP
  • 미정

기반 기사: Arch Robison(인텔)

내 경험상 그렇지 않습니다.해당 값에 쓸 때 스스로 적절하게 뮤텍스를 수행하거나 다른 스레드의 작업에 따라 달라지는 데이터에 액세스해야 하기 전에 스레드가 중지되도록 프로그램을 구성하면 됩니다.내 프로젝트 x264는 이 방법을 사용합니다.스레드는 엄청난 양의 데이터를 공유하지만 읽기 전용이거나 스레드는 데이터에 액세스하기 전에 데이터가 사용 가능하고 완료될 때까지 기다리기 때문에 대부분의 데이터에는 뮤텍스가 필요하지 않습니다.

이제 작업에 모두 크게 인터리브된 스레드가 많이 있는 경우(매우 세밀한 수준에서 서로의 출력에 의존함) 이는 훨씬 더 어려울 수 있습니다. 실제로 그런 경우에는 스레드를 더 많이 분리하여 더 깔끔하게 수행할 수 있는지 알아보려면 스레딩 모델을 다시 검토해 보세요.

아니요.

Volatile CPU 읽기/쓰기 명령과 독립적으로 변경될 수 있는 메모리 위치를 읽을 때만 필요합니다.스레딩 상황에서 CPU는 각 스레드에 대한 메모리 읽기/쓰기를 완전히 제어하므로 컴파일러는 메모리가 일관성이 있다고 가정하고 CPU 명령을 최적화하여 불필요한 메모리 액세스를 줄입니다.

주요 용도는 volatile 메모리 매핑된 I/O에 액세스하기 위한 것입니다.이 경우 기본 장치는 CPU와 독립적으로 메모리 위치 값을 변경할 수 있습니다.사용하지 않는 경우 volatile 이 조건에서 CPU는 새로 업데이트된 값을 읽는 대신 이전에 캐시된 메모리 값을 사용할 수 있습니다.

Volatile은 한 스레드가 무언가를 쓰는 시점과 다른 스레드가 이를 읽는 시점 사이에 지연이 전혀 필요하지 않은 경우에만 유용합니다.하지만 어떤 종류의 자물쇠가 없으면 당신은 아무것도 모릅니다. 언제 다른 스레드가 데이터를 썼는데, 이는 가능한 가장 최근 값이라는 것뿐입니다.

간단한 값(다양한 크기의 int 및 float)의 경우 명시적인 동기화 지점이 필요하지 않으면 뮤텍스가 과도할 수 있습니다.뮤텍스나 일종의 잠금을 사용하지 않는 경우 변수를 휘발성으로 선언해야 합니다.뮤텍스를 사용하면 모든 준비가 완료됩니다.

복잡한 유형의 경우 뮤텍스를 사용해야 합니다.이에 대한 작업은 비원자적이므로 뮤텍스 없이 절반만 변경된 버전을 읽을 수 있습니다.

휘발성은 이 값을 얻거나 설정하기 위해 메모리로 이동해야 함을 의미합니다.휘발성을 설정하지 않으면 컴파일된 코드가 오랫동안 레지스터에 데이터를 저장할 수 있습니다.

이것이 의미하는 바는 한 스레드가 값 수정을 시작하지만 두 번째 스레드가 와서 값을 읽으려고 시도하기 전에 결과를 쓰지 않는 상황이 발생하지 않도록 스레드 간에 공유하는 변수를 휘발성으로 표시해야 한다는 것입니다. .

Volatile은 특정 최적화를 비활성화하는 컴파일러 힌트입니다.컴파일러의 출력 어셈블리는 이 어셈블리가 없어도 안전할 수 있지만 공유 값에는 항상 이를 사용해야 합니다.

이는 시스템에서 제공하는 값비싼 스레드 동기화 개체를 사용하지 않는 경우 특히 중요합니다. 예를 들어 일련의 원자적 변경을 통해 유효성을 유지할 수 있는 데이터 구조가 있을 수 있습니다.메모리를 할당하지 않는 많은 스택은 이러한 데이터 구조의 예입니다. 스택에 값을 추가한 다음 끝 포인터를 이동하거나 끝 포인터를 이동한 후 스택에서 값을 제거할 수 있기 때문입니다.이러한 구조를 구현할 때 휘발성은 원자 명령이 실제로 원자인지 확인하는 데 중요합니다.

근본적인 이유는 C 언어 의미론이 단일 스레드 추상 머신.그리고 컴파일러는 추상 기계에서 프로그램의 '관찰 가능한 동작'이 변경되지 않는 한 프로그램을 변환할 수 있는 자체 권한을 갖습니다.인접하거나 겹치는 메모리 액세스를 병합하거나, 메모리 액세스를 여러 번 다시 실행하거나(예를 들어 레지스터 유출 시), 프로그램이 실행될 때 프로그램의 동작을 생각하면 간단히 메모리 액세스를 삭제할 수 있습니다. 단일 스레드, 변경되지 않습니다.그러므로 당신이 의심할 수 있듯이 행동은 하다 프로그램이 실제로 다중 스레드 방식으로 실행되어야 하는지 여부를 변경하십시오.

Paul Mckenney가 유명한 책에서 지적했듯이 리눅스 커널 문서:

_must_not_는 컴파일러가 read_once () 및 write_once ()에 의해 보호되지 않는 메모리 참조로 원하는 작업을 수행한다고 가정합니다.그것들이 없으면, 컴파일러는 컴파일러 배리어 섹션에서 다루는 모든 종류의 "창의적"변환을 수행 할 권리가 있습니다.

READ_ONCE() 및 WRITE_ONCE()는 참조된 변수에 대한 휘발성 캐스트로 정의됩니다.따라서:

int y;
int x = READ_ONCE(y);

다음과 같습니다:

int y;
int x = *(volatile int *)&y;

따라서 '휘발성' 액세스를 수행하지 않는 한 액세스가 발생한다고 확신할 수 없습니다. 정확히 한 번, 어떤 동기화 메커니즘을 사용하든 상관없습니다.외부 함수(예: pthread_mutex_lock)를 호출하면 컴파일러가 전역 변수에 대한 메모리 액세스를 강제로 수행할 수 있습니다.그러나 이는 컴파일러가 외부 함수가 이러한 전역 변수를 변경하는지 여부를 파악하지 못하는 경우에만 발생합니다.정교한 프로시저 간 분석 및 링크 시간 최적화를 사용하는 최신 컴파일러에서는 이 트릭을 전혀 쓸모 없게 만듭니다.

요약하자면, 여러 스레드에서 공유하는 변수를 휘발성으로 표시하거나 휘발성 캐스트를 사용하여 액세스해야 합니다.


Paul McKenney도 다음과 같이 지적했습니다.

나는 당신의 아이들이 알기를 원하지 않는 최적화 기술에 대해 토론할 때 그들의 눈이 반짝거리는 것을 보았습니다!


하지만 무슨 일이 일어나는지 봐 C11/C++11.

이해가 안 돼요.동기화 프리미티브는 어떻게 컴파일러가 변수 값을 다시 로드하도록 합니까?이미 가지고 있는 최신 사본을 사용하지 않는 이유는 무엇입니까?

휘발성은 변수가 코드 범위 외부에서 업데이트되므로 컴파일러가 해당 변수의 현재 값을 알고 있다고 가정할 수 없음을 의미합니다.메모리 장벽을 인식하지 못하는(맞죠?) 컴파일러가 여전히 캐시된 값을 사용할 수 있으므로 메모리 장벽도 쓸모가 없습니다.

어떤 사람들은 분명히 컴파일러가 동기화 호출을 메모리 장벽으로 취급한다고 가정하고 있습니다."Casey"는 정확히 하나의 CPU가 있다고 가정합니다.

동기화 프리미티브가 외부 함수이고 문제의 기호가 컴파일 단위 외부에 표시되는 경우(전역 이름, 내보낸 포인터, 이를 수정할 수 있는 내보낸 함수) 컴파일러는 이를 또는 기타 외부 함수 호출을 다음과 같이 처리합니다. 외부에서 볼 수 있는 모든 객체에 대한 메모리 펜스.

그렇지 않으면, 당신은 스스로입니다.그리고 휘발성은 컴파일러가 정확하고 빠른 코드를 생성하도록 만드는 데 사용할 수 있는 최고의 도구일 수 있습니다.그러나 휘발성이 필요할 때 일반적으로 이식성이 없으며 실제로 수행되는 작업은 시스템과 컴파일러에 따라 많이 달라집니다.

아니요.

첫 번째, volatile 필요가 없습니다.사용하지 않는 보장된 멀티스레드 의미론을 제공하는 수많은 다른 작업이 있습니다. volatile.여기에는 원자 연산, 뮤텍스 등이 포함됩니다.

두번째, volatile 충분하지 않습니다.C 표준은 선언된 변수의 다중 스레드 동작에 대한 어떠한 보장도 제공하지 않습니다. volatile.

따라서 필요하지도 충분하지도 않으므로 사용하는데 큰 의미가 없습니다.

한 가지 예외는 문서화된 다중 스레드 의미 체계가 있는 특정 플랫폼(예: Visual Studio)입니다.

스레드 간에 공유되는 변수는 '휘발성'으로 선언되어야 합니다.이것은 컴파일러에게 한 스레드가 그러한 변수에 쓸 때, 쓰기는 레지스터와 달리 메모리에 있어야한다는 것을 알려줍니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top