我們對資料競争的定義相當嚴格:必須以确切方式去執行原始、未經轉換的程式中并行的沖突操作。引入有害的資料競争将給編譯器帶來無法“打斷”程式的負擔。
舉例來說,考慮如下程式,x和y是兩個普通變量,初始化值均為0:
線程 1
線程2
if (x != 0)
y = 1;
y = 2;
這個例子并不會導緻資料競争的出現:x變量永遠都是0,是以線程1的 y = 1語句永遠都不會執行。
對于下面這個例子也同樣如此:
if (y != 0)
x = 1;
跟上面例子一樣,這兩個線程同樣不存在資料競争的問題。
(這兩個例子如果按照posix标準來看并不那麼清晰,是否存在資料競争會有一些争議。我們堅持認為不存在的理由就是考慮到,資料競争隻有在順序一緻性的執行過程中才會發生)
(譯者注:posix強調的是,如果上述程式的執行結果依賴于兩個線程的交叉執行順序,則存在資料競争)
然而,如果我們稍微改變一下第一個例子,就會導緻資料競争的出現了:
y = ((x != 0)? 1 : y)
盡管在單線程運作時,上述兩個例子的線程1 是等價的。但在多線程運作時就不一樣了,在新版本的例子中y的值需要依情況而定。在特定情況下,y的值有可能線上程2執行的同時被改寫。由于線程1的代碼往往會被分割為幾步執行,這種資料競争隐含着很容易出現的問題,如果上述代碼按照如下的順序執行:
一些其他常見的編譯器優化技術也會潛在的引入資料競争。例如考慮下面這個循環語句,用來統計list中負數的數量,其中count是一個全局或者靜态類成員:
這段代碼很明顯會引入資料競争,如果目前線程正在更新count值的同時,有另一個線程也剛好通路到這個變量。
編譯器有可能會将count存放在某個臨時變量裡,然後将代碼轉換為以下形式:
但這也會引入資料競争。假設pos_list指向某個list,它包含兩個data值:1和2, 然後聲明count為全局變量并初始化為0。接下來有兩個線程分别執行:
count_neg(pos_list);
count++;
先前沒有使用臨時變量版本的程式不會存在資料競争,因為pos_list并不包含負數,是以線程1不會讀也不會改寫全局變量count。
(有些人可能會覺得關于資料競争的這種定義,沒有必要苛刻要求按照特定的執行順序,是以這就需要考慮傳遞給函數的一些特定的參數。尤其是在實際程式中需要傳遞指針參數時,這是唯一講得通的定義,否則任何通過指針來寫資料的函數都會引入資料競争了。)
上面這個被編譯器優化後的程式(引入臨時變量local_count),線程1不管何時都存在對count的寫操作,是以也就引入了資料競争。
(盡管在java和c++0x中都将這種轉換清楚地列為非法,然而在posix中卻沒有明确規定,而且在目前(2008年)許多c編譯器中都存在這種轉換。)
需要注意的是,至少在沒有異常出現的情況下,上述代碼可允許被按如下方式轉換: