簡體   English   中英

Visual C ++ x86上的volatile變量和原子操作

[英]volatile variable and atomic operations on Visual C++ x86

普通加載在x86上獲得了語義,普通商店具有發布語義,但編譯器仍然可以重新排序指令。 雖然圍欄和鎖定指令(鎖定的xchg,鎖定的cmpxchg)會阻止硬件和編譯器重新排序,但仍需要普通的加載和存儲來保護編譯器障礙。 Visual C ++提供了_ReadWriterBarrier()函數,它可以防止編譯器重新排序,同樣C ++提供volatile關鍵字也是出於同樣的原因。 我寫這些信息只是為了確保我把一切都弄好。 所以上面寫的都是真的,有沒有理由將其標記為將在_ReadWriteBarrier()保護的函數中使用的volatile變量?

例如:

int load(int& var)
{
    _ReadWriteBarrier();
    T value = var;
    _ReadWriteBarrier();
    return value;
}

使變量非易失性是否安全? 據我所知,因為函數受到保護,內部編譯器無法進行重新排序。 另一方面,Visual C ++為volatile變量提供了特殊的行為(不同於標准的變量),它使得volatile可以讀取和寫入原子載荷和存儲,但我的目標是x86,而且x86上的普通加載和存儲應該是原子的無論如何,對嗎?

提前致謝。

Volatile也提供易失性關鍵字。 “volatile”通常用於嵌入式系統,特別是當變量的值可能隨時發生變化時 - 代碼不采取任何操作 - 三種常見方案包括從內存映射外設寄存器讀取或全局變量修改中斷服務程序或多線程程序中的程序。

因此,最后一種情況是volatile可以被認為與_ReadWriteBarrier類似。

_ReadWriteBarrier不是函數 - _ReadWriteBarrier不插入任何附加指令,並且它不會阻止CPU重新排列讀取和寫入 - 它只會阻止編譯器重新排列它們。 _ReadWriteBarrier是為了防止編譯器重新排序。

MemoryBarrier是為了防止CPU重新排序!

編譯器通常會重新排列指令... C ++不包含對多線程程序的內置支持,因此編譯器假定在重新排序代碼時代碼是單線程的。 使用MSVC在代碼中使用_ReadWriteBarrier,這樣編譯器就不會在其上移動讀寫操作。

有關這些主題的更多詳細討論,請查看此鏈接http://msdn.microsoft.com/en-us/library/ee418650(v=vs.85).aspx

關於你的代碼片段 - 你不必使用ReadWriteBarrier作為SYNC原語 - 第一次調用_ReadWriteBarrier是沒有必要的。

使用ReadWriteBarrier時,您不必使用volatile

你寫道“它使得易失性讀寫原子載荷和存儲” - 我不認為可以這么說,原子性和波動性是不同的。 原子操作被認為是不可分割的 - ... http://www.yoda.arachsys.com/csharp/threads/volatility.shtml

注意:我不是這個主題的專家,我的一些陳述 “我在互聯網上聽到的”,但我認為我仍然清除了一些誤解。

[編輯]一般情況下,我會依賴於平台特定的,例如x86原子讀取和缺少OOOX僅在隔離的本地優化中, #ifdef檢查目標平台,理想情況下伴隨着#else的便攜式解決方案路徑。

值得關注的事情

  • 讀/寫操作的原子性
  • 由於編譯器優化而重新排序(這包括由於簡單的寄存器緩存而被另一個線程看到的不同順序)
  • CPU中的亂序執行

可能存在誤解

1. 據我所知,因為函數受到保護,內部編譯器無法進行重新排序。
[編輯]澄清_ReadWriteBarrier_ReadWriteBarrier提供了對指令重新排序的保護,但是,你必須超越函數的范圍。 _ReadWriteBarrier已經在VS 2010中修復了,早期版本可能會被破壞(取決於他們實際做的優化)。

優化不僅限於功能。 有多種機制(自動內聯,鏈接時間代碼生成)跨越函數甚至編譯單元(並且可以提供比小范圍寄存器緩存更重要的優化)。

2. Visual C ++ [...]使得易失性讀寫原子載荷和存儲,
你是在哪里找到那個東西的。 MSDN表示,超出標准,會在讀寫時產生內存障礙,無法保證原子讀取。

[編輯]請注意,C#,Java,Delphi等具有不同的內存mdoels,可能會有不同的保證。

3. 無論如何,普通的加載和存儲應該是x86上的原子,對吧?
不,他們不是。 未對齊的讀取不是原子的。 如果它們完全一致,它們恰好是原子的 - 除非它是孤立的並且易於交換,否則我不會依賴它。 否則,你的“x86簡化”將成為該目標的鎖定。

[編輯]未對齊的讀取發生:

char * c = new char[sizeof(int)+1];
load(*(int *)c);      // allowed by standard to be unaligned
load(*(int *)(c+1));  // unaligned with most allocators

#pragma pack(push,1)
struct 
{
   char c;
   int  i;
} foo;
load(foo.i);         // caller said so
#pragma pack(pop)

如果您記得參數必須對齊,並且您控制所有代碼,那么這當然是學術性的。 我不會再寫這樣的代碼,因為我經常被過去的懶惰所困擾。

4. 普通加載在x86上獲取語義,普通商店具有釋放語義
不.X86處理器不使用無序執行(或者更確切地說,沒有可見的OOOX - 我認為),但這並不能阻止優化器重新排序指令。

5. _ReadBarrier / _WriteBarrier / _ReadWriteBarrier做了所有的魔術它們沒有 - 它們只是阻止優化器的重新排序。 MSDN最終對VS2010 發出嚴重的警告 ,但這些信息顯然也適用於以前的版本


現在,問你的問題。

我假設片段的目的是傳遞任何變量N,並加載它(原子地?)直接的選擇是互鎖讀取或(在Visual C ++ 2005及更高版本上)易失性讀取。

否則,在讀取之前,你需要為編譯器和CPU提供一個屏障,在VC ++中,這將是:

int load(int& var)
{   
  // force Optimizer to complete all memory writes:
  // (Note that this had issues before VC++ 2010)
   _WriteBarrier();    

  // force CPU to settle all pending read/writes, and not to start new ones:
   MemoryBarrier();

   // now, read.
   int value = var;    
   return value;
}

Noe, _WriteBarrier在MSDN中有第二個警告:*在以前版本的Visual C ++編譯器中,_ReadWriteBarrier和_WriteBarrier函數僅在本地強制實施,並不影響調用樹的功能。 現在,這些函數一直強制執行調用樹。*


希望這是正確的。 如果我錯了,請糾正我。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM