2014-11-26 3 views
1

내 로거 용 SPSC 큐가 아래에 있습니다.어 그리 게이션이없는 SPSC 잠금 사용 가능 큐

확실히 일반용 SPSC 잠금 해제 대기열이 아닙니다.

그러나 사용 방법, 대상 아키텍처 등에 대한 많은 가정과 아래에 자세히 설명되어있는 몇 가지 허용되는 절충 사항을 고려하면 내 질문은 기본적으로 안전합니까/작동합니까? 그것은 단지 x86_64 아키텍처에 사용됩니다

  • , 그래서 원자 될 것 uint16_t에 기록합니다.
  • 생산자 만 tail을 업데이트합니다.
  • 소비자 만 head을 업데이트합니다.
  • 생산자가 head의 이전 값을 읽는 경우 실제보다 큐에 공간이 적게있는 것처럼 보입니다. 이는 사용되는 컨텍스트에서 허용되는 제한입니다.
  • 소비자가 이전 값 tail을 읽는다면, 실제보다 대기열에서 대기중인 데이터가 적어 수용 가능한 한계가있는 것처럼 보입니다.

    • 소비자가 즉시 최신 tail을 얻을 수 있지만, 결국 최신 tail이 도착, 데이터가 기록됩니다 대기 : 때문에

    제한은 위의 허용됩니다.

  • 생산자가 최신 head을 즉시받지 못해서 대기열이 실제보다 더 가득 차게 보일 것입니다. 로드 테스트에서 우리가 로그의 양과 큐의 크기, 로거가 큐를 소모하는 속도를 발견 했으므로이 제한은 효과가 없습니다. 큐에 항상 공간이 있습니다.

마지막으로를 사용하면 각 스레드가 읽는 변수가 최적화되지 않도록 방지 할 수 있습니다.

내 질문 :

  • 이 논리가 맞습니까?
  • 대기열 스레드가 안전합니까?
  • volatile이면 충분합니까?
  • volatile이 필요합니까?

내 큐 :

class LogBuffer 
{ 
public: 
    bool is_empty() const { return head_ == tail_; } 
    bool is_full() const { return uint16_t(tail_ + 1) == head_; } 
    LogLine& head()  { return log_buffer_[head_]; } 
    LogLine& tail()  { return log_buffer_[tail_]; } 
    void advance_head() { ++head_; } 
    void advance_hail() { ++tail_; } 
private: 
    volatile uint16_t tail_ = 0;  // write position 
    LogLine log_buffer_[0xffff + 1]; // relies on the uint16_t overflowing 
    volatile uint16_t head_ = 0;  // read position 
}; 
+0

상식 적용 :이 방법으로 많은 lockfree 라이브러리가 SPSC 대기열을 구현하지 못하는 이유는 무엇입니까? – sehe

+0

@ 열거 된 트레이드 오프 때문에 @sehe - 어 그리 키스를 사용하면 소비자의 읽기가 보장됩니다. 제작자의 최신 글을 볼 수 있습니다. 라이브러리 작성시 최신 쓰기가 표시되지 않을 수도 있습니다. –

답변

1

이 논리가 맞습니까?

예.

대기열 스레드가 안전합니까?

번호

충분한 휘발성인가? 휘발성이 필요합니까?

아니요, 둘 다. 휘발성은 임의의 가변 쓰레드 세이프를 만드는 마술 키워드가 아닙니다. 항목을 생성하거나 소비 할 때 메모리 정렬이 올바른지 확인하려면 인덱스에 원자 변수 또는 메모리 장벽을 사용해야합니다.

큐의 항목을 생성하거나 소비 한 후에는 다른 스레드에서 변경 사항을 확인할 수 있도록 메모리 장벽을 설정해야합니다. 원자 라이브러리를 갱신 할 때 많은 원자 라이브러리가이를 수행합니다.

제쳐두고, "is_empty"대신 "was_empty"를 사용하여 그것이하는 일을 분명히합니다. 이 호출의 결과는 시간에 따라 하나의 인스턴스가되며, 그 값에 따라 행동 할 때까지 변경되었을 수 있습니다.

+1

필자가 설명한 사용법에서 나열한 절충 사항을 감안할 때 왜 안전하지 않은 것입니까? 'head'와'tail'만이 전진하기 때문에'head'는 소비자가 쓰고'tail'은 제작자가 쓴 것입니다. 'tail'에 쓰기를하기 전에'tail'에 쓰기를하면 큐가 실제보다 더 작아 보이기 만합니다 - 로거는 현재 대기중인 것보다 적게 기록하지만'tail '에 대한 쓰기는 결국 소비자가 본. 다른 순서로 재주문하면 생산자가 실제로 사용 가능한 것보다 적은 공간으로 큐를 보게됩니다. 다시 받아 들일 수 있습니다. –

+0

@SteveLorimer Atomics는 단순히 값이 "부실"한 것 이상을 고려합니다. 또한 부분적으로 업데이트 된 변수를 읽는 스레드와 같은 다른 문제를 방지합니다. 목표 아키텍처 및 런타임 정렬 요구 사항에 따라 "잘못된 인덱스"를 읽고 프로그램을 정의되지 않은 상태로 둘 수 있습니다. – BlamKiwi

+1

자, 충분하지만,'x86_64'에'uint16_t'에 대한 쓰기는 원자적일 것입니다 - 그래서 모든 것을 주어진다면 여전히 안전하지 않습니까? –