2010-03-19 33 views
17

Tôi muốn xác minh rằng sự hiểu biết của tôi là chính xác. Loại điều này là khó khăn vì vậy tôi gần như chắc chắn tôi đang thiếu một cái gì đó. Tôi có một chương trình bao gồm một chuỗi thời gian thực và một chuỗi không theo thời gian thực. Tôi muốn các chủ đề không RT để có thể trao đổi một con trỏ đến bộ nhớ được sử dụng bởi các chủ đề RT.Hoán đổi nguyên tử trong GNU C++

Từ các tài liệu, hiểu biết của tôi là điều này có thể được thực hiện trong g++ với:

// global 
Data *rt_data; 

Data *swap_data(Data *new_data) 
{ 
#ifdef __GNUC__ 
    // Atomic pointer swap. 
    Data *old_d = __sync_lock_test_and_set(&rt_data, new_data); 
#else 
    // Non-atomic, cross your fingers.           
    Data *old_d = rt_data; 
    rt_data = new_data; 
#endif 
    return old_d; 
} 

Đây là nơi duy nhất trong chương trình (trừ thiết lập ban đầu), nơi rt_data được sửa đổi. Khi rt_data được sử dụng trong ngữ cảnh thời gian thực, nó được sao chép vào một con trỏ cục bộ. Đối với old_d, sau này khi chắc chắn rằng bộ nhớ cũ không được sử dụng, bộ nhớ sẽ được giải phóng trong chuỗi không phải RT. Điều này có đúng không? Tôi có cần volatile ở bất kỳ đâu không? Có nguyên gốc đồng bộ hóa khác tôi nên gọi?

Bằng cách này tôi đang làm điều này trong C++, mặc dù tôi quan tâm đến việc câu trả lời có khác với C.

Cảm ơn trước.

Trả lời

24

Thường không sử dụng volatile khi viết mã đồng thời trong C/C++.Các ngữ nghĩa của volatile là rất gần với những gì bạn muốn mà nó là hấp dẫn nhưng cuối cùng dễ bay hơi là not enough. Rất tiếc, Java/C# volatile != C/C++ volatile. Herb Sutter có một tuyệt vời article giải thích sự lộn xộn khó hiểu.

Điều bạn thực sự muốn là hàng rào bộ nhớ. __sync_lock_test_and_set cung cấp hàng rào cho bạn.

Bạn cũng sẽ cần một hàng rào bộ nhớ khi bạn sao chép (tải) con trỏ rt_data vào bản sao cục bộ của bạn.

Khóa chương trình miễn phí rất phức tạp. Nếu bạn sẵn sàng sử dụng các tiện ích mở rộng C++ 0x của Gcc, nó sẽ dễ dàng hơn một chút:

#include <cstdatomic> 

std::atomic<Data*> rt_data; 

Data* swap_data(Data* new_data) 
{ 
    Data* old_data = rt_data.exchange(new_data); 
    assert(old_data != new_data); 
    return old_data; 
} 

void use_data() 
{ 
    Data* local = rt_data.load(); 
    /* ... */ 
} 
+0

Cảm ơn bạn! Tôi có thể làm theo đề nghị của bạn về việc sử dụng std :: atomic, điều đó thật tuyệt vời. (Tôi chưa làm quen với các công cụ C++ 0x mới nhất.) Chỉ cần tò mò, nếu tôi sử dụng __sync_lock_test_and_set, hàng rào chính xác để sử dụng khi đọc là gì? (ví dụ: để tạo bản sao cục bộ) – Steve

3

Cập nhật: Câu trả lời này là không đúng, vì tôi đang thiếu thực tế là volatile đảm bảo rằng các truy cập vào volatile biến không được sắp xếp lại, nhưng không cung cấp đảm bảo như vậy đối với các phi volatile truy cập và thao tác khác. Một hàng rào bộ nhớ cung cấp bảo đảm như vậy, và là cần thiết cho ứng dụng này. Câu trả lời ban đầu của tôi là dưới đây, nhưng không hành động trên nó. Xem this answer để có giải thích tốt về lỗ hổng trong sự hiểu biết của tôi dẫn đến phản hồi không chính xác sau đây.

Original câu trả lời:

Có, bạn cần volatile về kê khai rt_data của bạn; bất kỳ lúc nào một biến có thể được sửa đổi bên ngoài luồng điều khiển của chuỗi truy cập, nó phải được khai báo volatile. Mặc dù bạn có thể thoát khỏi nếu không có volatile vì bạn đang sao chép sang con trỏ cục bộ, ít nhất volatile sẽ giúp bằng tài liệu và cũng có thể hạn chế tối ưu hóa một số trình biên dịch có thể gây ra sự cố. Hãy xem xét ví dụ sau đây, được thông qua từ DDJ:

volatile int a; 
int b; 
a = 1; 
b = a; 

Nếu nó có thể cho a có giá trị của nó thay đổi giữa a=1b=a, sau đó a cần được khai báo volatile (trừ khi, tất nhiên, gán một out-of- có thể chấp nhận giá trị ngày thành b). Đa luồng, đặc biệt với nguyên thủy nguyên tử, tạo thành một tình huống như vậy. Tình huống cũng được kích hoạt với các biến được sửa đổi bởi các trình xử lý tín hiệu và các biến được ánh xạ tới các vị trí bộ nhớ lẻ (ví dụ: các thanh ghi I/O phần cứng). Xem thêm this question.

Nếu không, có vẻ ổn với tôi.

Trong C, tôi có thể sử dụng nguyên thủy nguyên tử được cung cấp bởi GLib cho việc này. Họ sẽ sử dụng một hoạt động nguyên tử khi có sẵn và quay trở lại với việc thực hiện dựa trên mutex chậm nhưng chính xác nếu các hoạt động nguyên tử không có sẵn. Boost có thể cung cấp một cái gì đó tương tự cho C++.

+5

Dễ bay hơi không liên quan gì đến đồng thời, chúng hoàn toàn trực giao. Cả hai đều đối phó với việc buộc tải/lưu trữ và sắp xếp lại, nhưng các đảm bảo được cung cấp bởi dễ bay hơi không giải quyết được các vấn đề đồng thời. –

+0

@Caspin Có, dễ bay hơi là trực giao đối với các vấn đề tương tranh, và dễ bay hơi một mình là không đủ. Tuy nhiên, đó là sự hiểu biết của tôi rằng dễ bay hơi là hữu ích trong lập trình đồng thời để đảm bảo rằng các chủ đề nhìn thấy những thay đổi của nhau. Không có nhiều khác biệt giữa một biến được thay đổi bởi một luồng khác và nó bị thay đổi bởi một phần cứng ngắt - cả hai đều vi phạm các giả định cần thiết cho tải/lưu trữ sắp xếp lại, và bay hơi nói với trình biên dịch rằng những giả định đó không nhất thiết phải giữ. –

+0

Trực giao có nghĩa là các vấn đề giải quyết dễ bay hơi không phải là loại vấn đề lập trình đồng thời tạo ra. Đặc biệt, bảo đảm đặt hàng dễ bay hơi chỉ áp dụng cho luồng hiện tại và chỉ đối với các chất bay hơi khác. Vui lòng đọc các liên kết được cung cấp trong câu trả lời của tôi vì toàn bộ mô tả dễ bay hơi quá phức tạp để phù hợp với nhận xét. Hoặc tốt hơn nhưng yêu cầu stackoverflow "Tại sao không phải là dễ bay hơi hữu ích cho lập trình c/C++ đồng thời?" và tôi sẽ cung cấp một câu trả lời sâu sắc. –

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