[英]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;
}
這是完全有效的代碼,但是至關重要的是按給定的順序執行(*)
和(**)
。
-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.