如果你對以下幾個問題有疑問,,那么本文可能會有所幫助,。
1.2.3 談協(xié)程繞不開線程,,按傳統(tǒng)還得從進(jìn)程談起,不過我想業(yè)內(nèi)人員對進(jìn)程和線程應(yīng)該是耳熟能詳,,這里就簡單概括下,。 進(jìn)程擁有自己獨(dú)立的堆和棧,既不共享堆,,亦不共享棧,,進(jìn)程由操作系統(tǒng)調(diào)度;線程擁有自己獨(dú)立的棧,,共享堆(也可以有自己的私有域),,不共享棧,線程亦由操作系統(tǒng)調(diào)度,。一個進(jìn)程可以有多個線程,。 多線程一直以來是面試必考點(diǎn),雖然[web]服務(wù)端開發(fā)人員似乎從來不用直接操作線程,,其實是因為框架幫忙維護(hù)了,,開發(fā)人員只需要關(guān)心業(yè)務(wù)實現(xiàn)。這也導(dǎo)致了部分人對多線程的某些概念模糊不清。比如關(guān)于多線程的效率:在多核cpu下,,多個線程可以并行運(yùn)行在不同內(nèi)核上,,效率高;而在單核cpu中,,多個線程的并行執(zhí)行其實是一個錯覺,,因為它們都是運(yùn)行在一個內(nèi)核上,一個cpu內(nèi)核同一時間只能執(zhí)行一個進(jìn)程/線程,,因此在一個內(nèi)核上的多線程執(zhí)行其實效率反而比串行執(zhí)行低,,只是給用戶一種并發(fā)的錯覺,反而增加了線程切換的時間,。 但是效率的高低還要看線程占用cpu資源的占用率,,比如存在大量IO操作,IO比較慢,。也就是說,,如果只有單線程,那么一旦涉及到IO操作,,線程可能會被阻塞,程序的其余邏輯就只能傻等,,就算那些邏輯不依賴于這個IO操作,,此時線程對CPU的使用為0,CPU就是空閑狀態(tài),。如果是多線程,,是線程瓶頸,那么其余線程則可以使用cpu,,而非等待IO結(jié)束,。 題外話,一個空循環(huán)就能讓cpu滿載,,參看 為什么一個空的死循環(huán)會讓CPU占用達(dá)到100%,。 后來,出現(xiàn)了多路復(fù)用之類的技術(shù),,原先需要等待IO返回的線程也不需要等了,,可以和其它線程一樣忙別的事,IO返回時得到通知再處理接下去的事情,。Java的NIO和.Net的async/await就是這么干的,。 一般來說,為了避免線程頻繁創(chuàng)建銷毀帶來的性能問題,,程序里都會使用到線程池,。 然而還是在單核的場景下,事情似乎變得有點(diǎn)詭異。既然線程們現(xiàn)在都能心無旁騖地使用CPU計算,,而前面也說了,,一個cpu內(nèi)核同時只能運(yùn)行一個線程,管理多線程又是搶占式,,又是棧切換,,維護(hù)生命周期啥的,影響性能不說,,完全沒得必要嘛,,為什么不只用一個線程完成所有的計算呢。什么,,你說可能需要[偽]并行計算,?那就讓線程自己來安排咯,畢竟具體邏輯方面,,線程本身(或者說開發(fā)人員)比CPU要清楚的多,,知道什么時候該干什么,什么時候切換邏輯,,什么時候不切換,,都由線程自己說了算。于是,,協(xié)程粉墨登場,。 協(xié)程主要是針對單線程的一個概念(如Js、NodeJs,、Python由于GIL導(dǎo)致的偽多線程),,可以將其看作線程運(yùn)行時片段。和線程類似,,雖然貌似多個協(xié)程可以并行執(zhí)行,,一個時間仍然只能運(yùn)行一個。所以,,如果業(yè)務(wù)邏輯是順序相關(guān)(串行)或者各任務(wù)對反饋及時性要求不高,,那么沒必要用協(xié)程,就跟沒必要多線程一樣,。協(xié)程對比線程,,除了有更好的性能外,還讓開發(fā)人員對執(zhí)行片段有了更好的掌控,。比如Go語言,,通過阻塞條件(time.sleep()、select{}等),,我們可以手動將控制權(quán)轉(zhuǎn)移給其它的 Go 協(xié)程 , 也可以說是告訴調(diào)度器讓它去調(diào)度其它可用空閑的 Go 協(xié)程(Go如何判斷這是阻塞代碼尚未研究過),;或者通過channel調(diào)度指定協(xié)程,。 Go默認(rèn)情況下只用單線程。這就是說,,你即使開了幾百個goroutine,,系統(tǒng)中同一時間在跑的只有一個線程,也就是一個協(xié)程,。依據(jù)上面的內(nèi)容,,大家可以思考下Go為何默認(rèn)如此。我們可以通過 runtime.GOMAXPROCS() 設(shè)置的是Go語言能跑幾個線程,,講道理,,CPU幾核跑幾個線程比較合理,使用 runtime.NumCPU() 查看內(nèi)核數(shù),。 在編程層面來說,,協(xié)程的概念偏向于以同步編程的模式實現(xiàn)異步處理的編程模式,避免了多層回調(diào)代碼嵌套的問題,。 其實在很多年以前,,協(xié)程已經(jīng)被提出了,現(xiàn)在只是它煥發(fā)生機(jī)的階段,。 4 上文說了,,協(xié)程之間應(yīng)該是非順序相關(guān)的,即它們的上下文沒有強(qiáng)依賴關(guān)系,,是相對獨(dú)立的,。這里的上下文指的就是當(dāng)前的運(yùn)行棧空間,,它包括了參數(shù)、局部變量,、各寄存器的值等內(nèi)容,。在協(xié)程切換的時候,我們要想辦法將對應(yīng)的上下文投射到當(dāng)前線程的運(yùn)行棧中,,即讓線程執(zhí)行特定的上下文,。很容易想到malloc一塊臨時內(nèi)存存放掛起的協(xié)程上下文信息,resume的時候再覆蓋回去,,運(yùn)行棧在內(nèi)存中只有一處,,這就是stackless模式。相對的還有stackful模式,,在這種模式下,,每個協(xié)程都有自己的棧空間,,運(yùn)行棧指的就是當(dāng)前協(xié)程的??臻g。現(xiàn)有語言的實現(xiàn)中,Python, Kotlin等定義的就是stackless協(xié)程,, Go語言中實現(xiàn)的是stackful協(xié)程,。 對于其它沒有在語言層面直接支持協(xié)程的語言來說,由于協(xié)程涉及到底層的[堆]棧切換控制,,因此很難單純依靠現(xiàn)有語法構(gòu)建算法的方式實現(xiàn),。有人做過此類嘗試(可參看Coroutines in C),但也沒有實用性,。 能直接操作執(zhí)行堆棧并暴露api的,,現(xiàn)在市面上的語言以C/C++最為流行,基于它們也有很多開源的協(xié)程庫,。下面介紹幾種實現(xiàn)方式,。 協(xié)程分為非對稱協(xié)程和對稱協(xié)程。在非對稱協(xié)程中,,調(diào)用者和被調(diào)用者的關(guān)系是固定的,,調(diào)用者將控制流轉(zhuǎn)到被調(diào)用者,被調(diào)用者運(yùn)行完畢后只能返回到調(diào)用者,,而不能返回到其他協(xié)程,。對稱協(xié)程則不然。對稱協(xié)程可以很容易由非對稱協(xié)程來表達(dá),。且按一般的調(diào)用邏輯,,A調(diào)B,B應(yīng)返回到A,,再由A發(fā)起到C的調(diào)用,,而非B直接返回到C。因此,,目前大多數(shù)協(xié)程庫都只實現(xiàn)非對稱協(xié)程,。
關(guān)于匯編語法的平臺差異,類Unix下采用的是AT&T的匯編語法格式,,Dos/Windows下面采用的是Intel匯編語法格式,。 參考資料: 轉(zhuǎn)載請注明本文出處:https://www.cnblogs.com/newton/p/11104187.html |
|