簡體   English   中英

C ++ 11中的數據競爭,UB和計數器

[英]Data races, UB, and counters in C++11

以下模式在許多軟件中很常見,這些軟件想告訴用戶它做了多少事情:

int num_times_done_it; // global

void doit() {
  ++num_times_done_it;
  // do something
}

void report_stats() {
  printf("called doit %i times\n", num_times_done_it);
  // and probably some other stuff too
}

不幸的是,如果多個線程可以調用doit沒有某種形式的同步,並發讀-修改-寫入num_times_done_it可能是一個數據的比賽,因此整個程序的行為將是不確定的。 此外,如果report_stats可以同時使用稱為doit缺席任何同步,還有線程修改之間的另一數據競爭num_times_done_it和線程報告其價值。

通常,程序員只想要盡可能少的開銷來調用doit的次數。

(如果你認為這個例子是微不足道的, Hogwild!比使用基本上這個技巧的數據無競爭隨機梯度下降獲得了顯着的速度優勢。而且,我相信Hotspot JVM正是這種無人看守,多線程訪問共享計數器對於方法調用計數---雖然它是明確的,因為它生成匯編代碼而不是C ++ 11。)

明顯的非解決方案:

  • 原子論,我所知道的任何內存順序,在這里“盡可能少的開銷”失敗(原子增量可能比普通增量貴得多),而在“大多數正確”(通過完全正確)過度交付。
  • 我不相信在混合中拋出volatile會使數據num_times_done_it正常,所以用volatile int num_times_done_it替換num_times_done_it的聲明並不能解決任何問題。
  • 有有每個線程獨立的櫃台,把它們加起來在尷尬的解決方案report_stats ,但這並不解決之間的數據爭doitreport_stats 此外,它很混亂,它假設更新是關聯的,並不真正適合Hogwild!的用法。

是否有可能在一個非平凡的多線程C ++ 11程序中實現具有良好定義語義的調用計數器,而無需某種形式的同步?

編輯 :似乎我們可以使用memory_order_relaxed以稍微間接的方式執行此memory_order_relaxed

atomic<int> num_times_done_it;
void doit() {
  num_times_done_it.store(1 + num_times_done_it.load(memory_order_relaxed),
                          memory_order_relaxed);
  // as before
}

但是, gcc 4.8.2在x86_64(帶-O3)上生成此代碼:

   0:   8b 05 00 00 00 00       mov    0x0(%rip),%eax
   6:   83 c0 01                add    $0x1,%eax
   9:   89 05 00 00 00 00       mov    %eax,0x0(%rip)

clang 3.4在x86_64上生成此代碼(再次使用-O3):

   0:   8b 05 00 00 00 00       mov    0x0(%rip),%eax
   6:   ff c0                   inc    %eax
   8:   89 05 00 00 00 00       mov    %eax,0x0(%rip)

我對x86-TSO的理解是這兩個代碼序列都禁止中斷和有趣的頁面保護標志,完全等同於單指令存儲器inc和由簡單代碼生成的單指令存儲器add memory_order_relaxed使用是否構成數據競爭?

分別計算每個線程,並在線程加入后總結。 對於中間結果,您也可以在兩者之間進行總結,但結果可能會關閉。 這種模式也更快。 您可以將它嵌入到線程的基本幫助器類中,這樣如果您經常使用它,就可以使用它。

並且 - 取決於編譯器和平台,原子並不那么昂貴(參見Herb Sutters“原子武器”談話http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter -atomic-Weapons-1-of-2 )但在你的情況下,它會產生緩存問題,所以這是不可取的。

似乎memory_order_relaxed技巧是正確的方法。

英特爾的Dmitry Vyukov撰寫的這篇博文首先回答了我的問題,並繼續列出memory_order_relaxed storeload為正確的選擇。

我仍然不確定這是否真的好; 特別是, N3710讓我懷疑我是否曾首先理解memory_order_relaxed

暫無
暫無

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

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