簡體   English   中英

重新排序操作和無鎖數據結構

[英]Reordering of operations and lock-free data structures

假設我們有一個Container維護一組int值,以及每個值的標志,指示該值是否有效。 無效值被認為是INT_MAX 最初,所有值均無效。 首次訪問值時,將其設置為INT_MAX並將其標志設置為有效。

struct Container {
  int& operator[](int i) {
    if (!isValid[i]) {
      values[i] = INT_MAX; // (*)
      isValid[i] = true;   // (**)
    }
    return values[i];
  }
  std::vector<int> values;
  std::vector<bool> isValid;
};

現在,另一個線程同時讀取容器值:

// This member is allowed to overestimate value i, but it must not underestimate it.
int Container::get(int i) {
  return isValid[i] ? values[i] : INT_MAX;
}

這是完全有效的代碼,但是至關重要的是按給定的順序執行(*)(**)

  1. 在這種情況下,標准是否保證按給定順序執行行? 至少從單線程的角度來看,行可以互換,不是嗎?
  2. 如果沒有,最有效的方式來確保他們的訂單? 這是高性能的代碼,所以我不能沒有-O3並且不想使用volatile

這里沒有同步。 如果您從一個線程訪問這些值,然后從另一個線程更改它們,則會出現未定義的行為。 您可能需要對所有訪問進行鎖定,在這種情況下,情況會很好。 否則,您將需要使所有std::vector元素成為atomic<T>並且可以使用適當的可見性參數來控制值的可見性。

對於同步,特別是原子操作的作用,似乎有一個誤解:它們的目的是使代碼更快! 這可能看起來與直覺相反,因此在此進行了解釋: 非原子操作應與可能的速度一樣快,並且故意不能保證它們如何精確地訪問內存。 只要編譯器和執行系統產生正確的結果,編譯器iand系統就可以自由地執行他們需要或想要做的任何事情。 為了獲得良好的性能,假定不存在不同線程之間的交互。

但是,在並發系統中,線程之間存在交互。 這是原子操作進入階段的地方:它們允許確切說明所需的必要同步 因此,它們允許告訴編譯器必須遵循的最小約束,以使線程正確操作無誤。 編譯器將使用這些指示符來生成最佳代碼,以實現指定的目標。 該代碼可能與不使用任何同步的代碼相同,盡管在實踐中通常還需要防止CPU重新排序操作。 結果,正確使用同步會導致最高效的代碼,而只有絕對必要的開銷。

棘手的部分是在某種程度上找到需要哪些同步並將其最小化。 完全沒有任何內容將使編譯器和CPU自由地對操作進行重新排序,並且將無法工作。

由於提到的問題volatile請注意, volatile與並發完全無關! volatile的主要目的是通知系統地址指向其訪問可能有副作用的內存。 首先,它用於使內存映射的I / O或硬件控制可訪問。 死於副作用的可能是C ++定義程序語義的兩個方面之一(另一個是使用標准庫I / O設施的I / O)。

暫無
暫無

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

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