5

여유 시간에 동시성을 가지고 놀고, 판독기 쪽에서 잠금을 사용하지 않고 찢어진 읽기를 시도하여 동시 판독기가 서로 간섭하지 않도록하려고했습니다.C#/CLR : MemoryBarrier 및 찢어진 읽기

아이디어는 잠금을 통해 쓰기를 직렬화하지만 읽기 측면에서는 메모리 배리어만을 사용하는 것입니다. 여기에 내가 해낸 방법 캡슐화 재사용 가능한 추상화입니다 :

public struct Sync<T> 
    where T : struct 
{ 
    object write; 
    T value; 
    int version; // incremented with each write 

    public static Sync<T> Create() 
    { 
     return new Sync<T> { write = new object() }; 
    } 

    public T Read() 
    { 
     // if version after read == version before read, no concurrent write 
     T x; 
     int old; 
     do 
     { 
      // loop until version number is even = no write in progress 
      do 
      { 
       old = version; 
       if (0 == (old & 0x01)) break; 
       Thread.MemoryBarrier(); 
      } while (true); 
      x = value; 
      // barrier ensures read of 'version' avoids cached value 
      Thread.MemoryBarrier(); 
     } while (version != old); 
     return x; 
    } 

    public void Write(T value) 
    { 
     // locks are full barriers 
     lock (write) 
     { 
      ++version;    // ++version odd: write in progress 
      this.value = value; 
      // ensure writes complete before last increment 
      Thread.MemoryBarrier(); 
      ++version;    // ++version even: write complete 
     } 
    } 
} 

이 버전 변수에 오버 플로우에 대해 걱정하지 마십시오을, 나는 다른 방법 피하기. 그래서 내 이해와 Thread.MemoryBarrier의 응용 위의 올바른 무엇입니까? 장벽이 불필요한가요?

+0

귀하의 의견과 코드 (정보/버전)가 맞지 않습니다. –

+0

감사합니다. 수정 사항 게시! – naasking

답변

3

코드를 자세히 살펴 봤는데 올바르게 생각됩니다. 즉시 저에게 뛰어 들었던 한 가지는 낮은 잠금 작업을 수행하기 위해 확립 된 패턴을 사용했기 때문입니다. 가상 잠금 장치의 일종으로 version을 사용하고있는 것을 볼 수 있습니다. 짝수 번호가 해제되고 홀수 번호가 획득됩니다. 가상 잠금에 대해 단조롭게 증가하는 값을 사용하고 있으므로 ABA problem도 피할 수 있습니다. 그러나 가장 중요한 것은 가상 잠금 값이 이전에 전에 읽기가 시작될 때까지 계속 읽기를 시도하는 동안 반복을 계속한다는 것입니다.이 완료된 후 과 비교하여 읽기가 시작됩니다. 그렇지 않으면 실패한 읽기로 간주하고 다시 시도하십시오. 그래, 핵심 논리에서 잘 끝났어.

그렇다면 메모리 장벽 제너레이터의 배치는 어떻습니까? 음,이 모든 것이 꽤 잘 어울립니다. Thread.MemoryBarrier 전화는 모두 필요합니다. 만약 니트 픽을 선택했다면, Write 메쏘드에서 하나 더 필요하다는 것을 말할 것입니다.

public void Write(T value) 
{ 
    // locks are full barriers 
    lock (write) 
    { 
     ++version;    // ++version odd: write in progress 
     Thread.MemoryBarrier(); 
     this.value = value; 
     Thread.MemoryBarrier(); 
     ++version;    // ++version even: write complete 
    } 
} 

추가 된 호출은 여기 ++versionthis.value = value이 교환되지 않도록합니다. 이제, ECMA 사양은 기술적으로 그러한 종류의 명령 재정렬을 허용합니다. 그러나 Microsoft의 CLI 및 x86 하드웨어 구현에는 이미 쓰기에 대한 휘발성 의미가 있으므로 대부분의 경우 실제로 필요하지는 않습니다. 그러나 누가 알겠는가? 아마도 ARM CPU를 타겟으로하는 Mono 런타임에서 필요할 것이다.

Read 쪽에서는 오류가 발견되지 않습니다. 사실, 당신이 가지고있는 전화의 위치는 내가 놓았던 곳입니다. 어떤 사람들은 version의 초기 읽기 전에 왜 필요 없는지 궁금해 할 것입니다. 이유는 외부 루프가 Thread.MemoryBarrier이 더 이상 실행되지 않아 첫 번째 읽기가 캐시되었을 때 대/소문자를 catch하기 때문입니다.

그래서 성능에 대한 토론을하게됩니다. 이 방법은 Read 방법으로 하드 록을 사용하는 것보다 실제로 빠릅니까? 글쎄, 나는 당신의 코드에 대해 꽤 광범위한 테스트를 해왔다. 대답은 확실한 예입니다! 하드 록을 사용하는 것보다 훨씬 빠릅니다. 나는 그것이 128 비트이기 때문에 값 유형으로 Guid을 사용하여 테스트했는데 내 컴퓨터의 기본 단어 크기 (64 비트)보다 큽니다. 나는 또한 작가와 독자의 수에 대해 몇 가지 다른 변형을 사용했다. 귀하의 낮은 잠금 기술은 하드 잠금 기술보다 지속적으로 그리고 현저히 뛰어납니다. 나는 약간의 변이를 시도하여 Interlocked.CompareExchange을 사용하여 읽기를 보호했고 모든 속도가 느렸다. 사실, 어떤 경우에는 하드 록을 사용하는 것보다 실제로 느립니다. 나는 정직해야한다. 나는 이것에 전혀 놀랐다.

나는 또한 꽤 중요한 타당성 테스트를 수행했습니다. 꽤 오랫동안 실행될 테스트를 만들었지 만 찢어진 읽기가 한번도 없었습니다. 그리고 나서 제어 테스트로 Read 메서드를 수정하여 잘못된 것으로 알고 테스트를 다시 실행했습니다. 이번에는 예상대로 찢어진 읽기가 무작위로 나타납니다. 나는 코드를 다시 당신이 가지고있는 코드로 바 꾸었습니다. 다시 예상대로. 이것은 내가 이미 예상 한 것을 확인하는 것처럼 보였다. 즉, 코드가 올바르게 표시됩니다. 나는 다양한 런타임 및 하드웨어 환경을 테스트 할 필요가 없으며 (시간도 없음), 승인을 100 % 할 수는 없지만, 구현에 두 가지 큰 이점을 줄 수 있다고 생각합니다. 지금 당장.

마지막으로 말했듯이, 나는 여전히 이것을 생산에 넣는 것을 피할 것입니다. 그래, 맞을지도 모르지만 코드를 유지해야하는 다음 사람은 아마 그것을 이해하지 못할 것이다. 누군가는 변경 사항의 결과를 이해하지 못하기 때문에 코드를 변경하고 중단 할 수 있습니다. 이 코드는 꽤 부서지기 쉽다는 것을 인정해야합니다. 사소한 변화조차도 그것을 깨뜨릴 수 있습니다.

+0

자세한 검토 주셔서 감사합니다. 실제로 내 블로그에이 글을 게시했습니다 : http://higherlogics.blogspot.ca/2013/09/clr-concurrency-preventing-torn-reads.html, 그리고이 기능들과 이들을 기반으로 구축 된 다른 것들은 내 공개 소스 인 Sasa에 있습니다. library : https://sourceforge.net/p/sasa/code/ci/default/tree/Sasa/Atomics.cs#l262. 최신 버전은 명확성을 위해 VolatileRead/VolatileWrite를 사용하도록 전환되었습니다. 실제로 이들을 사용하여로드 연결/저장 조건부 프리미티브를 구현할 수도 있습니다. – naasking

+0

@nasking : LL/SC 조작을 작성하여 블로그에 게시 할 수 있는지 확인하십시오. 네가 무엇을 생각해 내는지보고 싶다. 시간이되면 직접해볼 수도 있습니다. –

+0

LL/SC는 이미 위에 링크 된 코드에 있습니다 (페이지 하단). API를 단순화하기 위해 코드 일부를 재사용 가능한 구조체에 포함 시켰습니다. https://sourceforge.net/p/sasa/code/ci/default/tree/Sasa.Concurrency/LLSC.cs – naasking