簡評:并發問題的牛鼻子
Concurrency 的牛鼻子是 shared data,找準了 shared data 基本上就解決了一大半的問題。很多時候,意識不到 concurrency,或者無法利用 concurrency 的加鎖性質,就在于無法正確識別 shared data。
如果一個 concurrent 的程序,壓根兒就沒有 shared data,那么恭喜你,這就意味著你完全不必考慮 concurrency conflict,因為沒有什么地方會有交集,自然也就不會用沖突。所謂井水不犯河水,哪來的沖突。
但是,這同樣意味著:如果你想利用 concurrency conflict 來加鎖去做「順序化」或「排他性」,是辦不到的。例如,聽起來高大上的「分布式鎖」,其本質就在于不同的 server 之間,根本沒有 shared data,于是,你根本無法控制 server 之間的順序化/排他性。而解決的方案也非常簡單,就是為這些不相關的 server 引入一個 shared data 就可以了。
很多了解不透徹的人,一談到分布式鎖似乎就必須使用 Zookeeper、使用 Redis。而事實上呢,一切可以提供 shared data 的 service 都可以。例如使用 db 中的一個字段,甚至使用某個單機服務的變量做 shared data。要點不在于使用的是什么中間件,而是需要有一個 shared data 供這些 server 訪問。
再來,很多時候意識不到或者弄錯了 concurrency conflict,就在于把 shared data 弄錯了。例如典型的 ++i 這個操作,從代碼層級看,似乎不同的 thread 之間沒有 shared data。可是,如果你從 CPU 的 instruction operation 的角度來看,這個操作會有 shared data 殘留在不同 thread 之間共享的 memory 中。
所以,要識別清楚 shared data,一個非常核心的要點就在于你必須對「底層」機制特別清楚。因為如果無法知曉底層機制,那么這就意味著這一塊 code 往下都是黑箱。你自然是無法判別黑箱之中是否有 shared data 存在。
總之,找到了 shared data 就找到了 concurrency problem 的坐標系。大到分布式系統的千百臺 server,中到一個 project 中數十個代碼變量,小到 CPU 的 instruction operation,它們都可以被歸結為同一類問題,即:什么是你要解決的問題的 shared data,這些 shared data 可由哪些操作改變。有了這個坐標系,一切分析問題和解決問題的工作才得以開展,是以不變應萬變的根本。
另:解決 concurrency conflict 的方式其實很自然,就是讓訪問 shared data 的操作強制有序,也就是排隊。就像大家都要去搶占公園入口這么個 shared data,只需要在這個入口前方構建一個隊列即可。特別地,所謂的「加鎖」,無非就是長度為1的隊列罷了。
如此來看,解決 concurrency conflict 的方式也極其簡單粗暴,就是讓它無法做 concurrent 操作,即:拉出隊列做「順序化」。
浙公網安備 33010602011771號