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

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

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