怎麼樣才算得上熟悉多線程編程?
本人待畢業生一名,看到很多招聘信息中都要求:熟悉多線程編程(或並發編程/非同步編程)。
我在自己寫爬蟲的時候為了提高效率用過多線程。但是不是很理解招聘信息中所指的熟悉多線程編程是指會使用多線程么?還是要深入理解其原理、框架?總之對多線程還不是很理解,多線程的具體網路應用也不是很清楚。怎麼樣才能做到熟悉多線程編程呢?望大俠還能解讀一二!
第一,明白進程和線程的基本概念
第二,明白保護線程安全的基本方法有哪些
第三,明白這些線程安全的方法,包括互斥鎖,自旋鎖,無鎖編程的適用的業務場景是什麼?從OS和硬體角度說說原理是怎麼樣的?開銷在哪裡?
第四,能在現場藉助cas操作,風險指針實現無鎖的數據結構,比如無鎖棧,無鎖環形隊列。無鎖排序鏈表
雖然不是大俠,但以下回答基於我自己的面試不同公司經歷,有一定參考性,本人也是在簡歷中聲稱熟悉多線程,原來覺得這是真的,但後來發現這是吹牛不怕吹死的無畏精神。我覺得大多數只是干過多線的人工作經驗上其實很少能cover完全下面這麼多topic的。因此還是要準備準備。。
不同平台提供的工具很不一樣,看公司要求。關鍵還是要了解原理。下面的案例差不多是6家左右要求多線程熟練的公司應聘的總結。1. 了解進程線程的基本概念,能用一種語言在一個平台上實現一個多線程的例子。(這些不會還寫熟悉多線程就太大無畏了)2. 了解為什麼要用Mutex之類的工具做鎖來同步和保護資源。弄懂諸如racing condition,死鎖之類的概念。50%公司的見面題,用來砍死大無畏。3. 了解編譯器優化帶來的影響,了解cache的影響,了解volatile,memory barrier之類的概念。如果是主Java的話,去了解一下JVM的內存模型。以上這些偏硬偏系統端的公司喜歡問,不過由於太基礎,稍稍好奇一點的多線程領域程序員都應該會了解,否則略顯大無畏。4. 了解一下你主攻平台+語言所提供的工具庫,知道常用的工具的用法和使用場景:Mutex,Semaphore,原子操作集,Condition Variable,spin lock。這幾個算是比較常用的,在各個平台+語言也都有對應實現。老實說,spinlock,condition variable是我工作里從沒用過的,但是也被問過,其他幾個都太常用了,如果是java的話再多看一組Executor相關的,以及Java多線程相關的keywords,和object本身提供的同步函數,wait notify之類的,在主Java的公司問過。5. 了解常用的多線程設計範式,比如讀寫鎖(Reader/Writer Lock,非常經典的範式,有偏向讀和寫的不同變形,至少被要求寫過3次),生產消費範式(寫過2次),一些常用容器的實現,比如BlockingQueue(寫過3次)或者concurrentHashmap(寫過2次)。如果是主Java的話可以看看JDK的實現。熟悉一下一些算不上多線程設計模式的小技巧,比如傳遞只讀對象可以避免加鎖,或者Copy傳遞以防外部修改之類的(討論環節被問過)。另外值得特別一提的一個小細節是,Singleton的線程安全是個很有意思而且容易出錯的話題,值得一看(只被問過一次,不過我答掛了,所以印象及其深)。還有可能會問的是一些有趣的小場景讓你實現一些功能需要線程安全,無法特別準備,但是你能了解上面說的這些範式,不傻的話大多數都能想出來。如果和我一樣多線程方面是主Java的話,記得Doug Lea的書寫的很明白,不過不記得當時讀完的是哪本,70%可能是下面這個http://www.amazon.com/Java-Concurrency-Practice-Brian-Goetz/dp/0321349601否則就是Concurrent Programming in Java: Design Principles and Pattern (2nd Edition): Doug Lea: 0785342310092: Amazon.com: Books
這個大致是一些公司對多線程部分的要求,如果應聘者聲稱熟悉這個部分。上面所有點都是本人面試被問到的,基本上能看完上面這些,可以做到不用很心虛在簡歷上寫自己熟悉多線程而不會被揭穿。當不敢再隨便用多線程的時候,就算真的熟悉了。
線程這玩意本來就不該應用開發操心,應多了必爛。真正熟悉多線程編程的,都在做架構,做多線程組件,類似下面這些:
實現微線程/協程架構,微線程分為stackful和stackless兩種,目前開源多數都是stackful的,其中 知乎 @朱元 目測是在stackful 領域中最牛的大牛,開源軟體,值得學習。我廠實現stackless的微線程,理論上推測可以有更大的性能潛力,stackless不需要棧的切換,所以理論性能更高,stackless架構目前沒有完整的開源架構。我廠是完全自研的,當時是看重了該模型的理論推測性能更高,實現難度也更高,各公司均保密。僅僅是微線程架構,也需要不少的代碼量。
微線程架構減少了線程切換的開銷,性能會比傳統架構高不少。多線程之間免不了數據交換,典型的就是隊列,傳統的隊列是有鎖隊列,這個是第一代隊列。更先進的就是基於cas指令的lock_free隊列,性能要高不少,開源軟體有不少,這個是第二代產品。最先進的第三代隊列是雙端同時並發的無等待wait_free架構,wait_free架構沒有完整的開源實現,比以前的產品快很多,性能超高,單生產者單消費者隊列每秒3億,各公司均保密。我廠這部分代碼wait_free架構超過5000行代碼,實現了多生產者和多消費者模型無等待並發模型。
多線程下必須使用智能指針,智能指針第一代就是類似std::shared_ptr這種,引入make_shared打了個性能補丁,快了一些。std::shared_ptr不是完全線程安全的。第二代智能指針std::atomic_shared_ptr目測c++20可能會引入,實現了完全線程安全。其實第二代智能指針很多公司早都在用了,一些遊戲公司十多年前就都在使用自研的更安全的產品,完全線程安全。我廠也研發實現了第二代智能指針,性能比第一代快一倍,完全線程安全,並進一步實現了第三代智能指針,完成了類似java gc功能,但是比java更快的模型,減少了析構的開銷,使得使用智能指針比裸指針更高的性能,並且使用方法上與std::shared_ptr一樣,全兼容第一代智能指針,第二代第三代智能指針技術各公司都保密。
多線程下高性能鎖/多功能鎖類似facebook開源c++庫folly裡面的高性能多功能鎖,基本上每家公司都有類似的實現。課本上那些spin_lock的設計都是玩具級別的,性能也差。各公司在這塊都有獨自的特色,以前各公司均保密,自從facebook開源後,高性能多功能鎖技術就擴散了一部分技術出來,但是還是有一些第二代鎖的先進技術不為公眾所知,總之第二代技術的鎖比第一代std庫的鎖快功能也多,我廠也實現了類似的庫,性能遠超std::mutex等常規鎖,也有類似folly的讀寫鎖,支持讀鎖升級為寫鎖,支持寫鎖降級為讀鎖等等功能,兼容std::lock_guard等加鎖構件。
以上四個只是多線程領域的幾個小模塊,類似有很多這樣的模塊組合,最終實現高性能產品。
為了講清楚多線程編程,這兒先講一個場景。
這個場景是這樣子的,山上有座廟(進程),廟裡住了很多小和尚和老和尚(線程),當然隔壁山上也有很多尼姑妹子。小和尚和老和尚們每天都需要下山挑水喝。這些個和尚有剛上山的(就是有點傻的),有比較聰明的,也有鬧過矛盾的。場景,人物呢,大概就是這麼個樣子。
俗話說的好,有人的地方就有江湖。這些個小和尚,老和尚都不是很安分,為了每天的這個挑水工作鬧了很多很多的矛盾,也犯過很多傻。
下面對挑水工作所有發生的情況做個詳述:
1)剛上山的小和尚挑水,就是比較傻的那個,和別人都不認識,每天挑水都自己一個人,一次挑一桶水,一天下來發現挑的水根本不夠喝。(串列)
2)和尚裡面有個力氣大的,和別人關係不好,每次挑水能挑三桶左右,一天下來,發現挑的水勉強夠喝。 (並發)
3)和尚裡面有幾個關係好的,力氣也比較大,每次挑水都一起去,每個人都能挑兩桶左右,一天下來,發現挑的水不僅夠喝,還有時間幫隔壁山尼姑妹子挑水。 (並行)
4)在水井裡打水,有個專門的妹子負責打水,比較傻的那個小和尚每次都盯著打水的妹子,還隨時隨地看水桶裝滿沒有(同步);當然,有傻的,也有聰明的,這個聰明的和尚在妹子打水的時候,就乘機在一旁休息,水打滿以後,妹子都過來告訴他。(非同步)
5)水井裡有一天沒水了,傻和尚看見水井裡沒水以後,在水井邊上一直等著,等有水了再挑回去(阻塞);聰明和尚呢,看見水井裡沒水了,一點都不猶豫的回去了。(非阻塞)
6)又有一天,那鬧過矛盾的兩個和尚挑水,正好狹路相逢,誰也不讓誰過去,一直僵持著。(死鎖)
7)寺廟裡廚房空間有限,每次只能進去一定數量的和尚,當廚房滿了以後,其他人只能在外面等著,直到裡面的人出來。這個實現的方法是,廚房門口掛了很多鎖,每個人進去以後都拿走一把鎖,出來以後把鎖掛上,這樣外面的人就可以知道廚房裡面人滿沒滿。(信號量)
終於編完了。。。
通過上面生動形象的描述,這個線程裡面的一些概念已經明白了。
下面解釋下多線程。
還是上面的例子,在這個寺廟裡面,如果每天都由那個新來的傻子和尚挑水喝,肯定是不行的,有的喝沒得喝咱且不說,你讓他累死了咋整。何況寺廟裡那麼些個空著的水桶那麼些個不幹活的人閑著也是閑著。
所以多線程的意思就是多個線程一起去協同完成一個任務,通過充分去共享資源來達到提升效率的一種編程思想。
當然,在這個過程中會遇到很多麻煩,比如會碰上死鎖的問題,同時去爭搶同一個資源的問題等等。
為了解決這個問題,iOS里是使用加鎖這個方法避免,當然,使用鎖,會在一定的程度上印象執行的效率,我們需要在效率和安全之間尋找一個平衡點。
Synchronization (Windows)
Processes and Threads (Windows)
把這兩個全部看完,自己練練就差不多了。剛進M$RA的時候,被要求去做一個分散式系統的debugger。我花了三個月看完了所有的這些內容,爛熟於心之後,把這些函數都勾了(detour就是方便啊),完成了那個原型。
你們都搞錯了。
要想真正弄清楚多線程,必須從硬體開始,弄清楚電流到CPU、CPU到操作系統,操作系統到用戶程序的整個工作原理與流程才行。特別是CPU、主板、物理內存這一層面上的鎖的原理與各種實現方案,是用戶程序中鎖問題的根源所在。
不知道你看的是什麼樣的公司。
我(iOS)的經驗是面試的時候,是需要講理論的,尤其是不太懂的面試官。懂的會直接考場景。
入職後用的最多的多線就是網路非同步。但是介面都是經過良好封裝的。原理什麼的,考慮的不多。其他方面的多線應用,其實應該有,但是小公司不會過於關心UE的,很多耗時稍長的操作也就讓用戶等著了。
井底之蛙,權當作個標本吧。多線程有什麼好熟悉的,只是一個過度定型的模式。多線程要解決的問題說到底只有一個,如何並發,包括共享資源和最大化利用資源兩種。而並發本身是真實世界的本原,也是事物/問題可以通過細分獨立再關聯成為一個整體的固有特性。在計算中模擬並發的基本手段是調度,而多線程就是搶佔式調度模式在編程語言下的具體特徵。這麼說你就知道多線程本身是怎麼回事。至於其他問題,都是源於並發自身,就是如何高效的同步,如何訪問共享資源不發生衝突,如何解決共享資源的釋放或者叫所有權問題,以及避免使用一些手段解決這些問題時可能導致的一些其他問題,比如死鎖等等。
總之,困難的是並發本身,而不是什麼多線程,雖然目前的編程模式都過度的甚至是錯誤的把前者嫁接在後者之上。
給你個終極解決方案:看懂uc|os的源代碼
面下pure storage,哪天面過了你多線程就OK了
其實只要理解為什麼多個線程要加鎖的根本原因就可以了~
因為一個進程內的線程共享一個頁目錄項,所以不同的線程運行在不同的核上的時候,讀取同一個虛擬地址時,對應的物理地址是一致的,所以可能產生衝突~(不太準確)
所以所謂的各種模型,加的鎖應該就是為了解決這些問題吧~
多進程就沒這種問題,但是不同進程數據共享就很麻煩了,因為每個進程有自己的頁目錄項,即使虛擬地址一樣,映射的物理內存也是可能不一樣的~
僅供參考~談一下個人的體會吧。
首先要明白為什麼要使用多線程。
其次能清楚線程,協程和進程的區別和聯繫,通過比較能加深理解。
另外還要掌握線程直接的同步互斥鎖條件變數消息隊列等等線程間協作通信的原理和方法。
上面這些都算是基礎。
最後最重要的是結合具體的業務邏輯,合理劃分多線程任務,邏輯上各個線程功能清晰,物理上充分發揮多核優勢,降低線程間的耦合度,提高運行效率。
這裡舉一個例子,遊戲引擎裡面,io操作很適合劃分一個線程,因為io這個部分和其它部分相比,邏輯清晰自成一體,功能單一,ios速率和cpu不匹配。與OpenGL介面操作相關的部分也很適合劃分一個線程。但是場景樹的更新需要不需要劃分多個線程,怎麼劃分,就需要斟酌了。因為場景樹的更新邏輯上是一個東西,並且更新過程中要細心區分,確保子樹關聯最小。另外,最重要的是如果設備是單核,這個就根本沒必要做。
多線程最終是要在完成功能的前提下提高效率,而多線程方案的設計也是衡量程序員水平的一項技術指標。
我覺得吧,各種複雜的線程同步都能搞定就算了。
多線程編程裡面涉及到的唯一的有點難度的東西就是同步與非同步 你只要懂多線程原理 知道各種同步非同步的方法和應用場景就沒什麼問題了 面試我覺得也沒人問這個
java的話, 你能看懂juc的aqs框架, 應該就懂了
level 1. pthread_xxx , atomic_xxx, basic rw mutexlevel 2. read memory_barrier.txt, knows details in mutex, condvar, spinlock,...level 3. lock free algorithm imp
推薦閱讀:
※.NET中如何通過Razor引擎生成這樣的代碼?
※作為 .Net 開發人員,我們為什麼要學習 CLR?
※程序員自測程序時一般會填寫哪些有意思的資料?
※vs2015編譯出現拒絕訪問的問題.該怎麼解決?
※各位前輩,請問在開發App時,您們是怎麼設計自己的類的?