Jika ada dua utas yang mengakses variabel global, maka banyak tutorial mengatakan membuat variabel tidak stabil untuk mencegah kompiler melakukan caching variabel dalam register dan karenanya tidak diperbarui dengan benar. Namun dua utas yang mengakses variabel bersama adalah sesuatu yang membutuhkan perlindungan melalui mutex bukan? Tetapi dalam kasus itu, antara penguncian utas dan melepaskan mutex kode berada di bagian kritis di mana hanya satu utas yang dapat mengakses variabel, dalam hal mana variabel tidak perlu volatil?
Jadi, apa gunanya / tujuan volatile dalam program multi-threaded?
c++
multithreading
concurrency
atomic
volatile
David Preston
sumber
sumber
Jawaban:
Jawaban singkat & cepat :
volatile
hampir tidak berguna untuk pemrograman platform-agnostik, multithreaded. Itu tidak menyediakan sinkronisasi apa pun, tidak membuat pagar memori, juga tidak memastikan urutan pelaksanaan operasi. Itu tidak membuat operasi atom. Itu tidak membuat kode Anda utas aman secara ajaib.volatile
mungkin fasilitas paling banyak disalahpahami di semua C ++. Lihat ini , ini dan ini untuk informasi lebih lanjut tentangvolatile
Di sisi lain,
volatile
memang ada beberapa kegunaan yang mungkin tidak begitu jelas. Itu dapat digunakan banyak dengan cara yang sama akan digunakanconst
untuk membantu kompiler menunjukkan di mana Anda mungkin membuat kesalahan dalam mengakses beberapa sumber daya bersama dengan cara yang tidak dilindungi. Penggunaan ini dibahas oleh Alexandrescu dalam artikel ini . Namun, ini pada dasarnya menggunakan sistem tipe C ++ dengan cara yang sering dipandang sebagai penemuan dan dapat membangkitkan Perilaku Tidak Terdefinisi.volatile
secara khusus dimaksudkan untuk digunakan ketika berinteraksi dengan perangkat keras yang dipetakan memori, penangan sinyal dan instruksi kode mesin setjmp. Ini membuatvolatile
langsung berlaku untuk pemrograman tingkat sistem daripada pemrograman tingkat aplikasi normal.Standar C ++ 2003 tidak mengatakan bahwa
volatile
berlaku jenis Acquire atau Release semantik pada variabel. Faktanya, Standar ini sepenuhnya diam mengenai semua hal multithreading. Namun, platform tertentu memang menerapkan Acquire dan Release semantik padavolatile
variabel.[Pembaruan untuk C ++ 11]
C ++ 11 Standard sekarang tidak mengakui multithreading langsung dalam model memori dan lanuage, dan menyediakan fasilitas perpustakaan untuk menghadapinya dengan cara platform independen. Namun semantik
volatile
masih belum berubah.volatile
masih bukan mekanisme sinkronisasi. Bjarne Stroustrup mengatakan banyak hal di TCPPPL4E:[/ Akhiri pembaruan]
Semua di atas berlaku bahasa C ++ itu sendiri, sebagaimana didefinisikan oleh Standar 2003 (dan sekarang Standar 2011). Namun beberapa platform spesifik menambahkan fungsionalitas atau batasan tambahan untuk apa yang
volatile
tidak. Misalnya, dalam MSVC 2010 (setidaknya) Acquire dan Rilis semantik yang berlaku untuk operasi tertentu padavolatile
variabel. Dari MSDN :Namun, Anda mungkin mencatat fakta bahwa jika Anda mengikuti tautan di atas, ada beberapa perdebatan di komentar mengenai apakah memperoleh / melepaskan semantik benar-benar berlaku dalam kasus ini.
sumber
volatile
itu karena Anda berdiri di pundak orang yang digunakanvolatile
untuk mengimplementasikan pustaka threading.volatile
sebenarnya dilakukan di C ++. Apa yang dikatakan John benar , akhir cerita. Ini tidak ada hubungannya dengan kode aplikasi vs kode perpustakaan, atau "biasa" vs "programmer mahatahu seperti Tuhan" dalam hal ini.volatile
tidak perlu dan tidak berguna untuk sinkronisasi antar utas. Perpustakaan pustaka tidak dapat diimplementasikan dalam halvolatile
; itu harus bergantung pada detail platform khusus, dan ketika Anda mengandalkan itu, Anda tidak lagi perluvolatile
.(Catatan Editor: di C ++ 11
volatile
bukan alat yang tepat untuk pekerjaan ini dan masih memiliki data-race UB. Gunakanstd::atomic<bool>
denganstd::memory_order_relaxed
banyak / toko untuk melakukan ini tanpa UB. Pada implementasi nyata itu akan dikompilasi ke asm yang sama sepertivolatile
. Saya menambahkan jawaban dengan lebih detail, dan juga mengatasi kesalahpahaman dalam komentar bahwa memori yang tertata buruk mungkin menjadi masalah untuk kasus penggunaan ini: semua CPU dunia nyata memiliki memori bersama yang koheren sehinggavolatile
akan bekerja untuk ini pada implementasi C ++ nyata. dapat melakukannya.Beberapa diskusi dalam komentar tampaknya berbicara tentang kasus penggunaan lain di mana Anda akan membutuhkan sesuatu yang lebih kuat daripada atom santai. Jawaban ini sudah menunjukkan yang tidak
volatile
memberi Anda pemesanan.)Volatile kadang-kadang berguna karena alasan berikut: kode ini:
dioptimalkan oleh gcc ke:
Yang jelas salah jika bendera ditulis oleh utas lainnya. Perhatikan bahwa tanpa optimasi ini mekanisme sinkronisasi mungkin bekerja (tergantung pada kode lain beberapa hambatan memori mungkin diperlukan) - tidak perlu untuk mutex dalam 1 produsen - 1 skenario konsumen.
Kalau tidak, kata kunci yang mudah menguap terlalu aneh untuk dapat digunakan - kata kunci tersebut tidak memberikan jaminan pemesanan memori yang menggunakan akses yang mudah menguap dan tidak mudah menguap dan tidak menyediakan operasi atom apa pun - yaitu, Anda tidak mendapat bantuan dari kompiler dengan kata kunci yang mudah menguap kecuali caching register yang dinonaktifkan. .
sumber
volatile
tidak mencegah akses memori diatur ulang.volatile
akses tidak akan dipesan ulang sehubungan satu sama lain, tetapi mereka tidak memberikan jaminan tentang pemesanan ulang sehubungan dengan non-volatile
objek, dan karenanya, mereka pada dasarnya tidak berguna sebagai bendera juga.volatile
.while (work_left) { do_piece_of_work(); if (cancel) break;}
jika pembatalan disusun ulang dalam loop, logikanya masih valid. Saya punya sepotong kode yang bekerja sama: jika utas utama ingin mengakhiri, ia menetapkan bendera untuk utas lainnya, tetapi tidak ...Dalam C ++ 11, biasanya tidak pernah digunakan
volatile
untuk threading, hanya untuk MMIOTapi TL: DR, itu "bekerja" seperti atom dengan
mo_relaxed
pada perangkat keras dengan cache yang koheren (yaitu semuanya); itu cukup untuk menghentikan kompiler menjaga vars di register.atomic
tidak memerlukan penghalang memori untuk membuat atomicity atau visibilitas antar-thread, hanya untuk membuat utas saat ini menunggu sebelum / setelah operasi untuk membuat pemesanan antara akses utas ini ke variabel yang berbeda.mo_relaxed
tidak pernah membutuhkan penghalang, hanya memuat, menyimpan, atau RMW.Untuk menggulung atom Anda sendiri dengan
volatile
(dan inline-asm untuk hambatan) di masa lalu yang buruk sebelum C ++ 11std::atomic
,volatile
adalah satu-satunya cara yang baik untuk membuat beberapa hal bekerja . Tapi itu tergantung pada banyak asumsi tentang bagaimana implementasi bekerja dan tidak pernah dijamin oleh standar apa pun.Sebagai contoh, kernel Linux masih menggunakan atom gulungan tangan sendiri
volatile
, tetapi hanya mendukung beberapa implementasi C tertentu (GNU C, dentang, dan mungkin ICC). Sebagian itu karena ekstensi GNU C dan sintaks dan semantik asm inline, tetapi juga karena itu tergantung pada beberapa asumsi tentang cara kerja kompiler.Ini hampir selalu merupakan pilihan yang salah untuk proyek baru; Anda dapat menggunakan
std::atomic
(denganstd::memory_order_relaxed
) untuk mendapatkan kompiler untuk memancarkan kode mesin efisien yang sama dengan yang Anda bisavolatile
.std::atomic
denganmo_relaxed
obsoletvolatile
untuk tujuan threading. (Kecuali mungkin untuk mengatasi bug optimisasi yang terlewatkanatomic<double>
pada beberapa kompiler .)Implementasi internal
std::atomic
kompiler arus utama (seperti gcc dan dentang) tidak hanya digunakan secaravolatile
internal; kompiler secara langsung memaparkan fungsi muatan atom, penyimpanan, dan fungsi bawaan RMW. (mis. GNU C__atomic
bawaan yang beroperasi pada objek "biasa")Volatile dapat digunakan dalam praktek (tapi jangan lakukan itu)
Yang mengatakan,
volatile
apakah dapat digunakan dalam praktek untuk hal-hal sepertiexit_now
flag pada semua (?) Implementasi C ++ yang ada pada CPU nyata, karena bagaimana CPU bekerja (cache yang koheren) dan berbagi asumsi tentang bagaimanavolatile
seharusnya bekerja. Tapi tidak banyak lagi, dan tidak direkomendasikan. Tujuan dari jawaban ini adalah untuk menjelaskan bagaimana CPU yang ada dan implementasi C ++ benar-benar bekerja. Jika Anda tidak peduli tentang itu, yang perlu Anda ketahui adalah bahwastd::atomic
dengan mo_relaxed obsoletesvolatile
untuk threading.(Standar ISO C ++ cukup samar di atasnya, hanya mengatakan bahwa
volatile
akses harus dievaluasi secara ketat sesuai dengan aturan mesin abstrak C ++, tidak dioptimalkan. Mengingat bahwa implementasi nyata menggunakan ruang alamat memori mesin untuk memodelkan ruang alamat C ++, ini berartivolatile
membaca dan tugas harus dikompilasi untuk memuat / menyimpan instruksi untuk mengakses representasi objek dalam memori.)Sebagai jawaban lain menunjukkan,
exit_now
bendera adalah kasus sederhana komunikasi antar-thread yang tidak memerlukan sinkronisasi : itu tidak mempublikasikan bahwa isi array siap atau semacamnya. Hanya toko yang segera diperhatikan oleh beban yang tidak dioptimalkan-pergi di utas lain.Tanpa volatile atau atomik, aturan as-if dan asumsi tidak ada data-ras UB memungkinkan kompiler untuk mengoptimalkannya menjadi asm yang hanya memeriksa bendera sekali , sebelum memasukkan (atau tidak) loop tak terbatas. Inilah yang terjadi dalam kehidupan nyata untuk penyusun nyata. (Dan biasanya mengoptimalkan sebagian besar
do_stuff
karena loop tidak pernah keluar, sehingga setiap kode nanti yang mungkin menggunakan hasilnya tidak dapat dijangkau jika kita memasukkan loop).Program multithreading terjebak dalam mode yang dioptimalkan tetapi berjalan normal di -O0 adalah contoh (dengan deskripsi output asm GCC) tentang bagaimana sebenarnya ini terjadi dengan GCC pada x86-64. Juga pemrograman MCU - optimasi C ++ O2 rusak saat loop pada electronics.SE menunjukkan contoh lain.
Kami biasanya menginginkan optimisasi agresif yang CSE dan hoist memuatkan keluar dari loop, termasuk untuk variabel global.
Sebelum C ++ 11,
volatile bool exit_now
adalah salah satu cara untuk membuat pekerjaan ini sebagaimana dimaksud (pada implementasi C ++ normal). Tetapi dalam C ++ 11, perlombaan data UB masih berlakuvolatile
sehingga sebenarnya tidak dijamin oleh standar ISO untuk bekerja di mana saja, bahkan dengan asumsi cache koheren HW.Perhatikan bahwa untuk tipe yang lebih luas,
volatile
tidak memberikan jaminan kurangnya sobek. Saya mengabaikan perbedaan itu di sinibool
karena itu bukan masalah pada implementasi normal. Tapi itu juga bagian dari alasan mengapavolatile
masih tunduk pada perlombaan data UB bukannya setara dengan atom santai.Perhatikan bahwa "sebagaimana dimaksud" tidak berarti utas melakukan
exit_now
menunggu utas lainnya benar-benar keluar. Atau bahkan menungguexit_now=true
toko volatil untuk terlihat secara global sebelum melanjutkan operasi selanjutnya di utas ini. (atomic<bool>
dengan defaultmo_seq_cst
akan membuatnya menunggu sebelum seq_cst nanti memuat setidaknya. Pada banyak SPA Anda hanya akan mendapatkan penghalang penuh setelah toko).C ++ 11 menyediakan cara non-UB yang mengkompilasi yang sama
A "terus berjalan" atau "exit sekarang" bendera harus menggunakan
std::atomic<bool> flag
denganmo_relaxed
Menggunakan
flag.store(true, std::memory_order_relaxed)
while( !flag.load(std::memory_order_relaxed) ) { ... }
akan memberikan Anda asm yang sama persis (tanpa instruksi penghalang mahal) yang akan Anda dapatkan
volatile flag
.Selain tanpa robek,
atomic
juga memberi Anda kemampuan untuk menyimpan di satu utas dan memuat di utas lainnya tanpa UB, sehingga kompiler tidak dapat mengangkat beban keluar dari satu loop. (Asumsi tidak ada perlombaan data UB adalah yang memungkinkan optimalisasi agresif yang kita inginkan untuk objek non-atom yang tidak mudah menguap.) Fituratomic<T>
ini hampir sama dengan apa yangvolatile
dilakukan untuk muatan murni dan penyimpanan murni.atomic<T>
juga buat+=
dan seterusnya ke dalam operasi atom RMW (secara signifikan lebih mahal daripada beban atom menjadi sementara, operasikan, lalu simpan atom terpisah. Jika Anda tidak menginginkan RMW atom, tulis kode Anda dengan temporer lokal).Dengan
seq_cst
pemesanan default yang Anda dapatkanwhile(!flag)
, itu juga menambahkan jaminan pemesanan. akses non-atom, dan ke akses atom lainnya.(Secara teori, standar ISO C ++ tidak mengesampingkan optimasi waktu-kompilasi atom. Tetapi dalam praktiknya kompiler tidak melakukannya karena tidak ada cara untuk mengontrol kapan itu tidak akan terjadi. Ada beberapa kasus di mana bahkan
volatile atomic<T>
mungkin tidak menjadi cukup kontrol atas optimalisasi atomics jika kompiler melakukannya mengoptimalkan, jadi untuk sekarang compiler tidak. Lihat Mengapa tidak kompiler menggabungkan std berlebihan :: atom menulis? Perhatikan bahwa wg21 / p0062 merekomendasikan untuk tidak menggunakanvolatile atomic
kode saat ini untuk menjaga terhadap optimalisasi atom.)volatile
benar-benar berfungsi untuk ini pada CPU nyata (tapi masih tidak menggunakannya)bahkan dengan model memori yang tidak tertata dengan baik (non-x86) . Tapi tidak benar-benar menggunakannya, gunakan
atomic<T>
denganmo_relaxed
sebaliknya !! Inti dari bagian ini adalah untuk mengatasi kesalahpahaman tentang bagaimana CPU bekerja, bukan untuk membenarkanvolatile
. Jika Anda menulis kode tanpa kunci, Anda mungkin peduli dengan kinerja. Memahami cache dan biaya komunikasi antar thread biasanya penting untuk kinerja yang baik.CPU nyata memiliki cache yang koheren / memori bersama: setelah penyimpanan dari satu inti menjadi terlihat secara global, tidak ada inti lain yang dapat memuat nilai basi. (Lihat juga Pemrogram Mitos Percaya tentang Cache CPU yang berbicara tentang volatile Java, setara dengan C ++
atomic<T>
dengan urutan memori seq_cst.)Ketika saya mengatakan memuat , maksud saya instruksi asm yang mengakses memori. Itulah yang dijamin oleh
volatile
akses, dan bukan hal yang sama dengan konversi nilai-ke-nilai dari variabel C ++ non-atom / non-volatil. (misalnyalocal_tmp = flag
atauwhile(!flag)
).Satu-satunya hal yang perlu Anda kalahkan adalah optimasi waktu kompilasi yang tidak dimuat ulang sama sekali setelah pemeriksaan pertama. Setiap muatan + cek pada setiap iterasi sudah cukup, tanpa pemesanan. Tanpa sinkronisasi antara utas ini dan utas utama, tidak berarti untuk membicarakan kapan tepatnya toko terjadi, atau memesan wrt beban. operasi lain dalam loop. Hanya ketika itu terlihat oleh utas ini yang penting. Ketika Anda melihat flag exit_now diatur, Anda keluar. Latensi antar-inti pada x86 X86 khas dapat berupa sekitar 40ns antara inti fisik yang terpisah .
Secara teori: C ++ utas pada perangkat keras tanpa cache yang koheren
Saya tidak melihat cara ini bisa jauh efisien, hanya dengan ISO C ++ murni tanpa memerlukan programmer untuk melakukan flushes eksplisit dalam kode sumber.
Secara teori Anda bisa memiliki implementasi C ++ pada mesin yang tidak seperti ini, membutuhkan flushes eksplisit yang dihasilkan compiler untuk membuat sesuatu terlihat oleh utas lainnya pada core lainnya . (Atau untuk dibaca agar tidak menggunakan salinan yang mungkin basi). Standar C ++ tidak membuat ini mustahil, tetapi model memori C ++ dirancang agar efisien pada mesin memori bersama yang koheren. Misalnya standar C ++ bahkan berbicara tentang "baca-baca koherensi", "tulis-baca koherensi", dll. Satu catatan dalam standar bahkan menunjukkan koneksi ke perangkat keras:
Tidak ada mekanisme bagi
release
toko untuk hanya menyiram dirinya sendiri dan beberapa rentang alamat tertentu: ia harus menyinkronkan semuanya karena tidak akan tahu apa utas lain yang mungkin ingin dibaca jika mereka memperoleh beban melihat toko rilis ini (membentuk sebuah rilis-urutan yang menetapkan hubungan sebelum-terjadi di seluruh utas, menjamin bahwa operasi non-atom sebelumnya yang dilakukan oleh utas penulisan sekarang aman untuk dibaca. Kecuali jika itu menulis lebih lanjut kepada mereka setelah toko rilis ...) Atau kompiler akan memiliki menjadi sangat pintar untuk membuktikan bahwa hanya beberapa baris cache yang diperlukan pembilasan.Terkait: jawaban saya pada apakah mov + mfence aman di NUMA? masuk ke detail tentang tidak adanya sistem x86 tanpa memori bersama yang koheren. Terkait juga: Memuat dan menyimpan pemesanan ulang pada ARM untuk lebih lanjut tentang memuat / menyimpan ke lokasi yang sama .
Ada yang saya pikir cluster dengan non-koheren memori bersama, tapi mereka tidak mesin-sistem-gambar tunggal. Setiap domain koherensi menjalankan kernel terpisah, jadi Anda tidak dapat menjalankan utas program C ++ tunggal di atasnya. Alih-alih Anda menjalankan program yang terpisah dari program (masing-masing dengan ruang alamat mereka sendiri: pointer dalam satu contoh tidak valid di yang lain).
Untuk membuat mereka berkomunikasi satu sama lain melalui flushes eksplisit, Anda biasanya akan menggunakan MPI atau API lewat pesan untuk membuat program menentukan rentang alamat mana yang perlu dibilas.
Perangkat keras nyata tidak berjalan
std::thread
melintasi batas koherensi cache:Beberapa chip ARM asimetris ada, dengan ruang alamat fisik bersama tetapi tidak domain cache yang dapat dibagikan dalam. Jadi tidak koheren. (mis. utas komentar inti A8 dan Cortex-M3 seperti TI Sitara AM335x).
Tetapi kernel yang berbeda akan berjalan pada core tersebut, bukan gambar sistem tunggal yang dapat menjalankan thread di kedua core. Saya tidak mengetahui adanya implementasi C ++ yang menjalankan
std::thread
utas lintas inti CPU tanpa cache yang koheren.Khusus untuk ARM, GCC dan dentang menghasilkan kode dengan asumsi semua utas berjalan dalam domain yang dapat dibagikan dalam-dalam yang sama. Bahkan, kata manual ARMv7 ISA
Jadi memori bersama yang tidak koheren antara domain yang terpisah hanya merupakan hal untuk penggunaan khusus sistem yang eksplisit dari wilayah memori bersama untuk komunikasi antara berbagai proses di bawah kernel yang berbeda.
Lihat juga diskusi CoreCLR ini tentang kode-gen yang menggunakan
dmb ish
penghalangdmb sy
memori (Batin Shareable) vs. (Sistem) hambatan memori dalam kompiler itu.Saya membuat pernyataan bahwa tidak ada implementasi C ++ untuk ISA lainnya yang berjalan
std::thread
lintas core dengan cache yang tidak koheren. Saya tidak punya bukti bahwa tidak ada implementasi seperti itu, tetapi tampaknya sangat tidak mungkin. Kecuali jika Anda menargetkan potongan HW eksotis tertentu yang berfungsi seperti itu, pemikiran Anda tentang kinerja harus mengasumsikan koherensi cache mirip MESI antara semua utas. (atomic<T>
Meskipun demikian, lebih disukai digunakan dengan cara yang menjamin kebenaran!)Cache yang koheren membuatnya mudah
Tetapi pada sistem multi-core dengan cache yang koheren, mengimplementasikan rilis-store berarti memesan komit ke cache untuk toko thread ini, tidak melakukan pembilasan eksplisit. ( https://preshing.com/20120913/acquire-and-release-semantics/ dan https://preshing.com/20120710/memory-barriers-are-like-source-control-operations/ ). (Dan mendapatkan-memuat berarti memesan akses ke cache di inti lainnya).
Instruksi penghalang memori hanya memblokir beban dan / atau penyimpanan thread saat ini hingga buffer penyimpanan habis; itu selalu terjadi secepat mungkin sendiri. ( Apakah penghalang memori memastikan bahwa koherensi cache telah selesai? Alamat kesalahpahaman ini). Jadi jika Anda tidak perlu memesan, cukup tampilkan visibilitas di utas lain,
mo_relaxed
tidak masalah. (Begitu jugavolatile
, tapi jangan lakukan itu.)Lihat juga pemetaan C / C ++ 11 ke prosesor
Fakta menyenangkan: pada x86, setiap toko asm adalah toko rilis karena model memori x86 pada dasarnya adalah seq-cst plus buffer toko (dengan penerusan toko).
Re terkait semi: penyangga toko, visibilitas global, dan koherensi: C ++ 11 menjamin sangat sedikit. Kebanyakan ISA sebenarnya (kecuali PowerPC) menjamin bahwa semua utas dapat menyetujui urutan penampilan dua toko oleh dua utas lainnya. (Dalam terminologi model memori arsitektur-komputer formal, mereka "multi-copy atomic").
Kesalahpahaman lain adalah bahwa instruksi memori pagar diperlukan untuk menyiram buffer toko untuk core lain untuk melihat toko kami sama sekali . Sebenarnya buffer toko selalu berusaha untuk mengeringkan dirinya sendiri (komit ke cache L1d) secepat mungkin, jika tidak maka akan mengisi dan menghentikan eksekusi. Apa yang dilakukan penghalang / pagar penuh adalah menunda utas saat ini sampai buffer toko dikeringkan , sehingga muatan kami yang kemudian muncul dalam urutan global setelah toko sebelumnya.
(Model memori asm x86 yang tertata sangat berarti bahwa
volatile
pada x86 mungkin berakhir memberi Anda lebih dekatmo_acq_rel
, kecuali bahwa penyusunan ulang waktu kompilasi dengan variabel non-atom masih dapat terjadi. Tetapi sebagian besar non-x86 memiliki model memori yang dipesan dengan lemah sehinggavolatile
danrelaxed
hampir sama lemah karenamo_relaxed
memungkinkan.)sumber
atomic
dapat menyebabkan utas berbeda memiliki nilai berbeda untuk variabel yang sama dalam cache . /Telapak tangan. Dalam cache, tidak, di register CPU ya (dengan variabel non-atom); CPU menggunakan cache yang koheren. Saya berharap pertanyaan lain tentang SO tidak penuh dengan penjelasan untukatomic
penyebaran kesalahpahaman tentang cara kerja CPU. (Karena itu adalah hal yang berguna untuk dipahami karena alasan kinerja, dan juga membantu menjelaskan mengapa aturan atom ISO C ++ ditulis sebagaimana adanya.)Suatu kali seorang pewawancara yang juga percaya bahwa volatile tidak berguna berdebat dengan saya bahwa Optimasi tidak akan menyebabkan masalah dan merujuk ke core yang berbeda memiliki garis cache yang terpisah dan semua itu (tidak benar-benar mengerti apa yang ia maksudkan dengan tepat). Tetapi potongan kode ini ketika dikompilasi dengan -O3 pada g ++ (g ++ -O3 thread.cpp -lpthread), ini menunjukkan perilaku yang tidak terdefinisi. Pada dasarnya jika nilai ditetapkan sebelum memeriksa sementara itu berfungsi dengan baik dan jika tidak itu menjadi loop tanpa repot-repot untuk mengambil nilai (yang sebenarnya diubah oleh utas lainnya). Pada dasarnya saya percaya nilai checkValue hanya akan diambil sekali ke dalam register dan tidak pernah diperiksa lagi di bawah tingkat optimasi tertinggi. Jika disetel ke true sebelum pengambilan, itu berfungsi dengan baik dan jika tidak itu akan menjadi loop. Harap perbaiki saya jika saya salah.
sumber
volatile
? Ya, kode ini adalah UB - tetapi juga UBvolatile
.Anda perlu stabil dan mungkin mengunci.
volatile memberi tahu pengoptimal bahwa nilainya dapat berubah secara tidak sinkron
akan membaca flag setiap kali di loop.
Jika Anda mematikan pengoptimalan atau membuat setiap variabel tidak stabil, program akan berperilaku sama tetapi lebih lambat. volatile hanya berarti 'Saya tahu Anda mungkin baru saja membacanya dan tahu apa yang dikatakannya, tetapi jika saya mengatakan membacanya maka bacalah.
Mengunci adalah bagian dari program. Jadi, omong-omong, jika Anda menerapkan semafor maka di antara hal-hal lain itu pasti tidak stabil. (Jangan mencobanya, itu sulit, mungkin akan membutuhkan assembler kecil atau barang-barang atom baru, dan itu sudah dilakukan.)
sumber
volatile
tidak terlalu berguna bahkan dalam kasus ini. Tapi menunggu yang sibuk adalah teknik yang kadang-kadang berguna.volatile
"no reordering". Anda berharap itu berarti bahwa toko akan terlihat secara global (ke utas lainnya) dalam urutan program. Itulah yang terjadiatomic<T>
denganmemory_order_release
atauseq_cst
memberi Anda. Tetapivolatile
hanya memberi Anda jaminan tidak ada penyusunan ulang waktu kompilasi : setiap akses akan muncul dalam asm dalam urutan program. Berguna untuk driver perangkat. Dan bermanfaat untuk interaksi dengan interrupt handler, debugger, atau signal handler pada core / thread saat ini, tetapi tidak untuk berinteraksi dengan core lainnya.volatile
dalam praktiknya cukup untuk memeriksakeep_running
flag seperti yang Anda lakukan di sini: CPU nyata selalu memiliki cache yang koheren yang tidak memerlukan pembilasan manual. Tapi tidak ada alasan untuk merekomendasikanvolatile
lebihatomic<T>
denganmo_relaxed
; Anda akan mendapatkan asm yang sama.