2009-05-18 4 views
29

참조로 캐시 유형의 메모리를 읽는 멀티 스레드 프로그램이있는 경우. 예기치 않은 값을 읽는 다른 스레드를 위험에 빠뜨리지 않고 주 스레드로이 포인터를 변경할 수 있습니까?C에서 포인터를 변경하는 것이 원자 적 동작으로 간주됩니까?

변경된 내용이 원자 단위 인 경우 다른 스레드는 이전 값이나 더 새로운 값을 읽습니다. 절대 임의의 메모리 (또는 null 포인터), 맞지?

어쨌든 동기화 방법을 사용해야한다는 것을 알고 있지만 여전히 궁금합니다.

포인터가 원자 적으로 변경됩니까?

업데이트 : 내 플랫폼은 64 비트 리눅스 (2.6.29), 나뿐만 아니라 크로스 플랫폼 대답을하고 싶습니다 있지만 :)

답변

23

다른 사람들이 언급했듯이 C 언어에는 아무 것도 없으며 사용자의 플랫폼에 따라 다릅니다.

대부분의 현대 데스크탑 플랫폼에서 워드 크기의 정렬 된 위치에 대한 읽기/쓰기는 원 자성입니다. 하지만 프로세서와 컴파일러가 읽기 및 쓰기를 다시 순서대로 지정하기 때문에 문제가 해결되지 않습니다.

는 예를 들어, 다음의 코드는 세분화된다

스레드 A :

DoWork(); 
workDone = 1; 

스레드 B : workDone에 기록 원자이지만

while(workDone != 0); 

ReceiveResultsOfWork(); 

은 많은 시스템에 존재 프로세서가 DoWork()을 통해 작성한 쓰기 작업을 수행하기 전에 workDone에 대한 쓰기가 다른 프로세서에서 볼 수 있음을 보장하지 않습니다. vi 시 블. 컴파일러는 DoWork() 호출 전에 workDone에 대한 쓰기 순서를 자유롭게 지정할 수도 있습니다. 두 경우 모두 ReceiveResultsOfWork()은 불완전한 데이터 작업을 시작할 수 있습니다.

플랫폼에 따라 적절한 순서가 지정되도록 메모리 펜스 등을 삽입해야 할 수 있습니다. 이것은 옳다는 것은 매우 까다로울 수 있습니다.

또는 잠금 장치 만 사용하십시오. 훨씬 더 간단하고, 올바른 것으로 확인하는 것이 훨씬 쉬우 며, 대부분의 경우에는 충분한 성능을 발휘합니다.

+0

사실, C (또는 C++) 컴파일러가 workDone에 대해 다른 것을 알지 못하면 (예 : 승인되지 않은 "noalias"키워드와 같이) workDone = 1을 앞에 오는 함수 호출 전에 다시 정렬 할 수 있다고 생각하지 않습니다. 반면에, foo = bar; qwerty = uiop; workDone = 1이면 workDone = 1이 움직일 수 있습니다 (또는 workDone이 휘발성이 아닌 경우 일시적으로 레지스터 전용 값이 될 수도 있음). ReceiveResultsOfWork가 qwerty 또는 foo를 보면 workDone이 1인데도 uiop 또는 bar가 표시되지 않을 수 있습니다. DoWork()가 인라인 될 경우 어떤 일이 발생하는지 확인해야합니다. – jesup

+3

컴파일러는 DoWork가 정의 된 방식으로 workDone에 액세스하지 못한다는 것을 입증 할 수 있으면 해당 순서를 변경할 수 있습니다. 이것은 DoWork가 충분히 작고 동일한 번역 단위에서 컴파일러가 인라인으로 결정하면 실제로 발생할 수 있습니다. – derobert

+0

중요한 것은 DoWork() 호출 후에 workDone이 작성되도록 컴파일러가 표준에 구속되지 않는다는 것입니다. 실제로 DoWork()가 실제 함수 호출이라면 컴파일러는 아마 순서를 바꾸지 않을 것입니다. DoWork가 인라인되어 있으면 WorkDone을 ​​DoWork() 이전이나 그 동안 쉽게 다시 정렬 할 수 있습니다. – Michael

12

C 언어는 모든 작업이 원자 여부에 대해 아무것도 말하지 않는다. 나는 8 비트 버스와 16 비트 포인터를 가진 마이크로 컨트롤러에 대해 연구 해왔다. 이러한 시스템에서의 모든 포인터 조작은 잠재적으로 비 원자적일 수 있습니다. 나는 Intel 386s (일부는 16 비트 버스를 가졌다)가 유사한 우려를 불러 일으킨 것을 기억한다. 마찬가지로, 64 비트 CPU를 가지고 있지만 32 비트 데이터 버스를 갖는 시스템을 상상할 수 있습니다. 그러면 비 원자 포인터 작업에 대해 비슷한 우려가있을 수 있습니다. (나는 그러한 시스템이 실제로 존재하는지 확인하지 않았다.)

EDIT : Michael's answer은 읽을만한 가치가있다. 버스 크기 대 포인터 크기는 원 자성과 관련하여 거의 고려하지 않습니다. 그것은 단순히 나를 반기는 첫 반례문이었습니다.

+1

이 있으므로 포인터가 컨텍스트 전환시 절반로드 될 수 있습니까? – ojblass

+1

@ojblass : 이런 일은 오래된 아키텍처에서 발생했습니다. 그들은 새로운 것들로도 일어날 수 있습니다. (내 업데이트 된 설명을 참조하십시오.) –

+1

매일 매일 무언가를 배웁니다. 감사합니다 ... – ojblass

5

플랫폼을 언급하지 않았습니다. 그래서 약간 더 정확한 질문이 될 것이라고 생각합니다

포인터 변경 사항이 원자 적으로 보장됩니까?

이 동작에서 다양한 C/C++ 구현이 다를 수 있으므로 구별이 필요합니다. 특정 플랫폼이 원자 할당을 보장하면서도 여전히 표준 내에있을 수 있습니다.

C/C++에서 이것이 전체적으로 보장되는지 여부에 대한 대답은 아니요입니다. C 표준은 이러한 보증을 제공하지 않습니다. 포인터 할당이 원자적임을 보장하는 유일한 방법은 할당의 원 자성을 보장하기 위해 플랫폼 특정 메커니즘을 사용하는 것입니다. 예를 들어, Win32의 Interlocked 메소드는이 보증을 제공합니다.

어떤 플랫폼에서 작업하고 있습니까?

4

cop-out 대답은 C 스펙은 포인터 할당이 원자적일 필요가 없기 때문에 원자적일 수는 없다.

실제 응답은 플랫폼, 컴파일러 및 프로그램을 작성한 날의 별 정렬에 따라 달라질 수 있습니다.

+2

그건 경찰이 아니야. 그것은 옳은 대답입니다. 당신이 동시성을 사용한다면 * 필요하다 *. 그렇지 않으면 당신은 한 - in - a - bazillion heisenbug을 복제하려고 더 많은 시간을 낭비합니다. –

1

표준에서 보증하는 유일한 것은 sig_atomic_t 유형입니다.

다른 답변에서 보았 듯이 일반 x86 아키텍처를 대상으로 할 때는 괜찮을 수 있지만 "전문적인"하드웨어는 매우 위험합니다.

정말로 알고 싶다면 sizeof (sig_atomic_t)를 sizeof (int *)와 비교하여 대상 시스템이 무엇인지 확인할 수 있습니다.

1

상당히 복잡한 질문입니다. 나는 similar question를 물었고 내가 지적한 모든 것을 읽었다. 나는 현대 아키텍처에서 캐싱이 어떻게 작동하는지에 대해 많은 것을 배웠고 결정적인 것을 찾지 못했습니다. 다른 사람들이 말했듯이 버스 폭이 포인터 비트 너비보다 작 으면 문제가 생길 수 있습니다. 특히 데이터가 캐시 라인 경계를 넘어서는 경우

신중한 아키텍처는 잠금 장치를 사용합니다.

3

'보통'포인터 수정은 원자적일 수 있습니다.

C 표준이 아닌 'Compare and Swap'(CAS) 및 기타 원자 연산을 검사하지만 대부분의 컴파일러는 프로세서 기본 요소에 대한 일부 액세스 권한을가집니다. GNU gcc의 경우에는 built-in functions

관련 문제