2009-04-26 43 views
14

Tôi mới dùng C++ và đang viết một ứng dụng đa luồng, nhờ đó các nhà văn khác nhau sẽ đẩy các đối tượng vào ngăn xếp và người đọc kéo chúng ra khỏi ngăn xếp (hoặc ít nhất là đẩy con trỏ đến một đối tượng) ..Ngăn xếp C++ an toàn Chủ đề

Có bất kỳ cấu trúc nào được tích hợp trong C++ có thể xử lý điều này mà không cần thêm mã khóa, v.v ... không? Nếu không, những thư viện Boost thì sao?

EDIT:

Xin chào. Cảm ơn các câu trả lời tuyệt vời ban đầu. Tôi đoán một lý do tôi nghĩ rằng điều này có thể được xây dựng trong là tôi đã suy nghĩ hoàn toàn trong không gian x86 và nghĩ rằng một PUSH/POP của con trỏ nên là một hành động nguyên tử ở cấp độ giảng dạy.

Tôi không chắc chắn nếu linh cảm ban đầu của tôi là đúng hay không, nhưng tôi đoán điều này sẽ không nhất thiết phải đúng trên tất cả các nền tảng. Mặc dù nếu chạy trên x86, bạn có nhận được PUSHes và POP nguyên tử vào ngăn xếp hay không và nếu vậy, điều này có thực sự làm cho nó không có khóa không?

+0

Nếu bạn quan tâm đến tính nguyên tử của lệnh x86 PUSH/POP, hãy đặt câu hỏi riêng biệt - nó không liên quan gì đến C++, sẽ không sử dụng hướng dẫn này để truy cập cấu trúc dữ liệu ngăn xếp. –

+2

Ủy ban bận rộn hơn khi viết các lớp giảng dạy song song về DDJ thay vì làm cho các trừu tượng mô hình bộ nhớ nguyên tử và tốt hơn nhiều cho trình biên dịch bắt buộc trong TR1 (có lẽ không phải trong TR2). Để trả lời: bạn không thực sự đẩy và bật và do đó thay đổi hoàn toàn các thanh ghi trên các luồng nói hiện đang chạy trên các lõi riêng biệt phải không? :-) Bắn tốt, nhưng sẽ không hoạt động .. Bạn không thể làm điều đó không có khóa hoặc ít nhất là không có búa CAS. Đối với các cộng sự của C++: Họ chỉ cần ngồi xuống và xác định và đồng ý về các giao thức mạch lạc hiện có, + để lại một số phạm vi cho những phát triển mới. –

+0

Đối với những người quan tâm, tôi nhìn vào hoạt động nguyên tử và Intel hỗ trợ DCAS thông qua cmpxchg16b. Thật không may AMD chỉ có cmpxchg8b. Không quan trọng đối với tôi, vì tôi đang viết cho các máy Intel :) – bugmenot77

Trả lời

21

Yep: Boost.Thread thật tuyệt vời và phù hợp với nhu cầu của bạn rất tốt. (Những ngày này, nhiều người nói rằng bạn gần như có thể đếm Boost là chức năng được tích hợp sẵn.)

Vẫn không có lớp nào bạn có thể sử dụng ngoài hộp, nhưng một khi bạn có đồng bộ hóa nguyên gốc trong tầm tay , nó thực sự là khá đơn giản để thực hiện wrapper thread-an toàn của riêng bạn xung quanh, ví dụ, std::stack. Nó có thể giống như thế này (không phải thực hiện tất cả các phương pháp ...):

template <typename T> class MyThreadSafeStack { 
    public: 
    void push(const T& item) { 
     boost::mutex::scoped_lock lock(m_mutex); 
     m_stack.push(item); 
    } 
    void pop() { 
     boost::mutex::scoped_lock lock(m_mutex); 
     m_stack.pop(); 
    } 
    T top() const { // note that we shouldn't return a reference, 
        // because another thread might pop() this 
        // object in the meanwhile 
     boost::mutex::scoped_lock lock(m_mutex); 
     return m_stack.top(); 
    } 

    private: 
    mutable boost::mutex m_mutex; 
    std::stack<T> m_stack; 
}  

Nếu bạn chưa quen với C++, xin vui lòng tìm hiểu về RAII. Có liên quan đến trường hợp này, Boost.Thread có các lớp "scoped lock" để làm cho nó khó chụp ở chân bằng cách quên để nhả khóa.

Nếu bạn đã bao giờ thấy mình viết code như thế này:

void doStuff() { 
    myLock.lock(); 
    if (!condition) { 
    reportError(); 
    myLock.unlock(); 
    return; 
    } 
    try { 
    doStuffThatMayThrow(); 
    } 
    catch (std::exception& e) { 
    myLock.unlock(); 
    throw e; 
    } 
    doMoreStuff(); 
    myLock.unlock(); 
} 

, sau đó bạn nên chỉ nói không, và đi RAII thay vì (cú pháp không trực tiếp từ Boost):

void doStuff() { 
    scoped_lock lock; 
    if (!condition) { 
    reportError(); 
    return; 
    } 
    doStuffThatMayThrow(); 
    doMoreStuff(); 
} 

Điểm là khi đối tượng scoped_lock vượt quá phạm vi, hàm hủy của nó giải phóng tài nguyên - trong trường hợp này là khóa. Điều này sẽ luôn xảy ra, bất kể bạn có thoát khỏi phạm vi bằng cách ném một ngoại lệ hay bằng cách thực hiện câu lệnh return lẻ mà đồng nghiệp của bạn lén lút thêm vào giữa chức năng của bạn hoặc đơn giản bằng cách đến cuối hàm.

+0

Câu trả lời thú vị. Cảm ơn, đây là câu trả lời rất rõ ràng và rất hữu ích! – bugmenot77

+0

Bạn được chào đón; Tôi rất vui nếu tôi có thể giúp! – Reunanen

+2

Bạn nên lưu ý rằng các thùng chứa std không an toàn chỉ ngay cả khi bị khóa bởi một mutex. Lý do là việc sửa đổi của họ làm mất hiệu lực các trình vòng lặp hiện có. – ASk

4

Chuẩn C++ hiện tại không giải quyết vấn đề luồng, vì vậy câu trả lời cho câu hỏi đầu tiên của bạn là không. Và nói chung, một ý tưởng tồi là xây dựng khóa thành các cấu trúc dữ liệu cơ bản, bởi vì chúng không có đủ thông tin để thực hiện nó một cách chính xác và/hoặc hiệu quả. Thay vào đó, khóa phải được thực hiện trong các lớp sử dụng cấu trúc dữ liệu - nói cách khác, trong các lớp ứng dụng của riêng bạn.

+1

Tôi phải phân loại không đồng ý (mặc dù không phải là downvote). Tôi nghĩ rằng nó là hoàn toàn ổn để viết một lớp ThreadSafeStack mà kết thúc tốt đẹp một cá nhân std :: stack dụ. Sau đó, vào đầu mỗi phương thức (push, pop, ...) bạn nhập một phần quan trọng, hoặc tương tự. Đúng là đầu() không nên trả về một tham chiếu mà là một bản sao. Điều này gây ra mất hiệu quả, nhưng sự dễ dàng hơn trong việc viết các lớp ứng dụng thường rất đáng giá, theo ý kiến ​​của tôi. Và trong trường hợp khi không (ví dụ: các đối tượng lớn đang được sao chép), thì bạn chỉ không sử dụng lớp trình bao bọc. – Reunanen

+0

Vâng, đó là những gì tôi muốn nói, nhưng có thể thể hiện một cách tệ hại - ThreadSafeStack là một trong những lớp học của ứng dụng của bạn. Nhưng một thư viện đa năng không nên (IMHO) cung cấp các lớp như vậy. –

+1

Ah, ok. Tôi đã nghĩ về các lớp ứng dụng như một cái gì đó mà chủ yếu chứa logic kinh doanh. – Reunanen

1

AFAIK, không được hỗ trợ trong C++. Bạn sẽ phải đồng bộ hóa các hoạt động ngăn xếp bằng cách sử dụng một công cụ đồng bộ hóa đơn giản. CriticalSection sẽ làm gì nếu các chủ đề thuộc về cùng một proceass nếu không thì hãy dùng Mutex.

1

Không có cơ chế tích hợp để hỗ trợ tính năng này trong C++ cũng như trong thư viện Boost (lưu ý: một số người đã viết thread-safe stacks/etc. in the Boost style). Bạn sẽ phải mượn một số mã hoặc nấu trong đồng bộ hóa của riêng bạn.Lưu ý rằng trường hợp của bạn có thể yêu cầu một trình bảo vệ nhiều đầu đọc một người viết (SWMRG), trong đó nhiều luồng của người viết có thể truy cập vào ngăn xếp (nhưng chỉ có một chủ đề tại một thời điểm nhất định) và trong đó nhiều người đọc có thể truy cập stack (nhiều tại một điểm nhất định trong thời gian). Richter có reference implementation.

1

Nếu bạn không muốn sử dụng khóa, bạn cần sử dụng ngăn xếp không khóa. Đây thực sự không phải là khó (hàng đợi không có khóa khó khăn hơn). Bạn cần một nền tảng trao đổi so sánh cụ thể, chẳng hạn như InterlockedCompareExchange trên Windows, nhưng điều này không khó để trừu tượng.

Xem ở đây cho một ví dụ trong C#:

http://www.boyet.com/Articles/LockFreeRedux.html

0

Nếu bạn đang chạy trên Windows, SLIST thực hiện một chồng lockfree (với các cấu trúc SLIST_HEADER & SLIST_ENTRY).

Thuật toán được triển khai bằng cách sử dụng ngăn xếp danh sách liên kết đơn giản/đẩy đơn giản bằng cách sử dụng các chức năng khóa liên động. Mục không rõ ràng duy nhất là số lượt truy cập tăng để tránh các vấn đề ABA.

+0

Cảm ơn. Tôi hy vọng điều này hữu ích cho người khác. Tôi đã chạy trên Linux và sử dụng giải pháp khóa scoped ở trên. Cuối cùng, tôi đặt mã khóa vào mã thay vì cấu trúc. – bugmenot77