Saya mengerti itu std::atomic<>
adalah benda atom. Tetapi atom sampai sejauh mana? Menurut pemahaman saya, operasi bisa bersifat atom. Apa sebenarnya yang dimaksud dengan membuat objek atom? Misalnya jika ada dua utas yang secara bersamaan mengeksekusi kode berikut:
a = a + 12;
Lalu apakah seluruh operasi (katakanlah add_twelve_to(int)
) atomik? Atau apakah ada perubahan pada atom variabel (begitu operator=()
)?
c++
multithreading
c++11
atomic
curiousguy
sumber
sumber
a.fetch_add(12)
jika Anda menginginkan RMW atom.std::atomic
memungkinkan perpustakaan standar memutuskan apa yang diperlukan untuk mencapai atomicity.std::atomic<T>
adalah jenis yang memungkinkan untuk operasi atom. Itu tidak secara ajaib membuat hidup Anda lebih baik, Anda masih harus tahu apa yang ingin Anda lakukan dengannya. Ini untuk kasus penggunaan yang sangat spesifik, dan penggunaan operasi atom (pada objek) umumnya sangat halus dan perlu dipikirkan dari perspektif non-lokal. Jadi, kecuali Anda sudah tahu itu dan mengapa Anda menginginkan operasi atom, tipenya mungkin tidak banyak berguna bagi Anda.Jawaban:
Setiap instantiasi dan spesialisasi penuh dari std :: atomic <> mewakili tipe yang dapat digunakan secara bersamaan oleh thread yang berbeda (instansinya), tanpa memunculkan perilaku yang tidak terdefinisi:
std::atomic<>
membungkus operasi yang, dalam pra-C ++ 11 kali, harus dilakukan menggunakan (misalnya) fungsi yang saling terkait dengan MSVC atau atom bultin dalam kasus GCC.Selain itu,
std::atomic<>
memberi Anda lebih banyak kontrol dengan mengizinkan berbagai pesanan memori yang menentukan sinkronisasi dan batasan pemesanan. Jika Anda ingin membaca lebih lanjut tentang atom C ++ 11 dan model memori, tautan ini mungkin berguna:Perhatikan bahwa, untuk kasus penggunaan umum, Anda mungkin akan menggunakan operator aritmatika yang kelebihan beban atau rangkaian lainnya :
Karena sintaksis operator tidak memungkinkan Anda menentukan urutan memori, operasi ini akan dilakukan dengan
std::memory_order_seq_cst
, karena ini adalah urutan default untuk semua operasi atom dalam C ++ 11. Ini menjamin konsistensi berurutan (total global order) antara semua operasi atom.Namun, dalam beberapa kasus, ini mungkin tidak diperlukan (dan tidak ada yang gratis), jadi Anda mungkin ingin menggunakan formulir yang lebih eksplisit:
Sekarang, contoh Anda:
tidak akan mengevaluasi ke op atom tunggal: itu akan menghasilkan
a.load()
(yang merupakan atom itu sendiri), kemudian penambahan antara nilai ini dan12
dana.store()
(juga atom) dari hasil akhir. Seperti yang saya catat sebelumnya,std::memory_order_seq_cst
akan digunakan di sini.Namun, jika Anda menulis
a += 12
, itu akan menjadi operasi atom (seperti yang saya sebutkan sebelumnya) dan kira-kira setara dengana.fetch_add(12, std::memory_order_seq_cst)
.Adapun komentar Anda:
Pernyataan Anda hanya berlaku untuk arsitektur yang memberikan jaminan atomicity untuk toko dan / atau muatan. Ada arsitektur yang tidak melakukan ini. Selain itu, biasanya diperlukan bahwa operasi harus dilakukan pada alamat word- / dword-aligned menjadi atom
std::atomic<>
adalah sesuatu yang dijamin atom pada setiap platform, tanpa persyaratan tambahan. Selain itu, Anda dapat menulis kode seperti ini:Perhatikan bahwa kondisi pernyataan akan selalu benar (dan karenanya, tidak akan pernah memicu), sehingga Anda selalu dapat yakin bahwa data siap setelah
while
loop keluar. Itu karena:store()
untuk flag dilakukan setelahsharedData
diatur (kami menganggap bahwagenerateData()
selalu mengembalikan sesuatu yang bermanfaat, khususnya, tidak pernah kembaliNULL
) dan menggunakanstd::memory_order_release
urutan:sharedData
digunakan setelahwhile
loop keluar, dan dengan demikian setelahload()
dari flag akan mengembalikan nilai bukan nol.load()
menggunakanstd::memory_order_acquire
pesanan:Ini memberi Anda kontrol tepat atas sinkronisasi dan memungkinkan Anda menentukan secara eksplisit bagaimana kode Anda mungkin / mungkin tidak / akan / tidak akan berperilaku. Ini tidak akan mungkin jika hanya jaminannya adalah atomisitas itu sendiri. Terutama ketika datang ke model sinkronisasi yang sangat menarik seperti pemesanan rilis-konsumsi .
sumber
int
s?std::atomic
(std::memory_order
) berfungsi persis dengan tujuan membatasi pemesanan ulang yang diizinkan terjadi.Itu masalah perspektif ... Anda tidak dapat menerapkannya pada objek yang berubah-ubah dan menjadikan operasinya menjadi atom, tetapi spesialisasi yang disediakan untuk sebagian besar tipe dan pointer integral dapat digunakan.
std::atomic<>
tidak (ekspresi penggunaan template untuk) menyederhanakan ini untuk operasi atom tunggal, sebaliknyaoperator T() const volatile noexcept
anggota melakukan sebuah atomload()
daria
, maka dua belas ditambahkan, danoperator=(T t) noexcept
melakukanstore(t)
.sumber
int
tidak dengan mudah memastikan perubahan terlihat dari utas lainnya, juga tidak membacanya memastikan Anda melihat perubahan utas lainnya, dan beberapa hal sepertimy_int += 3
tidak dijamin dilakukan secara atomis kecuali Anda menggunakannyastd::atomic<>
- mereka mungkin melibatkan ambil, lalu tambahkan, lalu simpan urutan, di mana beberapa utas lain yang mencoba memperbarui nilai yang sama mungkin masuk setelah pengambilan dan sebelum toko, dan klik pembaruan utas Anda.std::atomic
ada karena banyak ISA memiliki dukungan perangkat keras langsung untuk ituApa yang dikatakan standar C ++
std::atomic
telah dianalisis dalam jawaban lain.Jadi sekarang mari kita lihat apa yang
std::atomic
dikompilasi untuk mendapatkan wawasan yang berbeda.Hasil utama dari percobaan ini adalah bahwa CPU modern memiliki dukungan langsung untuk operasi integer atom, misalnya awalan LOCK di x86, dan
std::atomic
pada dasarnya ada sebagai antarmuka portabel untuk instruksi tersebut: Apa arti instruksi "kunci" dalam perakitan x86? Di aarch64, LDADD akan digunakan.Dukungan ini memungkinkan untuk alternatif yang lebih cepat untuk metode yang lebih umum seperti
std::mutex
, yang dapat membuat bagian multi-instruksi atom menjadi lebih kompleks, dengan biaya lebih lambat daripadastd::atomic
karenastd::mutex
membuatfutex
panggilan sistem di Linux, yang jauh lebih lambat daripada instruksi pengguna lahan yang dipancarkan olehstd::atomic
, lihat juga: Apakah std :: mutex membuat pagar?Mari kita pertimbangkan program multi-utas berikut ini yang meningkatkan variabel global di beberapa utas, dengan mekanisme sinkronisasi berbeda tergantung pada yang menentukan preprosesor digunakan.
main.cpp
GitHub hulu .
Kompilasi, jalankan, dan bongkar:
Output kondisi ras yang sangat "salah" untuk
main_fail.out
:dan output "benar" deterministik dari yang lain:
Pembongkaran dari
main_fail.out
:Pembongkaran dari
main_std_atomic.out
:Pembongkaran dari
main_lock.out
:Kesimpulan:
versi non-atom menyimpan global ke register, dan menambah register.
Oleh karena itu, pada akhirnya, sangat mungkin empat penulisan terjadi kembali ke global dengan nilai "salah" yang sama
100000
.std::atomic
kompilasi kelock addq
. Awalan LOCK membuat yang berikutinc
mengambil, memodifikasi, dan memperbarui memori secara atom.prefiks LOCK inline assembly eksplisit kami mengkompilasi ke hal yang hampir sama dengan
std::atomic
, kecuali bahwa kamiinc
digunakan sebagai gantiadd
. Tidak yakin mengapa GCC memilihadd
, mengingat INC kami menghasilkan decoding 1 byte lebih kecil.ARMv8 dapat menggunakan LDAXR + STLXR atau LDADD di CPU yang lebih baru: Bagaimana cara memulai utas di C biasa?
Diuji di Ubuntu 19.10 AMD64, GCC 9.2.1, Lenovo ThinkPad P51.
sumber