C ++ std :: memory_order_relaxed kebingungan

Oct 12 2020

Saya membaca artikel ini dari GCC Wiki tentang hambatan memori C ++ (dan itu luar biasa).
Itu cukup mudah sampai saya mencapai titik ini:

Pendekatan sebaliknya adalah std :: memory_order_relaxed. Model ini memungkinkan sinkronisasi yang jauh lebih sedikit dengan menghapus pembatasan terjadi sebelum. Jenis operasi atomik ini juga dapat memiliki berbagai pengoptimalan yang dilakukan padanya, seperti penghapusan penyimpanan mati dan penyamarataan. Jadi pada contoh sebelumnya:

-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 */

Karena utas tidak perlu disinkronkan di seluruh sistem, pernyataan dalam contoh ini sebenarnya bisa GAGAL.

Oke, ini juga mudah, ayo lanjutkan ..

-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)

Penegasan tidak bisa gagal. Setelah penyimpanan 2 dilihat oleh utas 2, ia tidak dapat lagi melihat nilai 1. Hal ini mencegah penggabungan beban santai dari satu variabel di seluruh beban santai dari referensi berbeda yang mungkin alias.

Inilah yang membuat saya bingung, mengapa y tidak dapat memuat nilai 2 dan z memuat nilai 1 (dan menyebabkan pernyataan gagal), karena pengurutan tidak disinkronkan di utas 1?

Jawaban

4 NicolBolas Oct 12 2020 at 12:12

Pengurutan santai adalah relatif terhadap pengurutan operasi yang berkaitan dengan akses memori lainnya , bukan pengurutan relatif terhadap atom yang dimodifikasi-rileks. Dalam kasus pertama Anda, fakta bahwa Anda dapat melihat 10 xtidak berarti apa-apa terkait dengan nilai y. Dan sebaliknya.

Tetapi kasus kedua Anda berbeda, karena ini mempengaruhi objek atom yang sama.

[intro.races] / 10 memberi tahu kita bahwa, di dalam utas, jika satu operasi diurutkan sebelum yang lain, maka operasi itu "terjadi sebelum" yang lain. Dan [intro.races] / 14-17 menguraikan perilaku berikut sehubungan dengan atomics :

Empat persyaratan koherensi sebelumnya secara efektif melarang penyusunan ulang compiler operasi atom ke satu objek, meskipun kedua operasi tersebut adalah beban santai.

Dan itulah yang Anda miliki di sini. Semua modifikasi terjadi pada objek yang sama, jadi harus terjadi dalam beberapa urutan. Sekalipun urutan itu tidak dapat ditentukan dengan tepat, urutan tersebut harus menghormati hubungan "terjadi sebelum" kode.

Dua operasi utas 1 diurutkan oleh hubungan "terjadi sebelum". Dan operasi Thread 2 sendiri diurutkan oleh hubungan "terjadi sebelum".

Karena mereka semua bekerja pada objek atom yang sama, jika ymendapat nilai 2, maka itu pasti "terjadi setelah" xdisetel ke 2. Jadi urutan aksesnya xpasti "x = 1, x = 2, baca x ". Dan karena pembacaan terakhir dari xterjadi setelah pembacaan pertama dari x, nilai yang didapatnya tidak boleh 1.