Itu tidak benar-benar ulang entrancy ; Anda tidak menjalankan fungsi dua kali di utas yang sama (atau di utas berbeda). Anda bisa mendapatkannya melalui rekursi atau mengirimkan alamat fungsi saat ini sebagai fungsi-pointer callback ke fungsi lain. (Dan itu tidak akan tidak aman karena akan sinkron).
Ini hanyalah perlombaan data vanila UB (Undefined Behavior) antara pengatur sinyal dan utas utama: hanya sig_atomic_t
dijamin aman untuk ini . Orang lain mungkin bekerja, seperti dalam kasus Anda di mana objek 8-byte dapat dimuat atau disimpan dengan satu instruksi pada x86-64, dan kompiler kebetulan memilih asm itu. (Seperti yang ditunjukkan oleh jawaban icarus).
Lihat pemrograman MCU - optimasi C ++ O2 rusak sementara loop - pengendali interupsi pada mikrokontroler inti tunggal pada dasarnya adalah hal yang sama dengan pengendali sinyal dalam program berulir tunggal. Dalam hal ini hasil dari UB adalah bahwa beban diangkat dari loop.
Test-case robek Anda benar-benar terjadi karena perlombaan data UB mungkin dikembangkan / diuji dalam mode 32-bit, atau dengan kompiler dumber yang lebih lama yang memuat anggota struct secara terpisah.
Dalam kasus Anda, kompiler dapat mengoptimalkan toko keluar dari infinite loop karena tidak ada program bebas UB yang bisa mengamatinya. data
bukan _Atomic
atauvolatile
, dan tidak ada efek samping lain dalam loop. Jadi tidak mungkin pembaca dapat melakukan sinkronisasi dengan penulis ini. Ini sebenarnya terjadi jika Anda mengkompilasi dengan optimasi diaktifkan ( Godbolt menunjukkan loop kosong di bagian bawah utama). Saya juga mengubah struct menjadi dua long long
, dan gcc menggunakan toko movdqa
16-byte tunggal sebelum loop. (Ini tidak dijamin atom, tetapi dalam praktiknya di hampir semua CPU, dengan asumsi itu disejajarkan, atau pada Intel tidak hanya melewati batas cache-line. Mengapa penugasan integer pada atom variabel sejajar alami pada x86? )
Jadi kompilasi dengan optimasi yang diaktifkan juga akan merusak pengujian Anda, dan menunjukkan nilai yang sama setiap kali. C bukan bahasa rakitan portabel.
volatile struct two_int
juga akan memaksa kompiler untuk tidak mengoptimalkannya, tetapi tidak akan memaksanya untuk memuat / menyimpan seluruh struktur secara atom. (Ini tidak akan berhenti dari melakukannya baik, meskipun.) Perhatikan bahwa volatile
tidak tidak menghindari UB data ras, tetapi dalam prakteknya itu cukup untuk komunikasi antar-benang dan bagaimana orang-orang membangun teori atom linting tangan (bersama dengan asm inline) sebelum C11 / C ++ 11, untuk arsitektur CPU normal. Mereka cache-koheren sehingga volatile
adalah dalam prakteknya sebagian besar mirip dengan _Atomic
denganmemory_order_relaxed
untuk murni-beban dan murni-toko, jika digunakan untuk jenis mempersempit cukup bahwa kompiler akan menggunakan instruksi tunggal sehingga Anda tidak mendapatkan robek. Dan tentu sajavolatile
tidak memiliki jaminan dari standar ISO C vs. kode penulisan yang mengkompilasi dengan asm yang sama menggunakan _Atomic
dan mo_relaxed.
Jika Anda memiliki fungsi yang dilakukan global_var++;
pada int
atau long long
yang Anda jalankan dari main dan secara tidak sinkron dari penangan sinyal, itu akan menjadi cara untuk menggunakan re-entrancy untuk membuat data-race UB.
Bergantung pada bagaimana itu dikompilasi (ke inc tujuan memori atau menambah, atau untuk memisahkan beban / inc / store) itu akan atomik atau tidak sehubungan dengan penangan sinyal di utas yang sama. Lihat Bisakah num ++ menjadi atom untuk 'int num'? untuk lebih lanjut tentang atomicity pada x86 dan di C ++. (C11 stdatomic.h
dan _Atomic
atributnya menyediakan fungsionalitas yang setara dengan std::atomic<T>
templat C ++ 11 )
Pengecualian atau interupsi lainnya tidak dapat terjadi di tengah instruksi, jadi add tujuan memori adalah atomic wrt. konteks mengaktifkan CPU single-core. Hanya penulis DMA (koheren cache) yang dapat "menginjak" peningkatan dari add [mem], 1
tanpa lock
awalan pada CPU single-core. Tidak ada core lain yang dapat digunakan untuk menjalankan thread lain.
Jadi mirip dengan kasus sinyal: handler sinyal berjalan bukannya eksekusi normal dari benang yang menangani sinyal, sehingga tidak dapat ditangani di tengah satu instruksi.