Có hàng đợi không có khóa sẵn sàng sản xuất hoặc triển khai băm trong C ++ không [đóng]


79

Tôi đã tìm kiếm trên Google một chút cho một hàng đợi không có khóa trong C ++. Tôi đã tìm thấy một số mã và một số thử nghiệm - nhưng không có gì mà tôi có thể biên dịch. Một hàm băm không có khóa cũng sẽ được hoan nghênh.

TÓM TẮT: Cho đến nay tôi không có câu trả lời tích cực nào. Không có thư viện "sẵn sàng sản xuất" và đáng ngạc nhiên là không có thư viện hiện có nào tuân thủ API của vùng chứa STL.


4
Visual Studio 2010 có chứa một khóa đợi miễn phí trong <concurrent_queue.h>
Rick

1
Và có một hash_map và unardered_map
Rick

2
Lưu ý rằng, thật kỳ lạ, thuật ngữ "không có khóa" không nhất thiết có nghĩa là không có khóa. Xem en.wikipedia.org/wiki/Non-blocking_algorithm để biết một định nghĩa.
Kristopher Johnson

7
Ồ, một câu hỏi hỏi làm thế nào để giải quyết một vấn đề phổ biến nhưng khó khăn trong lập trình đa luồng có nhiều giải pháp, tạo ra rất nhiều cuộc thảo luận và kiếm được hàng tấn ủng hộ ... Sau đó 9 năm sau, bạn kết thúc nó như lạc đề. Cám ơn sự đóng góp của bạn để StackOverflow, NathanOliver, Sir E_net4 the Wise Downvoter, Jean-François Fabre, Machavity, và gre_gor / s
muusbolla

1
Tôi sẽ nói rằng những người đóng câu hỏi có lẽ không hiểu nó.
Derf Skren

Câu trả lời:


40

Kể từ 1.53, boost cung cấp một tập hợp các cấu trúc dữ liệu miễn phí có khóa , bao gồm hàng đợi, ngăn xếp và hàng đợi đơn sản xuất / người tiêu dùng đơn (tức là bộ đệm vòng).


boost :: lockfree :: queue chỉ hoạt động với các loại POD và hầu hết các trường hợp nó không hoàn toàn hữu ích. Tôi chắc chắn nếu có một cách nào đó để cung cấp một cấu trúc linh hoạt hơn, thì boost sẽ giới thiệu nó.
rahman

1
@rahman vấn đề với điều đó là ở đâu? Nếu bạn muốn chuyển bất kỳ thứ gì khác, đặc biệt là các vật thể có tác dụng phụ ngăn chặn mờ đục, bạn chưa hiểu toàn bộ mục đích của thiết kế không khóa.
Ichthyo

Tại sao boost cung cấp hàng đợi và ngăn xếp không có khóa, cả hai đều dựa trên danh sách được liên kết. Nhưng họ không cung cấp danh sách liên kết không khóa?
Deqing

25

Điểm khởi đầu sẽ là một trong các bài báo DDJ của Herb Sutter cho một nhà sản xuất và người tiêu dùng hoặc nhiều bài báo . Mã mà anh ta đưa ra (trong dòng bắt đầu từ trang thứ hai của mỗi bài viết) sử dụng kiểu mẫu C ++ 0x kiểu nguyên tử <T>; mà bạn có thể bắt chước bằng cách sử dụng thư viện liên quy trình Boost.

Mã tăng cường được chôn sâu trong thư viện liên quy trình, nhưng sau khi đọc qua tệp tiêu đề thích hợp (atom.hpp), các triển khai cho các hoạt động so sánh và hoán đổi cần thiết trên các hệ thống mà tôi đã quen thuộc.


1
Steve, tôi cũng quan tâm đến việc triển khai nguyên tử của Boost, nhưng chúng dường như nằm trong chi tiết của Interprocess / và không được ghi lại. Chúng có an toàn để sử dụng không? Cảm ơn!
Kim Gräsman

Tôi biết rất rõ các bài báo của Herb Sutter - bạn đã tìm nguồn ở đâu? Chúng không được xuất bản bởi DDJ cũng như trên trang web của anh ấy (hoặc có thể tôi bị mù?).
QUẢNG CÁO MỀM ĐỎ,

1
Mã nằm trong dòng trong các bài viết đó bắt đầu từ các trang thứ hai tương ứng của chúng.
Steve Gilham

3
Nếu bạn muốn mã thực sự không bị khóa, cho nhiều đầu dò hoặc người tiêu dùng, thì tôi không có. Ví dụ về hàng đợi nhiều nhà sản xuất của Sutter không khóa miễn phí - có một khóa để tuần tự hóa các nhà sản xuất và một khóa để tuần tự hóa người tiêu dùng. Nếu bạn có thể tìm thấy một cái, tôi cũng muốn quan tâm đến điều đó.
Steve Gilham,

1
Có một dự án tăng cường :: lockfree tại tim.klingt.org/git?p=boost_lockfree.git ; mà bạn có thể nhìn vào. Một trong những mục tiêu của nó là cung cấp phiên bản không :: chi tiết :: của nguyên thủy nguyên tử.
sstock, 31-07-09

17

Đúng!

Tôi đã viết một hàng đợi không có khóa . Nó có các Tính năng ™:

  • Hoàn toàn không chờ đợi (không có vòng lặp CAS)
  • Siêu nhanh (hơn một trăm triệu thao tác enqueue / dequeue mỗi giây)
  • Sử dụng ngữ nghĩa di chuyển C ++ 11
  • Phát triển khi cần thiết (nhưng chỉ khi bạn muốn)
  • Quản lý bộ nhớ không khóa cho các phần tử (sử dụng các khối liền kề được cấp phát trước)
  • Độc lập (hai tiêu đề cộng với giấy phép và readme)
  • Biên dịch theo MSVC2010 +, Intel ICC 13 và GCC 4.7.2 (và phải hoạt động trong bất kỳ trình biên dịch hoàn toàn tuân thủ C ++ 11 nào)

có sẵn trên GitHub theo giấy phép BSD đơn giản hóa (vui lòng tách nó!).

Lưu ý:

  • Chỉ dành cho kiến ​​trúc một người tiêu dùng đơn sản xuất (tức là hai luồng)
  • Đã được kiểm tra kỹ lưỡng trên x86 (-64) và sẽ hoạt động trên ARM, PowerPC và các CPU khác trong đó các số nguyên có kích thước gốc được căn chỉnh và tải và lưu trữ con trỏ là nguyên tử tự nhiên, nhưng chưa được kiểm tra thực địa trên các CPU không phải x86 (nếu ai đó có một để kiểm tra nó cho tôi biết)
  • Không có ý kiến ​​nếu bất kỳ bằng sáng chế nào bị vi phạm (tự chịu rủi ro khi sử dụng, v.v.). Lưu ý rằng tôi đã tự thiết kế và thực hiện nó từ đầu.

2
Nghe có vẻ rất tốt nhưng cần có nhiều nhà sản xuất và / hoặc nhiều người tiêu dùng để tận dụng đa luồng thực sự.
RED SOFT ADAIR

2
@RED: Tùy thuộc vào ứng dụng. Đơn sản xuất / -consumer là tất cả những gì tôi cần, vì vậy đó là tất cả những gì tôi đã xây dựng ;-)
Cameron

@Cameron: Thứ tuyệt vời! Bạn đã đánh giá hàng đợi của mình so với ProducerConsumerQueue điên rồ của Facebook chưa? Tôi đã làm điều đó bằng cách sử dụng mã điểm chuẩn của bạn và nó dường như vượt trội hơn đáng kể so với cả RWQ và SPSC của Dmitry. Tôi đang sử dụng OS X 10.8.3 với Core 2 Duo 3.06 GHz (T9900) và đã biên dịch mã bằng cách sử dụng Clang với -O3. Tôi đã làm điều này bởi vì tôi hiện đang nhìn vào một hàng đợi đơn sản xuất / đơn tiêu dùng cho một trong những dự án của tôi và tôi coi bạn là một ứng cử viên :)
André Neves

@ André: Tôi vừa mới kiểm tra bây giờ :-) Cái dở hơi của Facebook nhanh hơn của tôi một chút khi xếp hàng từ một hàng trống và chậm hơn một chút khi xếp hàng từ hàng không trống trên một chuỗi. Tất cả các hoạt động khác gần như chính xác cùng một tốc độ (điều này là với g ++ -O3 trên máy ảo). Bạn đang sử dụng kích thước nào cho hàng đợi điên rồ? (Tôi đã sử dụng MAX.) Mine và Dmitry đều phát triển khi cần thiết, trong khi lỗi điên rồ đã được sửa - và tất nhiên, hoạt động xếp hàng nhanh nhất là khi không còn chỗ và nó chỉ đơn giản là thất bại. Nhìn vào mã, có vẻ như folly đang sử dụng các ý tưởng giống như của tôi, nhưng không có khả năng thay đổi kích thước.
Cameron

@ André: Ồ, một điều nữa mà tôi quên đề cập - với mã điểm chuẩn của tôi, điểm chuẩn "Xóa rỗng thô" thực hiện nhiều lần lặp nhất (vì nó quá đơn giản, nên cần nhiều hơn nữa để có được kết quả có thể đo lường), có xu hướng ảnh hưởng không tương xứng đến số "hoạt động trung bình / s" cuối cùng. Các số nhân (và các giá trị thời gian cố định) thường hữu ích hơn. Dù sao, ở những tốc độ, tất cả các hàng đợi sẽ được khá đủ nhanh nếu họ đang thực sự được sử dụng cho một cái gì đó meatier hơn tiêu chuẩn tổng hợp ngớ ngẩn của tôi ;-)
Cameron

15

Folly của Facebook dường như đã khóa các cấu trúc dữ liệu miễn phí dựa trên C ++ 11 <atomic>:

Tôi dám nói rằng chúng hiện đang được sử dụng trong sản xuất, vì vậy tôi đoán chúng có thể được sử dụng một cách an toàn trong các dự án khác.

Chúc mừng!


Họ cũng có một hàng đợi MPMC. mà họ tuyên bố là "tùy chọn chặn." Nó dường như không phải là một phần của tài liệu thông thường của họ, không chắc liệu nó có được khuyên dùng hay không.
Rusty Shackleford

11

Có một thư viện như vậy, nhưng nó ở C.

Việc kết hợp với C ++ phải đơn giản.

http://www.liblfds.org


10

Sau khi đã kiểm tra hầu hết các câu trả lời đã cho, tôi chỉ có thể nói:

Câu trả lời là KHÔNG .

Không có thứ quyền nào có thể được sử dụng ngay lập tức.


4
Đúng 100%. Tôi đã đến cùng một kết quả với sự trợ giúp từ nhóm tin comp.programming.threads. Một lý do là khu vực của các cấu trúc dữ liệu miễn phí khóa là một mỏ khai thác bằng sáng chế. Vì vậy, ngay cả những công ty thương mại như Intels cũng đang tránh nó.
Lothar

Đây là C, không phải C ++. Vui lòng đọc câu hỏi trước khi bỏ phiếu xuống.
QUẢNG CÁO MỀM ĐỎ,

Xin lỗi. Tôi lưu ý rằng SO sẽ không cho phép tôi hủy bỏ phiếu bầu của mình vì có cảm giác phiếu bầu đã quá cũ. Tôi nghĩ rằng các nhà phát triển SO cần phải làm nhiều hơn - họ dường như đang tăng thêm số lượng ngày càng nhiều các hành vi không có ích.

3
Tại sao câu trả lời này nhận được nhiều ủng hộ đến vậy. Câu hỏi có thể được chỉnh sửa dễ dàng. Hoặc điều này có thể nằm trong một bình luận.
người dùng


6

Điều gần nhất mà tôi biết là Windows Interlocked Singly Linked Lists . Tất nhiên, nó chỉ dành cho Windows.


Wow - có vẻ như vậy. Tôi sẽ cần một thời gian để kiểm tra nó (hiện tại tôi không thể làm được) nhưng tôi sẽ quay lại với bạn.
RED SOFT ADAIR

Interlocked Singly Linked List là một công cụ tuyệt vời nhưng đáng buồn là nó không phải là FIFO.
ali_bahoo

Đó không phải là một danh sách thích hợp, như tôi nhớ lại. Bạn không thể hủy liên kết các phần tử tùy ý; điều duy nhất bạn có thể làm là xóa toàn bộ danh sách. Có lẽ nó đang di chuyển trên kể từ đó ...

5

Nếu bạn có Hàng đợi nhiều nhà sản xuất / một người tiêu dùng / FIFO, bạn có thể dễ dàng tạo một LockFree bằng cách sử dụng SLIST hoặc một ngăn xếp Lock Free LIFO tầm thường. Những gì bạn làm là có một ngăn xếp "riêng tư" thứ hai cho người tiêu dùng (cũng có thể được thực hiện dưới dạng SLIST cho đơn giản hoặc bất kỳ mô hình ngăn xếp nào khác mà bạn chọn). Người tiêu dùng bật các mục ra khỏi ngăn xếp riêng tư. Bất cứ khi nào LIFO riêng tư được hết, bạn thực hiện Xả thay vì Tắt SLIST đồng thời được chia sẻ (lấy toàn bộ chuỗi SLIST) và sau đó di chuyển danh sách Xả theo thứ tự đẩy các mục vào ngăn xếp riêng.

Điều đó hoạt động cho một nhà sản xuất / một người tiêu dùng và cho nhiều nhà sản xuất / một người tiêu dùng.

Tuy nhiên, nó không hoạt động đối với các trường hợp nhiều người tiêu dùng (với một nhà sản xuất đơn lẻ hoặc nhiều nhà sản xuất).

Ngoài ra, theo như các bảng băm, chúng là một ứng cử viên lý tưởng cho việc "phân chia" tức là chỉ chia băm thành các phân đoạn có khóa trên mỗi phân đoạn của bộ nhớ cache. Đây là cách mà thư viện đồng thời Java thực hiện (sử dụng 32 sọc). Nếu bạn có khóa người đọc-ghi trọng lượng nhẹ, bảng băm có thể được truy cập đồng thời để đọc đồng thời và bạn sẽ chỉ dừng lại khi quá trình ghi xảy ra trên các sọc cạnh tranh (và có thể nếu bạn cho phép phát triển bảng băm).

Nếu bạn tự cuộn, hãy đảm bảo xen kẽ các ổ khóa của mình với các mục nhập băm thay vì đặt tất cả các ổ khóa của bạn trong một mảng cạnh nhau để bạn ít có khả năng bị chia sẻ sai.


Cảm ơn câu trả lời của bạn. Tôi đang tìm giải pháp / mẫu "sẵn sàng sản xuất" trong C ++. Tôi không muốn cuộn của riêng tôi. Bạn có biết cách thực hiện như vậy không?
RED SOFT ADAIR

4

Tôi có thể đến hơi muộn về việc này.

Việc không có giải pháp (ở câu hỏi đã được hỏi) chủ yếu là do một vấn đề quan trọng trong C ++ (trước C ++ 0x / 11): C ++ có (có) không có mô hình bộ nhớ đồng thời.

Bây giờ, sử dụng std :: atom, bạn có thể kiểm soát các vấn đề về thứ tự bộ nhớ và có các hoạt động so sánh và hoán đổi thích hợp. Tôi đã tự viết bản triển khai hàng đợi không có khóa của Micheal & Scott (PODC96) bằng cách sử dụng C ++ 11 và Con trỏ nguy hiểm của Micheal (IEEE TPDS 2004) để tránh các vấn đề về ABA và miễn phí sớm. Nó hoạt động tốt nhưng nó thực hiện nhanh chóng và bẩn thỉu và tôi không hài lòng với màn trình diễn thực tế. Mã có sẵn trên bitbucket: LockFreeExperiment

Cũng có thể triển khai hàng đợi không có khóa mà không có con trỏ nguy hiểm bằng cách sử dụng các từ kép CAS (nhưng phiên bản 64bit sẽ chỉ khả dụng trên x86-64 sử dụng cmpxchg16b), tôi đã đăng một bài blog về điều đó (với mã chưa được kiểm tra cho hàng đợi) ở đây : Thực hiện so sánh và hoán đổi từ kép chung cho x86 / x86-64 (blog LSE.)

Điểm chuẩn của riêng tôi cho tôi thấy rằng hàng đợi có khóa kép (cũng trong bài báo của Micheal & Scott 1996) hoạt động tốt như hàng không có khóa (tôi chưa đạt đủ ý kiến ​​để cấu trúc dữ liệu bị khóa có vấn đề về hiệu suất, nhưng băng ghế dự bị của tôi quá nhẹ cho bây giờ) và hàng đợi đồng thời từ TBB của Intel dường như thậm chí còn tốt hơn (nhanh hơn hai lần) với một số lượng tương đối nhỏ (tùy thuộc vào hệ điều hành, trong FreeBSD 9, giới hạn thấp nhất mà tôi tìm thấy cho đến nay, con số này là 8 luồng trên một i7 với 4 ht-core, và do đó là 8 CPU logic) của các luồng và có hành vi rất lạ (thời gian thực thi điểm chuẩn đơn giản của tôi chuyển từ giây sang giờ!)

Một hạn chế khác về hàng đợi không khóa theo kiểu STL: việc có trình vòng lặp trên hàng đợi không khóa không có cảm giác.


3

Và sau đó Intel Threading Building Blocks ra đời. Và trong một thời gian, nó đã được tốt.

Tái bút: bạn đang tìm kiếm concurrent_queue và concurrent_hash_map



1
Tôi biết, đối với cảm giác nghiêm ngặt của không có khóa, nhưng tôi vẫn nghĩ rằng nó có thể giúp OP giải quyết vấn đề của anh ta, vì thứ không khóa chỉ là một chi tiết triển khai. Tôi nghĩ anh ấy đang tìm kiếm các bộ sưu tập hoạt động tốt với quyền truy cập đồng thời.
Edouard A.

Điều không có khóa không chỉ là một chi tiết bổ sung. Nó hoàn toàn là một con thú khác.
arunmoezhi

1

Theo sự hiểu biết tốt nhất của tôi, vẫn chưa có thứ gì như vậy được công bố rộng rãi. Một vấn đề mà người triển khai cần giải quyết là bạn cần một trình cấp phát bộ nhớ không có khóa, tồn tại, mặc dù tôi dường như không thể tìm thấy liên kết ngay bây giờ.


Tôi không hiểu tại sao lại có công cụ cấp phát bộ nhớ. Chỉ cần sử dụng cấu trúc dữ liệu với con trỏ nội tại (bạn biết cách làm cũ cho đến khi phát điên với các thùng chứa và mất kỹ năng thậm chí triển khai Hashtables đơn giản).
Lothar

1

Sau đây là từ bài viết của Herb Sutter về Hàng đợi không khóa đồng thời http://www.drdobbs.com/parallel/writing-a-generalized-concurrent-queue/211601363?pgno=1 . Tôi đã thực hiện một số thay đổi như sắp xếp lại thứ tự của trình biên dịch. Người ta cần GCC v4.4 + để biên dịch mã này.

#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
Đây không phải là khóa miễn phí. Chắc chắn nó không sử dụng khóa do hệ điều hành cung cấp, nhưng cách nó quay trên (ví dụ) "Atom <bool> ConsumerLock" chắc chắn là hành vi khóa. Nếu một luồng bị treo trong khi nó giữ một trong những ổ khóa đó, thì không thể hoàn thành thêm công việc nào nữa. Ngay cả bản thân Herb cũng nói như vậy (tôi nghĩ ở trang 4 của bài báo đó).
James Caccese


0

Tôi đã viết điều này vào một thời điểm nào đó có thể là vào năm 2010, tôi chắc chắn với sự trợ giúp từ các tài liệu tham khảo khác nhau. Nó đa sản xuất một người tiêu dùng.

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
    }

};
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.