2010-05-24 3 views
7

다중 스레드에서 단일 정수 유형 (예 : long, int, bool, etc ...)에 액세스하는 경우에는 다음과 같은 동기화 메커니즘을 사용해야합니까? 그들을 잠그기위한 뮤텍스. 내 이해는 원자 유형으로, 내가 하나의 스레드에 대한 액세스를 잠글 필요가 없다지만, 거기에 잠금을 사용하는 많은 코드를 참조하십시오. 이러한 코드를 프로파일 링하면 잠금을 사용하면 성능이 크게 저하된다는 것을 알 수 있습니다. 그래서 내가 액세스하는 항목이 32 비트 프로세서에서 4 바이트와 같은 정수의 버스 너비에 해당하는 경우 여러 스레드에서 사용될 때 액세스 권한을 잠글 필요가 있습니까? 다시 말하면, 쓰레드 A가 쓰레드 B가 같은 변수로부터 읽는 동안 정수 변수 X에 쓰는 중이라면, 쓰레드 B가 몇 바이트의 이전 값을 섞어서 끝낼 수있는가? 가치는 쓰여졌습니까? 이 아키텍처는 예를 들어 32 비트 시스템에서는 4 바이트 정수를 사용할 수 있지만 64 비트 시스템에서는 8 바이트 정수에서는 안전하지 않습니까?C++ 스레드에서 정수로 잠금을 사용해야합니까?

편집 : 그냥 보았습니다. related post 이는 공정한 도움이됩니다.

답변

7

절대 값을 잠글 수 없습니다. 값에 연산을 잠그고 있습니다.

C & C++는 스레드 또는 원자 연산을 명시 적으로 언급하지 않습니다. 따라서 원자 연산이 가능하거나 원자 일 수있는 것처럼 보이는 연산은 언어 사양에 의해 원자 적으로 보장되지 않습니다.

아마도 int에서 비 원자 읽기를 관리하는 꽤 이상한 컴파일러가 될 것입니다. 값을 읽는 연산이 있다면 아마 그것을 지킬 필요가 없을 것입니다. 그러나 기계어 경계를 넘어서는 경우 원자가 아닐 수도 있습니다.

간단히 말해서 m_counter++과 같은 조작은 가져 오기, 증가 및 저장 작업을 포함합니다. 경쟁 조건 : 다른 스레드가 가져 오기 후 값을 변경할 수 있지만 저장소 이전에는 값을 변경할 수 있으므로 뮤텍스로 보호해야합니다. 컴파일러는 연동 연산을 지원합니다. MSVC에는 _InterlockedIncrement()와 같은 기능이 있습니다.이 함수는 다른 모든 쓰기가 인터 로킹 된 api를 사용하여 메모리 위치를 업데이트하는 것과 비슷하게 중요하게 중요 섹션을 호출하는 것보다 훨씬 가볍습니다.

GCC는 __sync_add_and_fetch과 같은 내장 함수를 가지고 있으며이 함수는 기계어 단어 값에 대해 연동 연산을 수행하는데도 사용할 수 있습니다.

+0

감사합니다. InterlockedExchange는 아마도 내가 찾고있는 함수 일 것입니다. 문제의 변수에 실제로 쓰는 스레드는 한 스레드 뿐이며 단순히 읽는 스레드 일뿐입니다. –

4

C++에서 원자 변수를 지원하지 않으므로 잠금이 필요합니다. 잠금을 사용하지 않으면 정확한 조작 방법이 데이터 조작에 사용되는 것과 해당 지시가 원자 적 액세스를 보장하는지 여부 만 알 수 있습니다. 이는 신뢰할 수있는 소프트웨어를 개발하는 방법이 아닙니다.

+4

나는 (다소) 동의하지 않으면 안된다. C++에는 원자 변수가 없지만 잠금은 없습니다. 멀티 스레딩을 시작하자마자 특정 컴파일러가 제공하는 보증에 의존해야합니다. 그리고 원자력에 대한 많은 보장이 있습니다. 일반적으로 단어 크기의 객체에 대한 액세스는 원자 적입니다. 물론, C++ 언어가이를 보장하지는 않지만 특정 컴파일러는 아마 그렇게 할 것입니다. 물론 컴파일러는 일반적으로 재정렬에 대한 보장을 거의하지 않으므로 정확히 무엇을하고 있는지에 따라 잠금 또는 적어도 메모리 장벽이 필요할 수 있습니다. – jalf

3

예 동기화를 사용하는 것이 좋습니다. 여러 스레드가 액세스하는 모든 데이터는 동기화되어야합니다.

Windows 플랫폼 인 경우 여기에서 확인할 수도 있습니다 : Interlocked Variable Access.

2

예. Windows를 사용하고 있다면 Interlocked 함수/변수를 살펴보고 Boost 설득의 경우 implementation of atomic variables을 볼 수 있습니다.

부스트가 너무 무거 우면 "atomic c++"을 좋아하는 검색 엔진에 넣으면 생각할 음식을 많이 줄 수 있습니다.

3

99.99 %의 경우 은 겉보기 원자 변수에 액세스하더라도이어야합니다.C++ 컴파일러는 언어 수준에서 멀티 스레딩을 인식하지 못하기 때문에 많은 양의 재정렬을 할 수 있습니다.

사례 : 잠금 해제가 단순히 정수 변수 volatile에 0을 할당하는 스핀 잠금 구현에 의해 물 렸습니다. 컴파일러는 자물쇠 아래에서 실제 작업을하기 전에 잠금 해제 작업을 재정렬하고 있었고, 당연히 불가사의 한 충돌로 이어졌습니다.

참조 :

  1. Lock-Free Code: A False Sense of Security
  2. 개 이상의 코어 당신과 함께 시스템에 있다면 제대로 심지어 정수의 쓰기 불구하고 일을 할 필요Threads Cannot be Implemented as a Library
+1

전혀 놀랄 일이 아닙니다. 재정렬은 여기서 불쾌합니다 ... –

+0

컴파일러 버그. volatile은 최적화 경계입니다. – Joshua

+2

@ Joshua, 나는 풍자를 느낍니까? 그렇지 않다면 컴파일러 버그가 아닙니다.컴파일러가 휘발성이 아닌 비 휘발성 레드/쓰기를 재정렬하는 것을 막을 수는 없으며, 휘발성 읽기/쓰기를 재정렬 할 수는 없습니다. 이 질문보기 : http://stackoverflow.com/questions/2535148/volatile-qualifier-and-compiler-reorderings –

3

원자입니다. 문제는 두 가지로 나뉩니다.

  1. 실제 작성을 최적화하지 않도록 컴파일러를 중단해야합니다! (어느 정도 중요합니다. ;-))
  2. 다른 코어가 변경 사실을 알아 차릴 수 있도록 메모리 장벽이 필요합니다 (C로 모델링 된 것이 아님). 그렇지 않으면 모든 프로세서와 다른 더러운 세부 사항 사이의 캐시에서 엉망이 될 것입니다. 그냥 제일 먼저라면

, 당신은 변수 volatile를 표시와 OK 것,하지만 두 번째는 정말 살인자이며 멀티 코어 시스템의 차이를 볼에만 정말 것입니다. 그 어느 때보 다 훨씬 더 많이 사용되고있는 아키텍처가 될 것입니다 ... 죄송합니다! 시간 낭비되는 것을 멈출 시간; 플랫폼에 맞는 뮤텍스 (또는 동기화 또는 무엇이든) 코드를 사용하고 메모리가 어떻게 작동하는지 자세히 알려줍니다.

2

멀티 스레딩은 어렵고 복잡합니다. 발생할 수있는 문제를 진단하기가 어렵습니다. 특히 인텔 아키텍처에서 정렬 된 32 비트 정수의 읽기 및 쓰기는 프로세서에서 원자 적으로 보장되지만 멀티 스레드 환경에서 그렇게하는 것이 안전하다는 것을 의미하지는 않습니다.

적절한 보호 장치가 없으면 컴파일러 및/또는 프로세서가 코드 블록의 명령어를 재정렬 할 수 있습니다. 레지스터에 변수를 캐시 할 수 있으며 다른 스레드에서는 볼 수 없습니다.

잠금이 비싸고 고성능을 위해 최적화하기 위해 잠금없는 데이터 구조가 다르기는하지만 어렵습니다 바르게. 그리고 문제는 병행 성 버그가 일반적으로 애매하고 디버깅하기 어렵다는 것입니다.

관련 문제