C ++에 프로덕션 준비 잠금이없는 큐 또는 해시 구현이 있습니까?


80

나는 C ++에서 잠금없는 큐를 위해 꽤 많이 인터넷 검색을 해왔다. 몇 가지 코드와 시험판을 찾았지만 컴파일 할 수있는 것은 없습니다. 잠금없는 해시도 환영합니다.

요약 : 지금까지는 긍정적 인 대답이 없습니다. "프로덕션 준비"라이브러리가 없으며 놀랍게도 기존 라이브러리 중 STL 컨테이너의 API를 준수하는 라이브러리가 없습니다.


4
비주얼 스튜디오 2010에서 잠금 무료 큐를 포함 <concurrent_queue.h>


2
흥미롭게도 "잠금없는"이라는 용어가 반드시 잠금이 없음을 의미하는 것은 아닙니다. 하나의 정의는 en.wikipedia.org/wiki/Non-blocking_algorithm 을 참조하십시오 .
Kristopher Johnson

8
여러 솔루션이 있고 많은 토론을 불러 일으켰으며 많은 찬성표를 얻은 멀티 스레드 프로그래밍에서 일반적이지만 어려운 문제를 해결하는 방법을 묻는 질문이 있습니다. 그런 다음 9 년 후 주제를 벗어난 것으로 종료합니다. StackOverflow에, NathanOliver, 선생님 E_net4 현명한 Downvoter, 장 - 프랑수아 파브르, Machavity 및 gre_gor / s의 기부에 감사드립니다
muusbolla

1
나는 질문을 닫은 사람들이 아마도 그것을 이해하지 못할 것이라고 말할 것입니다.
Derf Skren 2019

답변:


40

1.53부터 boost는 대기열, 스택 및 단일 생산자 / 단일 소비자 대기열 (즉, 링 버퍼)을 포함 하는 잠금없는 데이터 구조 세트를 제공합니다 .


boost :: lockfree :: queue는 POD 유형에서만 작동하며 대부분의 경우 유용하지 않습니다. 좀 더 유연한 구조를 제공하는 방법이 있다면 부스트가 도입했을 것입니다.
rahman

1
@rahman 그 문제는 어디입니까? 다른 것을 전달하려는 경우, 특히 불투명 할 가능성이있는 부작용을 차단하는 개체는 잠금없는 디자인의 전체 목적을 이해하지 못한 것입니다.
Ichthyo는

왜 Boost는 연결 목록을 기반으로 잠금없는 큐와 스택을 제공합니다. 그러나 그들은 잠금없는 연결 목록을 제공하지 않습니까?
Deqing 2016

25

시작점은 단일 생산자와 소비자를 위한 Herb Sutter의 DDJ 기사 또는 여러 기사 중 하나 입니다 . 그가 제공하는 코드 (각 기사의 두 번째 페이지에서 시작하는 인라인)는 C ++ 0x 스타일 atomic <T> 템플릿 유형을 사용합니다. Boost 프로세스 간 라이브러리를 사용하여 모방 할 수 있습니다.

부스트 코드는 프로세스 간 라이브러리의 깊숙한 곳에 숨겨져 있지만 적절한 헤더 파일 (atomic.hpp)을 통해 필자가 익숙한 시스템에서 필요한 비교 및 ​​스왑 작업에 대한 구현을 읽었습니다.


1
Steve, 저는 Boost의 원자 구현에도 관심이 있지만 Interprocess의 세부 사항에있는 것 같고 문서화되지 않았습니다. 어쨌든 사용하기에 안전한가요? 감사!
Kim Gräsman

나는 Herb Sutter의 기사를 아주 잘 알고 있습니다. 출처는 어디에서 찾았습니까? DDJ 나 그의 사이트에 게시되지 않았습니다 (또는 내가 맹인일까요?).
RED SOFT ADAIR

1
코드는 해당 기사의 두 번째 페이지에서 시작하는 인라인입니다.
Steve Gilham

3
여러 명의 개인이나 소비자를 위해 진정으로 잠금이없는 코드를 원한다면 나는 나가야한다. Sutter의 다중 생산자 대기열 예제는 잠금이없는 것이 아닙니다. 생산자를 직렬화하는 잠금과 소비자를 직렬화하는 잠금이 있습니다. 찾을 수 있다면 저도 관심을 가질 것입니다.
Steve Gilham

1
tim.klingt.org/git?p=boost_lockfree.git에 boost :: lockfree 프로젝트가 있습니다 . 당신이 볼 수 있습니다. 그 목표 중 하나는 원자 프리미티브의 비 :: details :: 버전을 제공하는 것입니다.
sstock

17

예!

나는 잠금없는 큐를 썼다 . Features ™가 있습니다.

  • 완전히 대기하지 않음 (CAS 루프 없음)
  • 초고속 (초당 1 억 건 이상의 대기열 삽입 / 대기열 제거 작업)
  • C ++ 11 이동 의미 체계 사용
  • 필요에 따라 성장 (원하는 경우에만)
  • 요소에 대한 잠금없는 메모리 관리를 수행합니다 (사전 할당 된 연속 블록 사용).
  • 독립형 (헤더 2 개, 라이센스 및 추가 정보 1 개)
  • MSVC2010 +, Intel ICC 13 및 GCC 4.7.2에서 컴파일 (그리고 C ++ 11 완전 호환 컴파일러에서 작동해야 함)

그건 GitHub의에서 사용할 수 단순화 된 BSD 라이선스에 따라 (그것을 포크 주시기 바랍니다!).

주의 사항 :

  • 단일 생산자 단일 소비자 아키텍처 (예 : 두 개의 스레드) 에만 해당
  • x86 (-64)에서 철저히 테스트되었으며 정렬 된 기본 크기 정수와 포인터로드 및 저장이 자연스럽게 원자 적이지만 x86이 아닌 CPU에서 필드 테스트를 거치지 않은 ARM, PowerPC 및 기타 CPU에서 작동해야합니다. 테스트 할 사람은 알려주세요)
  • 특허가 침해되었는지 여부는 알 수 없습니다 (자신의 책임하에 사용 등). 내가 처음부터 직접 디자인하고 구현했습니다.

2
매우 좋게 들리지만 실제 멀티 스레딩을 활용하려면 여러 제작자 및 / 또는 여러 소비자가 필요합니다.
RED SOFT ADAIR

2
@RED : 애플리케이션에 따라 다릅니다. 그것은 그래서 모든 내가 필요했다 - 소비자 싱글 프로듀서 / 모든 나는 ;-) 내장
카메론

@Cameron : 좋은 물건! Facebook의 어리석은 ProducerConsumerQueue 에 대해 대기열을 벤치마킹 했습니까 ? 벤치 마크 코드를 사용하여 수행했으며 RWQ와 Dmitry의 SPSC를 모두 능가하는 것으로 보입니다. 3.06GHz Core 2 Duo (T9900)가 설치된 OS X 10.8.3을 사용하고 있으며 -O3와 함께 Clang을 사용하여 코드를 컴파일했습니다. 저는 현재 제 프로젝트 중 하나에 대해 단일 프로듀서 / 단일 소비자 대기열을보고 있고 귀하의 프로젝트를 후보자로 간주했기 때문에이 작업을 수행했습니다. :)
André Neves 2013 년

@ André : 방금 확인했습니다 :-) Facebook의 어리 석음은 빈 대기열에서 대기열을 뺄 때 내 것보다 약간 빠르며 단일 스레드의 비어 있지 않은 대기열에서 대기열을 뺄 때 약간 느립니다. 다른 모든 작업은 거의 정확히 동일한 속도입니다 (VM에서 g ++ -O3 사용). 어리 석음 대기열에 어떤 크기를 사용하고 있습니까? (저는 MAX를 사용했습니다.) Mine과 Dmitry는 모두 필요에 따라 성장하지만 어리석은 것은 고정되어 있습니다. 물론 가장 빠른 인큐 작업은 공간이없고 실패 할 때입니다. 코드를 보면 folly 's는 저와 같은 아이디어를 사용하는 것 같지만 크기 조정 기능이 없습니다.
Cameron

@ André : 아, 내가 잊고있는 한 가지 더-내 벤치 마크 코드를 사용하면 "Raw empty remove"벤치 마크가 가장 많은 반복 작업을 수행합니다 (너무 간단하기 때문에 측정 가능한 결과를 얻는 데 더 많은 시간이 걸립니다). 최종 "평균 작업 / 초"수치에 불균형하게 영향을 미치기 때문입니다. 일반적으로 승수 (및 플랫 타이밍 값)가 더 유용합니다. 어쨌든, 이러한 속도에서 이러한 모든 대기열은 실제로 내 어리석은 합성 벤치 마크보다 더 많은 것을 사용하는 경우 충분히 빠를 것입니다 ;-)
Cameron

15

Facebook의 Folly 는 C ++ 11에 기반한 잠금없는 데이터 구조를 가지고있는 것 같습니다 <atomic>.

현재 프로덕션에서 사용되고 있으므로 다른 프로젝트에서도 안전하게 사용할 수 있다고 감히 말할 수 있습니다.

건배!


MPMC 대기열 도 있습니다 . "선택적으로 차단" 이라고 주장 합니다. 일반 문서의 일부가 아닌 것으로 보이며 사용을 권장하는지 확실하지 않습니다.
Rusty Shackleford

11

그런 라이브러리가 있지만 C에 있습니다.

C ++로 래핑하는 것은 간단해야합니다.

http://www.liblfds.org


10

주어진 답변의 대부분을 확인한 후 다음과 같이 말할 수 있습니다.

대답은 아니오 입니다.

상자에서 바로 사용할 수있는 권리는 없습니다.


4
100 % 맞습니다. comp.programming.threads 뉴스 그룹의 도움으로 동일한 결과를 얻었습니다. 한 가지 이유는 잠금없는 데이터 구조의 영역이 특허 광산 분야이기 때문입니다. 따라서 인텔과 같은 상용 라이브러리조차도이를 피하고 있습니다.
Lothar

이것은 C ++가 아니라 C입니다. 아래로 투표하기 전에 질문을 읽으십시오.
RED SOFT ADAIR 2010 년

사과. 투표가 너무 오래되었다고 느끼기 때문에 내 투표를 취소 할 수 없습니다. SO 개발자는 더 많은 일을해야한다고 생각합니다. 그들은 점점 더 많은 도움이되지 않는 행동을 추가하는 것 같습니다.

3
이 답변이 너무 많은 찬성표를받는 이유. 질문은 쉽게 편집 할 수 있습니다. 또는 이것은 주석에있을 수 있습니다.
사용자


6

내가 아는 가장 가까운 것은 Windows Interlocked Singly Linked Lists 입니다. 물론 Windows 전용입니다.


와우-그게 다인 것 같습니다. 확인하는 데 시간이 좀 필요하지만 (현재는 할 수 없습니다) 다시 연락 드리겠습니다.
RED SOFT ADAIR

Interlocked Singly Linked List는 훌륭한 도구이지만 슬프게도 FIFO가 아닙니다.
ali_bahoo

내가 회상하는 것처럼 적절한 목록이 아닙니다. 임의의 요소를 연결 해제 할 수 없습니다. 할 수있는 유일한 일은 전체 목록을 삭제하는 것입니다. 아마도 그것은 ... 그 이후에 이동 것

5

여러 생산자 / 단일 소비자 대기열 / FIFO가있는 경우 SLIST 또는 간단한 Lock Free LIFO 스택을 사용하여 하나의 LockFree를 쉽게 만들 수 있습니다. 소비자를위한 두 번째 "개인"스택이 있습니다 (단순함을 위해 SLIST 또는 선택한 다른 스택 모델로도 수행 할 수 있음). 소비자는 개인 스택에서 항목을 꺼냅니다. 개인 LIFO가 노출 될 때마다 공유 된 동시 SLIST (전체 SLIST 체인 확보)를 팝하는 대신 Flush를 수행 한 다음 항목을 개인 스택으로 푸시하는 순서대로 Flushed 목록을 살펴 봅니다.

이는 단일 생산자 / 단일 소비자 및 다중 생산자 / 단일 소비자에게 적용됩니다.

그러나 다중 소비자의 경우 (단일 생산자 또는 다중 생산자 모두) 작동하지 않습니다.

또한 해시 테이블에 관한 한 해시를 캐시의 세그먼트 당 잠금이있는 세그먼트로 나누는 "스트라이핑"에 이상적인 후보입니다. 이것이 Java 동시 라이브러리가 수행하는 방법입니다 (32 스트라이프 사용). 경량 리더-라이터 잠금이있는 경우 동시 읽기를 위해 해시 테이블에 동시에 액세스 할 수 있으며 경합 된 스트라이프에서 쓰기가 발생하는 경우에만 중단됩니다 (해시 테이블 확장을 허용하는 경우).

자신의 것을 롤링하는 경우 모든 잠금을 서로 옆에 배열하는 대신 해시 항목으로 잠금을 인터리브하여 잘못된 공유가 발생할 가능성을 줄이십시오.


귀하의 답변에 감사드립니다. C ++에서 "프로덕션 준비"솔루션 / 템플릿을 찾고 있습니다. 나는 내 자신을 굴리고 싶지 않습니다. 그러한 구현을 알고 있습니까?
RED SOFT ADAIR

4

이것에 대해 조금 늦게 올 수 있습니다.

솔루션의 부재 (질문에서)는 주로 C ++ (C ++ 0x / 11 이전)의 중요한 문제 때문입니다. C ++에는 동시 메모리 모델이 없습니다.

이제 std :: atomic을 사용하여 메모리 순서 문제를 제어하고 적절한 비교 및 ​​교체 작업을 수행 할 수 있습니다. 저는 초기 무료 및 ABA 문제를 피하기 위해 C ++ 11 및 Micheal의 위험 포인터 (IEEE TPDS 2004)를 사용하여 Micheal & Scott의 잠금없는 대기열 (PODC96) 구현을 직접 작성했습니다. 잘 작동하지만 빠르고 더러운 구현이며 실제 성능에 만족하지 않습니다. bitbucket에서 코드를 사용할 수 있습니다. LockFreeExperiment

이중 단어 CAS를 사용하여 위험 포인터없이 잠금없는 대기열을 구현할 수도 있습니다 (하지만 64 비트 버전은 cmpxchg16b를 사용하여 x86-64에서만 가능합니다), 여기에 대한 블로그 게시물 (대기열에 대한 테스트되지 않은 코드 포함)이 있습니다. : x86 / x86-64에 대한 일반적인 두 단어 비교 및 ​​교체 구현 (LSE 블로그)

내 벤치 마크는 이중 잠금 대기열 (또한 Micheal & Scott 1996 논문에서도)이 잠금없는 대기열만큼 성능을 ​​발휘한다는 것을 보여줍니다 (잠긴 데이터 구조에 성능 문제가있을만큼 충분한 경합에 도달하지 않았지만 내 벤치는 너무 가볍습니다. 지금) Intel의 TBB의 동시 대기열은 비교적 적은 수 (운영 체제에 따라 지금까지 찾은 가장 낮은 경계인 FreeBSD 9에서 2 배 빠름)가 훨씬 더 좋아 보입니다 (2 배 빠름). 4 ht-core, 따라서 8 개의 논리적 CPU가있는 i7) 스레드가 매우 이상하게 작동합니다 (간단한 벤치 마크의 실행 시간이 몇 초에서 몇 시간으로 이동합니다!)

STL 스타일을 따르는 잠금없는 큐에 대한 또 다른 제한 : 잠금없는 큐에 반복기를 갖는 것은 감각이 없습니다.


3

그리고 인텔 스레딩 빌딩 블록 이 나왔습니다. 그리고 한동안 좋았습니다.

추신 : 당신은 concurrent_queue 및 concurrent_hash_map을 찾고 있습니다.



1
나는 자물쇠가 없다는 엄격한 감각을 알고 있지만 그럼에도 불구하고 자물쇠가없는 것은 구현 세부 사항이므로 OP가 문제를 해결하는 데 도움이 될 것이라고 생각했습니다. 동시 액세스로 잘 작동하는 컬렉션을 찾고 있다고 생각했습니다.
Edouard A.

잠금 장치가없는 것은 단순한 팽창 세부 사항이 아닙니다. 완전히 다른 짐승입니다.
arunmoezhi

1

내가 아는 한, 아직 공개적으로 사용할 수있는 그러한 것은 없습니다. 구현자가 해결해야 할 한 가지 문제는 현재 링크를 찾을 수없는 것 같지만 존재하는 잠금없는 메모리 할당자가 필요하다는 것입니다.


메모리 할당자를 사용할 수있는 이유가 이해가되지 않습니다. 내장 포인터가있는 데이터 구조를 사용하기 만하면됩니다 (컨테이너에 열중하고 간단한 해시 테이블을 구현하는 기술을 잃을 때까지 좋은 옛날 방식을 알고 있습니다).
Lothar

1

다음은 Concurrent lock free Queue http://www.drdobbs.com/parallel/writing-a-generalized-concurrent-queue/211601363?pgno=1 에 대한 Herb Sutter의 기사입니다 . 컴파일러 재정렬과 같은 몇 가지 변경 사항을 적용했습니다. 이 코드를 컴파일하려면 GCC v4.4 이상이 필요합니다.

#include <atomic>
#include <iostream>
using namespace std;

//compile with g++ setting -std=c++0x

#define CACHE_LINE_SIZE 64

template <typename T>
struct LowLockQueue {
private:
    struct Node {
    Node( T* val ) : value(val), next(nullptr) { }
    T* value;
    atomic<Node*> next;
    char pad[CACHE_LINE_SIZE - sizeof(T*)- sizeof(atomic<Node*>)];
    };
    char pad0[CACHE_LINE_SIZE];

// for one consumer at a time
    Node* first;

    char pad1[CACHE_LINE_SIZE
          - sizeof(Node*)];

// shared among consumers
    atomic<bool> consumerLock;

    char pad2[CACHE_LINE_SIZE
          - sizeof(atomic<bool>)];

// for one producer at a time
    Node* last;

    char pad3[CACHE_LINE_SIZE
          - sizeof(Node*)];

// shared among producers
    atomic<bool> producerLock;

    char pad4[CACHE_LINE_SIZE
          - sizeof(atomic<bool>)];

public:
    LowLockQueue() {
    first = last = new Node( nullptr );
    producerLock = consumerLock = false;
    }
    ~LowLockQueue() {
    while( first != nullptr ) {      // release the list
        Node* tmp = first;
        first = tmp->next;
        delete tmp->value;       // no-op if null
        delete tmp;
    }
    }

    void Produce( const T& t ) {
    Node* tmp = new Node( new T(t) );
    asm volatile("" ::: "memory");                            // prevent compiler reordering
    while( producerLock.exchange(true) )
        { }   // acquire exclusivity
    last->next = tmp;         // publish to consumers
    last = tmp;             // swing last forward
    producerLock = false;       // release exclusivity
    }

    bool Consume( T& result ) {
    while( consumerLock.exchange(true) )
        { }    // acquire exclusivity
    Node* theFirst = first;
    Node* theNext = first-> next;
    if( theNext != nullptr ) {   // if queue is nonempty
        T* val = theNext->value;    // take it out
        asm volatile("" ::: "memory");                            // prevent compiler reordering
        theNext->value = nullptr;  // of the Node
        first = theNext;          // swing first forward
        consumerLock = false;             // release exclusivity
        result = *val;    // now copy it back
        delete val;       // clean up the value
        delete theFirst;      // and the old dummy
        return true;      // and report success
    }
    consumerLock = false;   // release exclusivity
    return false;                  // report queue was empty
    }
};

int main(int argc, char* argv[])
{
    //Instead of this Mambo Jambo one can use pthreads in Linux to test comprehensively
LowLockQueue<int> Q;
Q.Produce(2);
Q.Produce(6);

int a;
Q.Consume(a);
cout<< a << endl;
Q.Consume(a);
cout<< a << endl;

return 0;
}

4
이것은 잠금 장치가 없습니다. 물론 OS에서 제공하는 잠금을 사용하지 않지만 "atomic <bool> consumerLock"이 회전하는 방식은 확실히 잠금 동작입니다. 스레드가 이러한 잠금 중 하나를 보유하는 동안 충돌하면 더 이상 작업을 수행 할 수 없습니다. Herb 자신도 그렇게 말합니다 (그 기사의 4 페이지에서 생각합니다).
James Caccese


0

저는 아마도 2010 년에이 글을 썼습니다. 다른 참고 문헌의 도움으로 확신합니다. 다중 생산자 단일 소비자.

template <typename T>
class MPSCLockFreeQueue 
{
private:
    struct Node 
    {
        Node( T val ) : value(val), next(NULL) { }
        T value;
        Node* next;
    };
    Node * Head;               
    __declspec(align(4)) Node * InsertionPoint;  //__declspec(align(4)) forces 32bit alignment this must be changed for 64bit when appropriate.

public:
    MPSCLockFreeQueue() 
    {
        InsertionPoint = new Node( T() );
        Head = InsertionPoint;
    }
    ~MPSCLockFreeQueue() 
    {
        // release the list
        T result;
        while( Consume(result) ) 
        {   
            //The list should be cleaned up before the destructor is called as there is no way to know whether or not to delete the value.
            //So we just do our best.
        }
    }

    void Produce( const T& t ) 
    {
        Node * node = new Node(t);
        Node * oldInsertionPoint = (Node *) InterLockedxChange((volatile void **)&InsertionPoint,node);
        oldInsertionPoint->next = node;
    }

    bool Consume( T& result ) 
    {
        if (Head->next)
        {
            Node * oldHead = Head;
            Head = Head->next;
            delete oldHead;
            result = Head->value;
            return true;
        }       
        return false;               // else report empty
    }

};
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.