7

그래서 우리는 지금 꽤 오래된 버전의 부스트를 사용하고 있습니다. 업그레이드하기 전까지는 코드에 C++로 원자 적 연산을 사용하기 위해 이 필요합니다.비교 및 ​​스왑 C++

inline uint32_t CAS(volatile uint32_t *mem, uint32_t with, uint32_t cmp) 
{ 
    uint32_t prev = cmp; 
    // This version by Mans Rullgard of Pathscale 
    __asm__ __volatile__ ("lock\n\t" 
      "cmpxchg %2,%0" 
      : "+m"(*mem), "+a"(prev) 
       : "r"(with) 
       : "cc"); 

    return prev; 
} 
기능을 사용

내 코드는 다음과 같은 다소이다 : (우리는 하나 아직 C++ 0X를 사용하지 않는)

나는 다음과 같은 CAS 기능을 만들어

void myFunc(uint32_t &masterDeserialize) 
{ 
    std::ostringstream debugStream; 

    unsigned int tid = pthread_self(); 
    debugStream << "myFunc, threadId: " << tid << " masterDeserialize= " << masterDeserialize << " masterAddress = " << &masterDeserialize << std::endl; 

    // memory fence 
    __asm__ __volatile__ ("" ::: "memory"); 
    uint32_t retMaster = CAS(&masterDeserialize, 1, 0); 
    debugStream << "After cas, threadid = " << tid << " retMaster = " << retMaster << " MasterDeserialize = " << masterDeserialize << " masterAddress = " << &masterDeserialize << std::endl; 
    if(retMaster != 0) // not master deserializer. 
    { 
     debugStream << "getConfigurationRowField, threadId: " << tid << " NOT master. retMaster = " << retMaster << std::endl; 

     DO SOMETHING... 
    } 
    else 
    { 
     debugStream << "getConfigurationRowField, threadId: " << tid << " MASTER. retMaster = " << retMaster << std::endl; 

     DO SOME LOGIC 

     // Signal we're done deserializing. 
     masterDeserialize = 0; 
    } 
    std::cout << debugStream.str(); 
} 

내 이 코드를 테스트하면 10 개의 스레드가 생성되고 모두 동일한 masterDeserialize 변수를 사용하여 함수를 호출하도록 신호를 보냅니다.

이 방법은 대부분 잘 작동하지만 두 번 스레드가 모두 마스터 잠금을 획득 할 수있는 경로에 들어가는 횟수는 천 2 백만 회입니다.

나는 이것이 어떻게 가능한지, 또는 그것을 피하는 방법을 모르겠습니다.

내가 메모리 펜스를 사용하여 masterDeserialize를 재설정하기 전에 CPU OOO가 영향을 미칠 수 있다고 생각했지만 결과에 아무런 영향을주지 않습니다.

분명히 이것은 많은 코어가있는 시스템에서 실행되며 디버그 모드로 컴파일되므로 GCC는 최적화를 위해 실행 순서를 바꾸면 안됩니다.

위의 사항에 대한 잘못된 제안 사항이 있으십니까?

편집 : 어셈블리 코드 대신 gcc 프리미티브를 사용하여 동일한 결과를 얻었습니다.

inline uint32_t CAS(volatile uint32_t *mem, uint32_t with, uint32_t cmp) 
{ 
    return __sync_val_compare_and_swap(mem, cmp, with); 
} 
내가 멀티 코어, 멀티 CPU 시스템에서 실행하고, 그러나, 가상 기계가이 동작은 VM에 의해 어떻게 든 발생 가능성이

?

+0

두 플래그가 실제로 동일한 코드 경로를 동시에 입력하는 것을 어떻게 알 수 있습니까? 디버그 출력은 잠금에 의해 보호되지 않습니다. 또한, 완전성을 위해서만 약한 순서의 아키텍쳐에서만 중요합니다 (이것은 x86/x86-64와 많이 닮았습니다) :'masterDeserialize'의 설정은 원자 적 연산이어야합니다. – JustSid

+0

1. 코드 경로 :이 함수를 호출하는 모든 스레드가로드되고 스레드간에 공유되는 일부 플래그가 대기합니다. 컨트롤 스레드에서 그 플래그를 true로 설정했습니다. 2.이 프로그램은 x86-64 아키텍처로 32 비트로 컴파일됩니다. 3. 그냥 masterDeserialize를 설정하는 것은 원하지만 masterDeserialize == true의 코드 경로에서 단 하나의 스레드 만 찾을 수 있기를 바란다. 따라서 설정만으로는 충분하지 않다. - 이전 값을 테스트하여 각 스레드가 어느 길을 취해야하는지 알기. –

+0

하나의 스레드가 masterDeserialize = true를 사용하면 해당 반복에서 다른 스레드가 그렇게해야 함을 언급하는 것도 가치가 있습니다. (코드 섹션은 일부 비 직렬화를 수행하고 포인터가 null이 아니며 포인터가 null이 아닌 경우 흐름이 아무런 작동도 수행하지 않습니다. 언급 할 가치가있는 또 다른 사항은 디버그 정보가 그 이상한 행동에 의해 전시되는 코드 –

답변

1

이론적으로 두 개의 스레드 만이 "마스터"가 될 수 있습니다. 문제는 완료 후 마스터 경로를 취한 스레드가 masterDeserialize 변수를 다시 0으로 설정하여 CAS에 매우 늦게 도착할 수있는 스레드 (예 : 선점으로 인해)가 다시 "획득"할 수있게하는 것입니다.

수정 사항은 실제로 간단합니다.이 플래그에 세 번째 상태 (예 : 값 2)를 추가하여 "마스터 완료"를 의미하고이 상태를 (초기 상태 0 대신)이 끝에 사용합니다. 작업을 알리는 마스터 경로가 완료됩니다. 따라서 myFunc을 호출하는 스레드 중 하나만 0을 볼 수 있으므로 필요한 보장이 제공됩니다. 플래그를 재사용하려면 명시 적으로 0으로 다시 초기화해야합니다.

+0

고마워 Alexey! 나는 이것을 놓치기에 정말로 어리 석다. –