Bu gerçekten bir refakat değil ; bir işlevi aynı iş parçacığında (veya farklı iş parçacıklarında) iki kez çalıştırmıyorsunuz . Bunu özyineleme yoluyla veya geçerli işlevin adresini geri arama işlevi işaretçisi arg olarak başka bir işleve geçirerek alabilirsiniz. (Ve güvensiz olmaz çünkü senkron olur).
Bu, bir sinyal işleyici ve ana iş parçacığı arasında sadece düz vanilya veri yarışı UB'dir (Tanımsız Davranış): sadece sig_atomic_t
bunun için güvenli olduğu garanti edilir . 8 baytlık bir nesnenin x86-64 üzerinde bir komutla yüklenebileceği veya saklanabileceği gibi, derleyici de bu grubu seçerken diğerleri işe yarayabilir. (@ İcarus'un cevabının gösterdiği gibi).
Bkz. MCU programlama - döngü sırasında C ++ O2 optimizasyonu kesiliyor - tek çekirdekli bir mikro denetleyicideki bir kesme işleyicisi temel olarak tek bir dişli programdaki bir sinyal işleyici ile aynı şeydir. Bu durumda UB'nin sonucu, bir yükün bir döngüden kaldırılmasıdır.
Veri yarışı UB nedeniyle gerçekte yırtılma test durumunuz muhtemelen 32 bit modunda veya yapı üyelerini ayrı ayrı yükleyen eski bir derleyici derleyicisi ile geliştirildi / test edildi.
Sizin durumunuzda, derleyici depoları sonsuz döngüden optimize edebilir, çünkü hiçbir UB içermeyen program bunları gözlemleyemez. data
değil _Atomic
ya davolatile
, ve döngüde başka hiçbir yan etkisi yoktur. Yani herhangi bir okuyucunun bu yazıcıyla senkronize edebilmesi mümkün değil. Bu aslında optimizasyon etkinken derlerseniz gerçekleşir ( Godbolt ana alt kısmında boş bir döngü gösterir). Ayrıca yapıyı ikiye değiştirdim long long
ve gcc movdqa
, döngüden önce tek bir 16 baytlık depo kullanıyor . (Bu garanti atomik değildir , ancak pratikte neredeyse tüm CPU'larda, hizalı olduğu varsayılarak veya Intel'de sadece bir önbellek sınırını geçmez. X86'da neden doğal olarak hizalanmış bir değişken atomda tamsayı ataması var? )
Dolayısıyla optimizasyon etkinken derleme de testinizi kıracak ve size her seferinde aynı değeri gösterecektir. C taşınabilir bir montaj dili değildir.
volatile struct two_int
ayrıca derleyiciyi onları optimize etmemeye zorlar , ancak tüm yapıyı atomik olarak yüklemeye / depolamaya zorlamaz. (Bu olmaz durdurmak Not. Gerçi ya, bunu yaparken onu) volatile
yok değil veri yarış UB önlemek, ama pratikte arası iplik iletişim için yeterli ve insanlar (satır içi asm) ile birlikte elle sarılmış atomics inşa nasıldı normal CPU mimarileri için C11 / C ++ 11'den önce. Onlar ediyoruz önbellek tutarlı öylesine volatile
olduğu için çoğunlukla benzer pratikte _Atomic
ilememory_order_relaxed
saf yük ve saf-mağaza için, türleri için kullanılan eğer yırtılması alamadım bu yüzden derleyici tek talimat kullanacağı yeterince dar. Ve tabi kivolatile
ISO C standardından, aynı _Atomic
modayı kullanan ve mo_relaxed ile derleyen kod yazma garantisine sahip değildir .
Yaptığın bir işlevi olsaydı global_var++;
, bir de int
ya long long
ana çalıştırmak olduğunu ve bir sinyal işleyici gelen uyumsuz, yani veri yarış UB oluşturmak için kullanımı yeniden entrancy bir yolunu olacaktır.
Nasıl derlendiğine bağlı olarak (bellek varış yeri inc veya ekleme veya ayrı yükleme / inc / depolama için), aynı iş parçasındaki sinyal işleyicilerine göre atomik veya değil olacaktır. Bkz . 'İnt num' için num ++ atomik olabilir mi? x86 ve C ++ 'da atomisite hakkında daha fazla bilgi için. (C11 stdatomic.h
ve _Atomic
özniteliği C ++ 11'in std::atomic<T>
şablonuna eşdeğer işlevsellik sağlar )
Bir komutun ortasında bir kesinti veya başka bir istisna olamaz, bu nedenle bellek-hedef ekleme atomik wrt'tur. bağlam anahtarları tek çekirdekli bir CPU üzerinde. Yalnızca (önbellek uyumlu) bir DMA yazarı, tek çekirdekli bir CPU'da öneki add [mem], 1
olmayan bir lock
artıştan "adım atabilir" . Başka bir iş parçacığının üzerinde çalışabileceği başka hiçbir çekirdek yoktur.
Bu, sinyallere benzer: sinyali işleyen ipliğin normal yürütülmesi yerine bir sinyal işleyici çalışır , bu nedenle bir komutun ortasında işlenemez.