2013-05-15 44 views
5

Tôi có một số mã mà cần phải được thread an toàn và ngoại lệ an toàn. Mã bên dưới là phiên bản rất đơn giản của sự cố của tôi:Khóa một mutex trong một destructor trong C++ 11

#include <mutex> 
#include <thread> 

std::mutex mutex; 
int n=0; 

class Counter{ 
public: 
    Counter(){ 
     std::lock_guard<std::mutex>guard(mutex); 
     n++;} 
    ~Counter(){ 
     std::lock_guard<std::mutex>guard(mutex);//How can I protect here the underlying code to mutex.lock() ? 
     n--;} 
}; 

void doSomething(){ 
    Counter counter; 
    //Here I could do something meaningful 
} 

int numberOfThreadInDoSomething(){ 
    std::lock_guard<std::mutex>guard(mutex); 
    return n;} 

Tôi có một mutex mà tôi cần khóa trong trình phá hủy của đối tượng. Vấn đề là destructor của tôi không nên ném ngoại lệ.

Tôi có thể làm gì?

0) Tôi không thể thay thế n với một biến nguyên tử (tất nhiên nó sẽ làm các trick ở đây nhưng đó không phải là điểm của câu hỏi của tôi)

1) Tôi có thể thay thế mutex của tôi với một spin khóa

2) Tôi có thể thử và bắt khóa vào vòng lặp vô hạn cho đến khi tôi có được khóa mà không có ngoại lệ nào được nêu ra

Không có giải pháp nào hấp dẫn. Bạn có cùng một vấn đề? Bạn đã giải quyết nó như thế nào?

+3

'Tôi có một mutex mà tôi cần phải khóa trong destructor của một đối tượng' - Âm thanh như một ý tưởng tồi. Thay vì cung cấp cho chúng tôi giải pháp và giải quyết vấn đề với chúng tôi, có thể bạn nên cho chúng tôi biết bạn đang cố gắng giải quyết vấn đề gì, vì vậy chúng tôi có thể cung cấp giải pháp tốt hơn. –

+1

@RobertHarvey Điều tôi thực sự muốn làm là chèn các sửa đổi vào bộ nhớ cache dùng chung sau khi chúng được lưu trong cơ sở dữ liệu. – Arnaud

Trả lời

8

Theo đề nghị của Adam H. Peterson, cuối cùng tôi quyết định viết một mutex không ném:

class NoThrowMutex{ 
private: 
    std::mutex mutex; 
    std::atomic_flag flag; 
    bool both; 
public: 
    NoThrowMutex(); 
    ~NoThrowMutex(); 
    void lock(); 
    void unlock(); 
}; 

NoThrowMutex::NoThrowMutex():mutex(),flag(),both(false){ 
    flag.clear(std::memory_order_release);} 

NoThrowMutex::~NoThrowMutex(){} 

void NoThrowMutex::lock(){ 
    try{ 
     mutex.lock(); 
     while(flag.test_and_set(std::memory_order_acquire)); 
     both=true;} 
    catch(...){ 
     while(flag.test_and_set(std::memory_order_acquire)); 
     both=false;}} 

void NoThrowMutex::unlock(){ 
    if(both){mutex.unlock();} 
    flag.clear(std::memory_order_release);} 

Ý tưởng là để có hai mutex thay vì chỉ có một. Các mutex thực là mutex spin thực hiện với một std::atomic_flag. Mutex spin này được bảo vệ bởi một std::mutex mà có thể ném.

Trong trường hợp bình thường, macro tiêu chuẩn được mua và cờ được đặt với chi phí chỉ một hoạt động nguyên tử. Nếu các mutex tiêu chuẩn không thể bị khóa ngay lập tức, thread sẽ ngủ.

Nếu vì bất kỳ lý do gì mà mutex tiêu chuẩn sẽ ném, mutex sẽ chuyển sang chế độ quay của nó. Các chủ đề mà các trường hợp ngoại lệ xảy ra sau đó sẽ lặp cho đến khi nó có thể thiết lập cờ. Vì không có chủ đề nào khác nhận thức được rằng luồng này đã triệt tiêu hoàn toàn mutex chuẩn, chúng cũng có thể quay tròn.

Trong trường hợp xấu nhất, cơ chế khóa này giảm xuống khóa quay. Hầu hết thời gian nó phản ứng giống như một mutex bình thường.

3

Đây là tình huống không tốt. Bị phá hủy của bạn đang làm điều gì đó có thể không thành công. Nếu không cập nhật được bộ đếm này sẽ làm hỏng ứng dụng của bạn một cách không thể phục hồi, bạn có thể chỉ đơn giản là để cho phép hủy bỏ lệnh hủy. Điều này sẽ làm hỏng ứng dụng của bạn bằng một cuộc gọi đến terminate, nhưng nếu ứng dụng của bạn bị hỏng, có thể tốt hơn để giết quá trình và dựa vào một số chương trình khôi phục cấp cao hơn (chẳng hạn như cơ quan giám sát cho daemon hoặc thử thực thi lại một tiện ích khác) . Nếu không thể giảm được bộ đếm, bạn nên hấp thụ ngoại lệ với khối try{}catch() và khôi phục (hoặc có khả năng lưu thông tin cho một số thao tác khác để khôi phục). Nếu nó không thể phục hồi, nhưng nó không gây tử vong, bạn có thể muốn nắm bắt và hấp thụ ngoại lệ và đăng nhập thất bại (dĩ nhiên là chắc chắn đăng nhập một cách an toàn ngoại lệ).

Sẽ rất lý tưởng nếu mã có thể được cấu trúc lại sao cho trình hủy không làm bất cứ điều gì không thể thất bại. Tuy nhiên, nếu mã của bạn là chính xác, thất bại trong khi có được một khóa có thể hiếm, ngoại trừ trong trường hợp các nguồn lực hạn chế, do đó, hoặc hấp thụ hoặc hủy bỏ thất bại có thể rất tốt được chấp nhận. Đối với một số mutexes, khóa() có lẽ là một hoạt động không ném (chẳng hạn như một spinlock sử dụng atomic_flag), và nếu bạn có thể sử dụng một mutex như vậy, bạn có thể mong đợi lock_guard không bao giờ ném. Chỉ lo lắng của bạn trong tình huống đó sẽ là bế tắc.

Các vấn đề liên quan