Dalam dokumentasi std::memory_order
di cppreference.com ada contoh pemesanan santai:
Pemesanan santai
Operasi atom yang ditandai
memory_order_relaxed
bukan operasi sinkronisasi; mereka tidak memaksakan urutan di antara akses memori bersamaan. Mereka hanya menjamin konsistensi urutan atomitas dan modifikasi.Misalnya, dengan x dan y awalnya nol,
// Thread 1: r1 = y.load(std::memory_order_relaxed); // A x.store(r1, std::memory_order_relaxed); // B // Thread 2: r2 = x.load(std::memory_order_relaxed); // C y.store(42, std::memory_order_relaxed); // D
diizinkan untuk menghasilkan r1 == r2 == 42 karena, meskipun A diurutkan-sebelum B dalam utas 1 dan C diurutkan sebelum D dalam utas 2, tidak ada yang mencegah D muncul sebelum A dalam urutan modifikasi y, dan B dari muncul sebelum C dalam urutan modifikasi x. Efek samping D pada y dapat terlihat dengan beban A di ulir 1 sedangkan efek samping B pada x dapat terlihat dengan beban C di ulir 2. Secara khusus, ini dapat terjadi jika D selesai sebelum C di utas 2, baik karena penyusunan ulang kompiler atau saat runtime.
dikatakan "C diurutkan sebelum D dalam utas 2".
Menurut definisi urutan-sebelumnya, yang dapat ditemukan dalam Urutan evaluasi , jika A diurutkan sebelum B, maka evaluasi A akan diselesaikan sebelum evaluasi B dimulai. Karena C diurutkan sebelum D dalam utas 2, C harus diselesaikan sebelum D dimulai, maka bagian kondisi dari kalimat terakhir snapshot tidak akan pernah terpuaskan.
sumber
Jawaban:
Saya percaya bahwa preferensi adalah benar. Saya pikir ini bermuara pada aturan "as-if" [intro.execution] / 1 . Kompiler hanya terikat untuk mereproduksi perilaku yang dapat diamati dari program yang dijelaskan oleh kode Anda. Hubungan berurutan-sebelum hanya dibuat antara evaluasi dari perspektif utas di mana evaluasi ini dilakukan [intro.execution] / 15 . Itu berarti ketika dua evaluasi diurutkan satu demi satu muncul di suatu tempat di beberapa utas, kode yang sebenarnya berjalan di utas tersebut harus berperilaku seolah-olah apa pun yang dilakukan evaluasi pertama memang memengaruhi apa pun yang dilakukan evaluasi kedua. Sebagai contoh
harus mencetak 42. Namun, kompiler sebenarnya tidak harus menyimpan nilai 42 ke objek
x
sebelum membaca nilai kembali dari objek itu untuk mencetaknya. Mungkin juga perlu diingat bahwa nilai terakhir untuk disimpanx
adalah 42 dan kemudian hanya mencetak nilai 42 langsung sebelum melakukan penyimpanan sebenarnya dari nilai 42 kex
. Bahkan, jikax
merupakan variabel lokal, mungkin juga hanya melacak nilai apa variabel terakhir ditugaskan pada titik mana pun dan bahkan tidak pernah membuat objek atau benar-benar menyimpan nilai 42. Tidak ada cara bagi utas untuk mengetahui perbedaannya. Perilaku selalu akan seolah-olah ada variabel dan seolah-olah nilai 42 sebenarnya disimpan di objekx
sebelumnyasedang dimuat dari objek itu. Tetapi itu tidak berarti bahwa kode mesin yang dihasilkan harus benar-benar menyimpan dan memuat apa pun di mana pun. Semua yang diperlukan adalah bahwa perilaku yang dapat diamati dari kode mesin yang dihasilkan tidak dapat dibedakan dari perilaku apa yang akan terjadi jika semua hal ini benar-benar terjadi.Jika kita melihat
maka ya, C diurutkan sebelum D. Tetapi jika dilihat dari utas ini secara terpisah, tidak ada yang C lakukan yang mempengaruhi hasil D. Dan tidak ada yang D lakukan akan mengubah hasil C. Satu-satunya cara seseorang dapat mempengaruhi yang lain adalah sebagai konsekuensi tidak langsung dari sesuatu yang terjadi di utas lainnya. Namun, dengan menentukan
std::memory_order_relaxed
, Anda menyatakan secara eksplisitbahwa urutan muatan dan penyimpanan diamati oleh utas lainnya tidak relevan. Karena tidak ada utas lain yang dapat mengamati beban dan penyimpanan dalam urutan tertentu, tidak ada utas lain yang dapat dilakukan untuk membuat C dan D saling memengaruhi secara konsisten. Dengan demikian, urutan di mana beban dan penyimpanan sebenarnya dilakukan tidak relevan. Dengan demikian, kompiler bebas untuk memesan ulang. Dan, seperti yang disebutkan dalam penjelasan di bawah contoh itu, jika toko dari D dilakukan sebelum memuat dari C, maka r1 == r2 == 42 memang bisa terjadi ...sumber
Kadang-kadang mungkin untuk suatu tindakan dipesan relatif terhadap dua urutan tindakan lainnya, tanpa menyiratkan urutan relatif tindakan di urutan tersebut relatif terhadap satu sama lain.
Misalkan, misalnya, seseorang memiliki tiga peristiwa berikut:
dan pembacaan p2 dipesan secara independen setelah penulisan p1 dan sebelum penulisan p3, tetapi tidak ada pemesanan khusus di mana p1 dan p3 mengambil bagian. Bergantung pada apa yang dilakukan dengan p2, mungkin tidak praktis bagi kompiler untuk menunda p1 melewati p3 dan masih mencapai semantik yang diperlukan dengan p2. Misalkan, kompiler tahu bahwa kode di atas adalah bagian dari urutan yang lebih besar:
Dalam hal ini, ia dapat menentukan bahwa ia dapat menyusun ulang toko ke p1 setelah kode di atas dan menggabungkannya dengan toko berikut, sehingga menghasilkan kode yang menulis p3 tanpa menulis p1 terlebih dahulu:
Meskipun kelihatannya dependensi data akan menyebabkan bagian tertentu dari hubungan sekuensing untuk berperilaku secara transitif, kompiler dapat mengidentifikasi situasi di mana dependensi data yang nyata tidak ada, dan dengan demikian tidak akan memiliki efek transitif yang diharapkan.
sumber
Jika ada dua pernyataan, kompiler akan menghasilkan kode dalam urutan berurutan sehingga kode untuk yang pertama akan ditempatkan sebelum yang kedua. Tapi CPU internal memiliki pipa dan mampu menjalankan operasi perakitan secara paralel. Pernyataan C adalah instruksi muatan. Ketika memori diambil, saluran pipa akan memproses beberapa instruksi berikutnya dan mengingat mereka tidak bergantung pada instruksi pemuatan, mereka bisa berakhir dieksekusi sebelum C selesai (mis. Data untuk D ada di cache, C di memori utama).
Jika pengguna benar-benar membutuhkan dua pernyataan untuk dieksekusi secara berurutan, operasi pemesanan memori yang lebih ketat dapat digunakan. Secara umum pengguna tidak peduli asalkan program secara logis benar.
sumber
Apa pun yang Anda pikirkan sama validnya. Standar tidak mengatakan apa yang dieksekusi secara berurutan, apa yang tidak, dan bagaimana hal itu dapat digabungkan .
Terserah Anda, dan untuk setiap programmer, untuk membuat semantik yang konsisten di atas kekacauan itu, sebuah karya yang layak untuk beberapa PhD.
sumber