2015-02-28 15 views
5

Tôi có sai khi giả định rằng tải nguyên tử cũng hoạt động như một hàng rào bộ nhớ đảm bảo rằng tất cả các bài viết không nguyên tử sẽ trở nên hiển thị trước các chủ đề khác không?Hành vi sắp xếp bộ nhớ của std :: atomic :: load

Để minh họa:

volatile bool arm1 = false; 
std::atomic_bool arm2 = false; 
bool triggered = false; 

thread1:

arm1 = true; 
//std::std::atomic_thread_fence(std::memory_order_seq_cst); // this would do the trick 
if (arm2.load()) 
    triggered = true; 

thread2:

arm2.store(true); 
if (arm1) 
    triggered = true; 

Tôi mong rằng sau khi thực hiện cả hai 'kích hoạt' sẽ thành sự thật. Xin vui lòng không đề nghị để làm cho arm1 nguyên tử, điểm là để khám phá những hành vi của nguyên tử :: tải.

Trong khi tôi phải thừa nhận tôi không hiểu đầy đủ các định nghĩa chính thức về ngữ nghĩa thư giãn khác nhau của memory order Tôi nghĩ rằng tuần tự phù hợp đặt hàng là khá đơn giản ở chỗ nó đảm bảo rằng "tổng trật tự duy nhất tồn tại trong đó tất cả các chủ đề quan sát tất cả các sửa đổi theo cùng một thứ tự. " Với tôi điều này ngụ ý rằng std :: atomic :: load với thứ tự bộ nhớ mặc định của std :: memory_order_seq_cst cũng sẽ hoạt động như một hàng rào bộ nhớ. Điều này được chứng thực thêm bằng tuyên bố sau trong "Thứ tự liên tục theo thứ tự":

Tổng số thứ tự tuần tự yêu cầu một lệnh CPU hàng rào bộ nhớ đầy đủ trên tất cả các hệ thống đa lõi. Tuy nhiên, ví dụ đơn giản của tôi dưới đây cho thấy đây không phải là trường hợp với MSVC 2013, gcc 4.9 (x86) và clang 3.5.1 (x86), trong đó tải nguyên tử chỉ đơn giản là chuyển thành lệnh tải.

#include <atomic> 

std::atomic_long al; 

#ifdef _WIN32 
__declspec(noinline) 
#else 
__attribute__((noinline)) 
#endif 
long load() { 
    return al.load(std::memory_order_seq_cst); 
} 

int main(int argc, char* argv[]) { 
    long r = load(); 
} 

Với gcc này trông giống như:

load(): 
    mov rax, QWORD PTR al[rip] ; <--- plain load here, no fence or xchg 
    ret 
main: 
    call load() 
    xor eax, eax 
    ret 

tôi sẽ bỏ qua các msvc và kêu vang mà cơ bản là giống hệt nhau. Bây giờ trên gcc cho ARM chúng ta có được những gì tôi mong đợi:

load(): 
    dmb sy       ; <---- data memory barrier here 
    movw r3, #:lower16:.LANCHOR0 
    movt r3, #:upper16:.LANCHOR0 
    ldr r0, [r3]     
    dmb sy       ; <----- and here 
    bx lr 
main: 
    push {r3, lr} 
    bl load() 
    movs r0, #0 
    pop {r3, pc} 

Đây không phải là một câu hỏi học tập, nó kết quả trong một điều kiện chủng tộc tinh tế trong mã của chúng tôi mà gọi vào câu hỏi hiểu biết của tôi về hành vi của std :: nguyên tử.

+0

[(Cửa hàng sẽ yêu cầu hàng rào.)] (Http://www.cl.cam.ac.uk/~pes20/cpp/cpp0xmappings.html) –

+0

@tc sai khi nói rằng tải có seq_cst ngữ nghĩa trên x86. Nhưng bạn đúng rằng họ đủ mạnh .. để có ngữ nghĩa về thủy sản – Anton

+0

Vấn đề không phải với các mô hình bộ nhớ của bộ xử lý khác nhau mà đúng hơn là với tiêu chuẩn C++ đảm bảo cho tải nguyên tử ::. Vui lòng xem bản chỉnh sửa hiện chứa ví dụ về các kỳ vọng (có thể không chính xác) của tôi. – Alf

Trả lời

3

Sigh, đây là quá dài cho một lời nhận xét:

Không phải là ý nghĩa của nguyên tử "xuất hiện để xảy ra ngay lập tức với phần còn lại của hệ thống"?

Tôi muốn nói có và không với điều đó, tùy thuộc vào cách bạn nghĩ về nó. Để viết với SEQ_CST, vâng. Nhưng theo như cách tải nguyên tử được xử lý, hãy kiểm tra 29.3 của tiêu chuẩn C++ 11. Cụ thể, 29.3.3 là đọc thực sự tốt, và 29.3.4 có thể được cụ thể những gì bạn đang tìm kiếm:

Đối với hoạt động B nguyên tử mà đọc giá trị của một đối tượng M nguyên tử, nếu có một memory_order_seq_ - hàng rào c X được giải trình tự trước B, sau đó B quan sát sửa đổi memory_order_seq_cst cuối cùng của M trước X trong tổng số thứ tự S hoặc sửa đổi M sau đó theo thứ tự sửa đổi của nó.

Về cơ bản, SEQ_CST buộc đơn hàng toàn cầu giống như tiêu chuẩn nói, nhưng lần đọc có thể trả về và giá trị cũ mà không vi phạm ràng buộc 'nguyên tử'.

Để thực hiện 'nhận giá trị mới nhất tuyệt đối', bạn cần thực hiện một thao tác buộc giao thức kết nối phần cứng phải khóa (hướng dẫn lock trên x86_64). Đây là những gì các hoạt động so sánh và trao đổi nguyên tử làm, nếu bạn nhìn vào đầu ra lắp ráp.

+0

Đây là một giải thích thực sự hữu ích!Vì vậy, về cơ bản ví dụ của tôi là sai trên 2 số: a) không có gì đảm bảo rằng arm2.load sẽ đọc giá trị cập nhật mới nhất mà không có một atomic_thread_fence trước (seq_cst); và b) arm2.load (seq_cst) không có tác dụng gì trên cửa hàng arm1 trước đó. Nếu tôi hiểu bạn một cách chính xác để sửa ví dụ giả thiết, tôi sẽ không chỉ cần tạo arm1 cũng nguyên tử mà còn * giới thiệu hàng rào bộ nhớ trong cả hai luồng giữa cửa hàng và tải. – Alf

+0

Vâng ... a) cuộc gọi '' 'arm2.load()' '' thực sự có một '' 'SEQ_CST''' rào cản nếu bạn không chỉ định bất kỳ đối số, đó là thứ tự bộ nhớ nó mặc định. Ngoài ra, không có loại hàng rào bộ nhớ nào cho phép nó ghi lại 'giá trị được đảm bảo gần đây nhất', bởi vì đó không thực sự là hàng rào bộ nhớ. Tôi thích nghĩ về nó như _reads luôn luôn tương đối_. Bạn nhận được một hình ảnh bị trì hoãn về những gì đang xảy ra trong bộ nhớ. Điều này là do tốc độ của ánh sáng (giới hạn tốc độ truyền tín hiệu). b) Đúng. Bạn chỉ cần hàng rào với cửa hàng. Và để khắc phục, bạn sẽ chỉ cần tạo ra arm2 nguyên tử. –

+0

Để làm rõ, bạn cũng chỉ cần một hàng rào cửa hàng cửa hàng ở đó, giống như ngữ nghĩa _release_. '' 'SEQ_CST''' là không cần thiết. –

2

Tôi có sai khi giả định rằng tải trọng nguyên tử cũng sẽ hoạt động như một hàng rào bộ nhớ đảm bảo rằng tất cả các ghi không nguyên tử trước đó sẽ hiển thị với các luồng khác không?

Có. atomic::load(SEQ_CST) chỉ thi hành mà đọc không thể tải một giá trị 'hợp lệ', và không viết cũng không tải có thể được sắp xếp lại bởi trình biên dịch hoặc cpu xung quanh tuyên bố đó. Điều đó không có nghĩa là bạn sẽ luôn nhận được giá trị cập nhật nhất.

Tôi mong chờ mã của bạn để có một cuộc chạy đua dữ liệu vì một lần nữa, rào cản không đảm bảo nhiều nhất đến giá trị ngày được nhìn thấy tại một thời điểm nhất định, họ chỉ ngăn chặn sắp xếp lại.

của nó hoàn toàn hợp lệ cho thread1 để không thấy ghi bởi thread2 và do đó không được thiết lập triggered, và cho thread2 để không thấy ghi bởi thread1 (một lần nữa, không phải thiết lập triggered), bởi vì bạn chỉ viết 'nguyên tử' từ một thread .

Với hai chủ đề viết và đọc các giá trị được chia sẻ, bạn sẽ cần một rào cản trong mỗi chuỗi để duy trì tính nhất quán. Dường như bạn biết điều này đã có trong các bình luận mã của bạn, vì vậy tôi sẽ để nó ở "tiêu chuẩn C++ có phần gây hiểu nhầm khi nói đến mô tả chính xác ý nghĩa của các hoạt động nguyên tử/đa luồng".

Mặc dù bạn đang viết C++, vẫn tốt nhất, theo ý kiến ​​của tôi, để suy nghĩ về những gì bạn đang làm trên kiến ​​trúc cơ bản.

Không chắc chắn tôi đã giải thích rõ điều đó, nhưng tôi rất sẵn lòng đi vào chi tiết hơn nếu bạn muốn.

+0

Không phải là ý nghĩa của nguyên tử "xuất hiện để xảy ra ngay lập tức với phần còn lại của hệ thống"? http://en.wikipedia.org/wiki/Atomicity_(programming). Với tôi nó có nghĩa là tải nguyên tử :: thực sự được đảm bảo để có được giá trị cập nhật nhất được viết bởi cửa hàng :: nguyên tử cuối cùng. Tôi biết điều này là đúng đối với kiến ​​trúc x86. – Alf

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