09.29 多線程編程是後臺開發人員的基本功

多線程編程是後臺開發人員的基本功

這裡先給大家分享一個小故事:

在我剛開始參加工作的那年,公司安排我開發一款即時通訊軟件(IM,類似於 QQ 聊天軟件),在這之前我心裡也知道如果多線程操作一個整型值是要加鎖的,但是當時為了圖代碼簡便,而且在實際調試的時候,沒有加鎖的代碼也從來沒出過問題。於是我就心存僥倖了,覺得對整型值加鎖真是多此一舉。

我們的軟件有類似於 QQ 這種單人聊天功能,每個用戶都有一個整型的 userid,問題就出在這裡。當時公司的老闆和他妻子也使用這款軟件,問題來了:有一天早上老闆在這個軟件上給他妻子發了一段親密的話。問題來了,由於多線程操作他妻子的 userid 沒加鎖,最終變成了另外一個人的 userid,而這個 userid 恰好是我的賬戶。於是老闆發給他妻子的聊天內容就被髮給我了

我當時看到聊天內容很奇怪,還回復了他一句,並且還帶上了我自己的姓名……事情的結果,可想而知了,老闆非常尷尬也非常生氣……從那以後,老闆看我的眼神都是怪怪的。我自知理虧,再也不僥倖了,

凡是多線程讀寫整型變量都養成加鎖的好習慣

這是我曾經犯過的錯誤,也就是所謂的線程安全問題。“年輕”時的我,當時不明白為什麼一個整型變量在多線程操作時存在安全問題,進而引起業務上的錯亂問題。

很多年以前的時候,技術面試的時候面試官經常會問“程序什麼時候需要開啟新的線程”這樣的問題,那個時候多核 CPU 才剛開始普及,很多人也是才開始逐漸接觸多線程技術。而如今多核 CPU 和多線程編程技術已經是下里巴人的技術了。

因此“程序什麼時候需要開啟新的線程”這一類問題,簡單解釋一下,就是為了提高解決問題的效率,畢竟大多數情況下,多個 CPU 並行做一件事總比單個 CPU 做要快很多。

然而,多線程程序雖然強大,但也讓原來的程序執行流程變得複雜和具有一定的不確定性,比如帶來資源的競態問題,初學者或許意識不到帶來的後果,往往不能夠很好處理這個問題,幫助學習多線程編程的初中級讀者,理清多線程程序的執行原理、脈絡是我寫

專欄的創作初衷之一。

由於各種庫和運行環境對操作系統多線程接口的封裝,很多技術開發者做了很多年的開發,仍然只是個界面或者僅知道調用庫的“業務”程序員,他們只能面向搜索引擎編程,遇到稍微複雜一點的多線程邏輯就不知如何下手了。

專欄將從操作系統原理的角度介紹多線程技術的方方面面,從基礎的知識到高級進階,它們是筆者這些年工作的經驗總結和踩坑之後的教訓。

專欄的內容主要分為以下三大方面。

操作系統層面上關於多線程和多線程協作的接口原理

無論是哪種編程語言和庫,其最終都是要運行在操作系統上的,應用程序本身的特性應該是決定採用何種編程工具的最主要因素,但是無論你採用什麼樣的編程語言,通過了解操作系統 API 從而深入理解操作系統的工作原理,這本身就有很重要的意義。

操作系統是一個非常複雜的系統,在 API 之上加一層編程語言並不能消除其複雜性,最多不過是把複雜性隱藏起來而已。說不定什麼時候,複雜的那一面遲早會蹦出來拖你的後腿,懂得系統 API 能讓你可以更快地掙脫困境。

因此,針對多線程編程,本專欄將詳細地介紹 Windows 和 Linux 操作系統層面上提供的各種多線程接口,理解並熟悉它們的使用後,讀者在接觸或者學習其他語言或者庫提供的多線程功能時,可以快速地上手和掌握。

以協程這一技術為例,雖然協程是計算機操作系統原理之一,但是我們所接觸的大多數操作系統並沒有從系統層面上支持協程這一技術

而像 Golang 這一類語言是提供協程功能的,那這一類語言是如何支持的?如果你對操作系統的線程有著深入的瞭解,你也不難想明白:

線程是操作系統的內核對象,當多線程編程時,如果線程數過多,就會導致頻繁地上下文切換,這些對性能是一種額外的損耗。

協程,是在應用層模擬的線程,它避免了上下文切換的額外損耗,同時又兼顧了多線程的優點,簡化了高併發程序的複雜度。

再例如,線程局部存儲技術是我們常用的一項多線程技術,它的存在讓每個線程可以有自己私有存放數據的空間。那線程局部存儲技術是如何實現的呢?本專欄中會庖丁解牛地介紹線程局部存儲是什麼以及它的實現原理。

基於操作系統多線程理論衍生出來的一些擴展理論模型和應用

正因為存在多線程編程,所以有了線程池模型,據我瞭解,“線程池”的實現和原理應該是多線程編程新手問的、聊的最多的一個技術點了。

專欄會帶領讀者利用各種操作系統提供的線程同步對象來實現一個線程池,進而引出生產者消費者理論模型,再進一步昇華,引出所謂的消息中間件,如 Kafka、RabbitMQ。

對於技術方案,我們不推崇重複造輪子,但是一定要具有重複造輪子的能力和了解輪子的製造原理

。有了這項能力之後,在使用一些開源的消息中間件時,我們因為“知其然、知其所以然”才會把這些軟件在項目中用得更好。

專欄中會詳細地介紹多線程操作整型變量非線程安全的原因以及解決方案。掌握了這些,你在學習像 Java 語言時,就明白了為什麼 JDK 在操作一個整型變量時提供 AtomicInteger 這樣的類了。

和多線程相關的,一些實際開發中的技巧和經驗

如果你是一名開發者,那麼曾經或許會為下面一些問題而頭痛過,這些問題或許你在面試時被面試官問到或者在實際開發中遇到過:

  • 進程的 CPU 使用率過高如何查找原因並解決?

  • 如何讓一個程序只允許使用者運行一個實例?

  • 在實際開發中,避免死鎖有哪些可以遵循的規則?

  • 什麼是條件變量的虛假喚醒?虛假喚醒會帶來什麼問題?如何解決?

  • 如何設計高效的線程池和隊列模型?

  • 如何在線程函數中訪問類的成員變量和函數?

諸如此類實際開發中經常遇到問題,實在太多了,這裡就不一一列舉了。本專欄在保持主幹脈絡介紹的同時,也會穿插介紹一些與多線程相關的開發技巧和經驗。

當然,多線程問題本來就比較複雜,尤其是本專欄同時介紹 Windows 和 Linux 兩個操作系統平臺的接口,在實際編寫程序時,由於操作系統提供的 API 不一樣,為了跨平臺,我們不得不寫許多跨平臺代碼。好在,C++ 11/14 標準給 C++ 引入了大量的多線程類和庫,本

專欄也會詳盡地介紹它們的用法。

掃碼查看《C/C++多線程編程精髓》專欄詳情

多线程编程是后台开发人员的基本功

以下是專欄的主要內容導圖:

多线程编程是后台开发人员的基本功

內容亮點:

  • 解析操作系統 API 層的多線程編程原理

  • 展示 Windows 和 Linux 操作系統的基本原理

  • 講解線程間各種同步原語的適用場景、優缺點

  • 貫穿實際開發中的問題定位與排查

你將獲得:

  • 徹底掌握多線程編程原理和編碼經驗

  • Windows 和 Linux 操作系統的基本原理

  • 線程之間各種同步原語的適用場景和優缺點

  • 相關編程慣用法和手段技巧

  • 瞭解 Java、Go 等上層語言運行時環境提供的功能是如何基於操作系統 API 實現的

  • 實際開發中一些問題定位與排查


由於操作系統調度線程時的不確定性,同樣的邏輯可能在不同機器、不同時刻有不同的行為表現,也因此增加了排查和定位問題的難度。這是在學習和開發多線程程序時不得不面臨的問題。

建議讀者將專欄各個章節中的代碼示例都實際在機器上運行一遍,認真、準確地理解每一個多線程同步原語的適用場景和性能優缺點。只要透徹地理解了這些操作系統提供的基礎多線程同步原語,在面對它們的衍生物(如線程池、消息隊列、協程技術等)時可以更快地學習和用好。

操作系統提供的 API 接口一般在相當長的時間內會保持不變的(至少保持向後兼容),一經學會,終生受用。在新技術新思想層出不窮、令人應接不暇時,

掌握了操作系統層面的 API 和其設計思想原理,可以以不變應萬變

最後,多線程編程在現代軟件開發中是如此的重要,以至於熟練使用多線程編程是一名合格的後臺開發人員的基本功,它是如此的重要,希望本專欄能幫助你掌握它,願它能讓你徹底告別多線程編程煩惱。


分享到:


相關文章: