C ++ std :: memory_order_relaxed nhầm lẫn

Oct 12 2020

Tôi đang đọc bài viết này từ GCC Wiki về các rào cản bộ nhớ C ++ (và sự tuyệt vời của nó).
Nó khá đơn giản cho đến khi tôi đi đến điểm này:

Cách tiếp cận ngược lại là std :: memory_order_relaxed. Mô hình này cho phép đồng bộ hóa ít hơn nhiều bằng cách loại bỏ các hạn chế xảy ra trước đó. Các loại hoạt động nguyên tử này cũng có thể có các tối ưu hóa khác nhau được thực hiện trên chúng, chẳng hạn như loại bỏ cửa hàng chết và khởi động. Vì vậy, trong ví dụ trước:

-Thread 1-
y.store (20, memory_order_relaxed)
x.store (10, memory_order_relaxed)

-Thread 2-
if (x.load (memory_order_relaxed) == 10)
  {
    assert (y.load(memory_order_relaxed) == 20) /* assert A */
    y.store (10, memory_order_relaxed)
  }

-Thread 3-
if (y.load (memory_order_relaxed) == 10)
  assert (x.load(memory_order_relaxed) == 10) /* assert B */

Vì các luồng không cần phải được đồng bộ hóa trên toàn hệ thống, nên việc khẳng định trong ví dụ này thực sự có thể KHÔNG.

Ok, điều này cũng đơn giản, hãy tiếp tục ..

-Thread 1-
x.store (1, memory_order_relaxed)
x.store (2, memory_order_relaxed)

-Thread 2-
y = x.load (memory_order_relaxed)
z = x.load (memory_order_relaxed)
assert (y <= z)

Quả quyết không thể thất bại. Khi lưu trữ của 2 được luồng 2 nhìn thấy, nó không còn có thể thấy giá trị 1. Điều này ngăn việc kết hợp các tải thư giãn của một biến trên các tải thư giãn của một tham chiếu khác có thể là bí danh.

Đây là điều khiến tôi bối rối, tại sao y không thể tải giá trị 2 và z tải giá trị 1 (và khiến xác nhận không thành công), vì thứ tự không được đồng bộ hóa trong luồng 1?

Trả lời

4 NicolBolas Oct 12 2020 at 12:12

Thứ tự thư giãn có liên quan đến thứ tự của các hoạt động đối với các truy cập bộ nhớ khác , không phải thứ tự liên quan đến nguyên tử được sửa đổi thư giãn. Trong trường hợp đầu tiên của bạn, thực tế là bạn có thể thấy 10 in xkhông có ý nghĩa gì đối với giá trị của y. Và ngược lại.

Nhưng trường hợp thứ hai của bạn thì khác, bởi vì nó ảnh hưởng đến cùng một vật thể nguyên tử.

[intro.races] / 10 cho chúng ta biết rằng, trong một chuỗi, nếu một thao tác được sắp xếp trước một thao tác khác, thì thao tác đó "xảy ra trước" thao tác kia. Và [intro.races] / 14-17 phác thảo hành vi sau đối với nguyên tử :

Bốn yêu cầu liên kết trước đây không cho phép trình biên dịch sắp xếp lại thứ tự các hoạt động nguyên tử một cách hiệu quả cho một đối tượng, ngay cả khi cả hai hoạt động đều là tải thư giãn.

Và đó là những gì bạn có ở đây. Tất cả các sửa đổi đều xảy ra với cùng một đối tượng, vì vậy chúng phải xảy ra theo một số thứ tự. Ngay cả khi thứ tự đó không thể được xác định chính xác, thứ tự đó phải tôn trọng các mối quan hệ "xảy ra trước" của mã.

Hai hoạt động của luồng 1 được sắp xếp theo mối quan hệ "xảy ra trước". Và các hoạt động của Thread 2 được sắp xếp theo thứ tự của một mối quan hệ "xảy ra trước".

Vì tất cả chúng đều hoạt động trên cùng một đối tượng nguyên tử, nếu ynhận giá trị là 2, thì nó phải "xảy ra sau khi" xđược đặt thành 2. Vì vậy, thứ tự truy cập cho xphải là "x = 1, x = 2, đọc x ”. Và vì lần đọc cuối cùng xxảy ra sau lần đọc đầu tiên x, giá trị mà nó nhận được không thể là 1.