并發(fā)編程Bug源頭
一.簡介
編寫正確的并發(fā)程序是一件困難的事情,往往調(diào)試過程中發(fā)生很多不確定的事情,這時需要對理論知識的一個認(rèn)知,能夠準(zhǔn)確的追蹤問題。
二.硬件背景
CPU,內(nèi)存,I/O設(shè)備不斷迭代,這三者速度存在差異,CPU和內(nèi)存的速度差異可以形象的描述:CPU速度最快,內(nèi)存次之,I/O設(shè)備更次之。
為了合理利用CPU的高性能,平衡這三者的速度差異,計算機體系結(jié)構(gòu)、操作系統(tǒng)、編譯程序都做出了貢獻,主要體現(xiàn)為:
CPU增加緩存,以均衡與內(nèi)存速度差異;
操作系統(tǒng)增加了進程、線程,以分時復(fù)用CPU,進而均衡CPU與I/O設(shè)備的速度差異;
編譯程序優(yōu)化指令執(zhí)行次序,使得緩存能夠更合理利用。
三.源頭
3.1 源頭一
緩存導(dǎo)致的可見性問題
在單核時代,所有的線程都是在一顆 CPU 上執(zhí)行,CPU 緩存與內(nèi)存的數(shù)據(jù)一致性容易解決,所有都是串行。
一個線程對共享變量的修改,另一個線程能夠立刻看到,我們稱為可見性。

多核 CPU 的緩存與內(nèi)存關(guān)系圖
多核時代,每顆 CPU 都有自己的緩存,這時 CPU 緩存與內(nèi)存的數(shù)據(jù)一致性就沒那么容易解決了,當(dāng)多個線程在不同的 CPU 上執(zhí)行時,這些線程操作的是不同的 CPU 緩存。
3.2 源頭二
線程切換帶來的原子性問題
由于 IO 太慢,早期的操作系統(tǒng)就發(fā)明了多進程,即便在單核的 CPU 上我們也可以一邊聽著歌,一邊寫 Bug,這個就是多進程的功勞。
操作系統(tǒng)允許某個進程執(zhí)行一小段時間,例如 50 毫秒,過了 50 毫秒操作系統(tǒng)就會重新選擇一個進程來執(zhí)行(我們稱為“任務(wù)切換”),這個 50 毫秒稱為“時間片”。

線程切換示意圖
我們把一個或者多個操作在 CPU 執(zhí)行的過程中不被中斷的特性稱為原子性。CPU 能保證的原子操作是 CPU 指令級別的,而不是高級語言的操作符,這是違背我們直覺的地方。因此,很多時候我們需要在高級語言層面保證操作的原子性。
我們把一個或者多個操作在 CPU 執(zhí)行的過程中不被中斷的特性稱為原子性。
“原子性”的本質(zhì)是什么?其實不是不可分割,不可分割只是外在表現(xiàn),其本質(zhì)是多個資源間有一致性的要求,操作的中間狀態(tài)對外不可見。
3.3 源頭三
編譯優(yōu)化帶來的有序性問題
那并發(fā)編程里還有沒有其他有違直覺容易導(dǎo)致詭異 Bug 的技術(shù)呢?有的,就是有序性。顧名思義,有序性指的是程序按照代碼的先后順序執(zhí)行。編譯器為了優(yōu)化性能,有時候會改變程序中語句的先后順序,例如程序中:“a=6;b=7;”編譯器優(yōu)化后可能變成“b=7;a=6;”,在這個例子中,編譯器調(diào)整了語句的順序,但是不影響程序的最終結(jié)果。不過有時候編譯器及解釋器的優(yōu)化可能導(dǎo)致意想不到的 Bug。
四.總結(jié)
在介紹可見性、原子性、有序性的時候,特意提到緩存導(dǎo)致的可見性問題,線程切換帶來的原子性問題,編譯優(yōu)化帶來的有序性問題,其實緩存、線程、編譯優(yōu)化的目的和我們寫并發(fā)程序的目的是相同的,都是提高程序性能。
本文轉(zhuǎn)載自:https://blog.csdn.net/qq_19968255/article/details/109020395