C++核心準(zhǔn)則CP.2:避免數(shù)據(jù)競爭?

CP.2: Avoid data races
CP.2:避免數(shù)據(jù)競爭
Reason(原因)
Unless you do, nothing is guaranteed to work and subtle errors will persist.
除非你做到了,否則沒有任何東西可以保證動作,微妙的錯誤還會繼續(xù)存在。
Note(注意)
In a nutshell, if two threads can access the same object concurrently (without synchronization), and at least one is a writer (performing a non-const?operation), you have a data race. For further information of how to use synchronization well to eliminate data races, please consult a good book about concurrency.
簡而言之,如果兩個線程可以(不進(jìn)行任何同步)并發(fā)訪問同一個對象,至少一個線程執(zhí)行寫操作(執(zhí)行非常量操作),就會發(fā)生數(shù)據(jù)競爭。為了獲得如何更好地使用同步以消除數(shù)據(jù)競爭的進(jìn)一步信息,請查閱有關(guān)并發(fā)的經(jīng)典書籍。
Example, bad(反面示例)
There are many examples of data races that exist, some of which are running in production software at this very moment. One very simple example:
有關(guān)數(shù)據(jù)競爭的例子非常多,有些就發(fā)生于正在運(yùn)行的產(chǎn)品級軟件。下面是很簡單的例子:
int get_id()
{
static int id = 1;
return id++;
}
The increment here is an example of a data race. This can go wrong in many ways, including:
代碼中的增量操作就是數(shù)據(jù)競爭的例子。出錯的方式可以有很多種,包括:
Thread A loads the value of?id, the OS context switches A out for some period, during which other threads create hundreds of IDs. Thread A is then allowed to run again, and?id?is written back to that location as A's read of?id?plus one.
線程A獲取id的值之后操作系統(tǒng)上下文從A中退出一段時間,這時另外的線程生成了幾百個ID。接著線程A繼續(xù)運(yùn)行,這時id重新被寫入,而值是A讀取的局部變量加1之后的結(jié)果。
Thread A and B load?id?and increment it simultaneously. They both get the same ID.
線程A和B同時獲取id并加1。它們得到同樣的ID。
Local static variables are a common source of data races.
局部靜態(tài)變量是數(shù)據(jù)競爭的常見來源。
Example, bad(反面示例):
void f(fstream& fs, regex pattern)
{
array buf;
int sz = read_vec(fs, buf, max); // read from fs into buf
gsl::span s {buf};
// ...
auto h1 = async([&] { sort(std::execution::par, s); }); // spawn a task to sort
// ...
auto h2 = async([&] { return find_all(buf, sz, pattern); }); // spawn a task to find matches
// ...
}
Here, we have a (nasty) data race on the elements of?buf?(sort?will both read and write). All data races are nasty. Here, we managed to get a data race on data on the stack. Not all data races are as easy to spot as this one.
這里,保存在buf中的元素會發(fā)生(嚴(yán)重的)數(shù)據(jù)競爭(排序既包含讀操作也包含寫操作)。沒有哪個數(shù)據(jù)競爭是不嚴(yán)重的。代碼中的數(shù)據(jù)競爭發(fā)生在堆棧中的數(shù)據(jù)。不是所有的數(shù)據(jù)競爭都像本例這樣容易被發(fā)現(xiàn)。
Example, bad(反面示例):
// code not controlled by a lock
unsigned val;
if (val < 5) {
// ... other thread can change val here ...
switch (val) {
case 0: // ...
case 1: // ...
case 2: // ...
case 3: // ...
case 4: // ...
}
}
Now, a compiler that does not know that?val?can change will most likely implement that?switch?using a jump table with five entries. Then, a?val?outside the?[0..4]?range will cause a jump to an address that could be anywhere in the program, and execution would proceed there. Really, "all bets are off" if you get a data race. Actually, it can be worse still: by looking at the generated code you may be able to determine where the stray jump will go for a given value; this can be a security risk.
現(xiàn)在,編譯器不知道val會被修改,因此編譯結(jié)果很可能是一個帶有五個分支的跳轉(zhuǎn)表。那么一旦val的值超越了范圍[0..4],就有可能調(diào)轉(zhuǎn)到程序的任何位置并從那里繼續(xù)執(zhí)行。真的,一旦發(fā)生了數(shù)據(jù)競爭,結(jié)果會怎么樣誰也不知道。實(shí)際上,還有可能更壞:通過檢查生成的代碼,你或許可以準(zhǔn)確算出針對一個給定的值,程序可以跳轉(zhuǎn)到什么位置。這可能成為一個安全風(fēng)險。
Enforcement(實(shí)施建議)
Some is possible, do at least something. There are commercial and open-source tools that try to address this problem, but be aware that solutions have costs and blind spots. Static tools often have many false positives and run-time tools often have a significant cost. We hope for better tools. Using multiple tools can catch more problems than a single one.
有一些是可能的,至少做點(diǎn)什么。有些商用和開源工具試圖定位這些問題,但是需要注意的是:解決方案需要成本并存在盲區(qū)。靜態(tài)工具會產(chǎn)生很多誤報,而運(yùn)行時工具通常需要巨大的成本。我們希望出現(xiàn)更好的工具。使用多種工具會比單一工具捕捉更多的錯誤。
There are other ways you can mitigate the chance of data races:
存在另外的方法可以降低數(shù)據(jù)競爭的可能性:
Avoid global data
避免全局?jǐn)?shù)據(jù)
Avoid?static?variables
避免靜態(tài)數(shù)據(jù)
More use of value types on the stack (and don't pass pointers around too much)
在堆棧上更多地使用值類型(并且不要來回傳遞指針)
More use of immutable data (literals,?constexpr, and?const)
更多地使用不可修改的數(shù)據(jù)(literals,?constexpr, and?const)
原文鏈接
https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#cp2-avoid-data-races
覺得本文有幫助?請分享給更多人。
關(guān)注微信公眾號【面向?qū)ο笏伎肌枯p松學(xué)習(xí)每一天!
面向?qū)ο箝_發(fā),面向?qū)ο笏伎迹?/span>
