先了解一下并行和串行 和并發的概念
注意:
并發是對需求側的描述,并行、串行才是對實作側的描述,這兩根本不是同一個范疇的東西,更不可能是互斥的關系,一定不要被同時段(并發)、同時刻(并行)那些老被拿出來一起比較的概念,弄混了本質
并發是指同時有很多事要做,(10000個任務分給6個核心,肯定會出現并發現象),你可以串行處理也可以并行處理,
并行是指同時做多件事,
因此并發和并行是相關的,但是是不同的兩個概念,
并發:并發(同一時間段,而不是同時)(concurrent)指的是多個程式可以同時運行的現象,更細化的是多行程可以同時運行或者多指令可以同時運行,注意這是一種現象,同時運行多個程式或多個任務需要被處理的現象,在描述并發的時候也不會去扣這種字眼是否精確,并發的重點在于它是一種現象,并發描述的是多行程同時運行的現象(多核的時候),但實際上,對于單核心CPU來說,同一時刻只能運行一個行程,所以,這里的"同時運行"表示的不是真的同一時刻有多個行程運行的現象,這是并行的概念,而是提供一種功能讓用戶看來多個程式同時運行起來了,但實際上這些程式中的行程不是一直霸占CPU的,而是執行一會停一會,
**注意!**分為兩種:
執行緒和行程都可用于實作并發,
1、多核的時候,多行程、執行緒同時運行的現象算并發
2、單核的時候,多行程、執行緒在同一段時間內來回切換也算并發
在作業系統中,是指一個時間段中有幾個程式都處于已啟動運行到運行完畢之間,且這幾個程式都是在同一個處理機上運行,
并發不是真正意義上的“同時進行”,只是CPU把一個時間段劃分成幾個時間片段(時間區間),然后在這幾個時間區間之間來回切換,由于CPU處理的速度非常快,只要時間間隔處理得當,即可讓用戶感覺是多個應用程式同時在進行,如:打游戲和聽音樂兩件事情在同一個時間段內都是在同一臺電腦上完成了從開始到結束的動作,那么,就可以說聽音樂和打游戲是并發的,
并發現象可以通過一些手段去處理以此提高性能,也可以通過并行的方法去解決!你用并行去解決,并不代表它一定是通過并行解決的,因為你只管寫,用不用是作業系統決定的,可能用的還是串行的方式解決的,自己很大可能是并行,
并行:當系統有一個以上CPU時,當一個CPU執行一個行程時,另一個CPU可以執行另一個行程,兩個行程互不搶占CPU資源,可以同時進行,可以同時通過多行程/多執行緒的方式取得多個任務,并以多行程或多執行緒的方式同時執行這些任務,這種方式我們稱之為并行(Parallel),
其實決定并行的因素不是CPU的數量,而是CPU的核心數量,比如一個CPU多個核也可以并行,
并行的效率從代碼層次上強依賴于多行程/多執行緒代碼,從硬體角度上則依賴于多核CPU,單核是無法實作真正的并行的,注意(某些CPU有多執行緒支持,一顆CPU核心可以用不同的邏輯電路同時執行兩個執行緒)
串行:
串行表示所有任務都一一按先后順序進行,完成A才會進行B
資料少的時候,串行效率是
有一個很好的博客說明 關于串行和并行、并發的講解
有這么一句話
當程式中寫下多行程或多執行緒代碼時,這意味著的是并發而不是并行,并發是因為多行程/多執行緒都是需要去完成的任務,不并行是因為并行與否由作業系統的調度器決定,可能會讓多個行程/執行緒被調度到同一個CPU核心上,只不過調度演算法會盡量讓不同行程/執行緒使用不同的CPU核心,所以在實際使用中幾乎總是會并行,但卻不能以100%的角度去保證會并行,也就是說,并行與否程式員無法控制,只能讓作業系統決定,(可以看出并發是一種現象,)
行程
一個行程好比是一個程式,它是 資源分配的最小單位 ,同一時刻執行的行程數不會超過核心數,不過如果問單核CPU能否運行多行程?答案又是肯定的,(這里其實是并發)單核CPU也可以運行多行程,只不過不是同時的,而是極快地在行程間來回切換實作的多行程,舉個簡單的例子,就算是十年前的單核CPU的電腦,也可以聊QQ的同時看視頻,電腦中有許多行程需要處于「同時」開啟的狀態,而利用CPU在行程間的快速切換,可以實作「同時」運行多個程式,而行程切換則意味著需要保留行程切換前的狀態,以備切換回去的時候能夠繼續接著作業,所以行程擁有自己的地址空間,全域變數,檔案描述符,各種硬體等等資源,作業系統通過調度CPU去執行行程的記錄、回復、切換等等,
行程具有的特征:
1)動態性:行程是程式的一次執行程序,是臨時的,有生命期的,是動態產生,動態消亡的;
2)并發性:任何行程都可以同其他行程一起并發執行;
3)獨立性:行程是系統進行資源分配和調度的一個獨立單位;
4)結構性:行程由程式、資料和行程控制塊三部分組成,
3.2 為什么要有多行程?
多行程目的:提高cpu的使用率,
一個例子:一個用戶現在既想使用列印機,又想玩游戲,
假設只有一個行程(先不談多執行緒):從作業系統的層面看,我們使用列印機的步驟有如下:
1)使用CPU執行程式,去硬碟讀取需要列印的檔案,然后CPU會長時間的等待,直到硬碟讀寫完成;2)使用CPU執行程式,讓列印機列印這些內容,然后CPU會長時間的等待,等待列印結束,在這樣的情況下:其實CPU的使用率其實非常的低,列印一個檔案從頭到尾需要的時間可能是1分鐘,而cpu使用的時間總和可能加起來只有幾秒鐘,而后面如果單行程執行游戲的程式的時候,CPU也同樣會有大量的空閑時間,使用多行程后:當CPU在等待硬碟讀寫檔案,或者在等待列印機列印的時候,CPU可以去執行游戲的程式,這樣CPU就能盡可能高的提高使用率,再具體一點說,其實也提高了效率,因為在等待列印機的時候,這時候顯卡也是閑置的,如果用多行程并行的話,游戲行程完全可以并行使用顯卡,并且與列印機之間也不會互相影響,
行程,直觀點說:保存在硬碟上的程式運行以后,會在記憶體空間里形成一個獨立的記憶體體,這個記憶體體有自己獨立的地址空間,有自己的堆,上級掛靠單位是作業系統,
作業系統會以行程為單位,分配系統資源(CPU時間片、記憶體等資源),行程是資源分配的最小單位,
執行緒
執行緒是作業系統級別的
是行程的一個物體,是獨立運行和獨立調度的基本單位(CPU上真正運行的是執行緒),執行緒自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程式計數器,一組暫存器和堆疊),但是它可與同屬一個行程的其他的執行緒共享行程所擁有的全部資源,執行緒間的通信是要比行程間的通信,代價小很多,所以一個軟體,用多執行緒可以大大縮短處理一個事情的時間,
協程
協程是用戶、編輯器級別的
協程是一種用戶態的輕量級執行緒,協程的調度完全由用戶控制,協程擁有自己的暫存器背景關系和堆疊,協程調度切換時,將暫存器背景關系和堆疊保存到其他地方,在切回來的時候,恢復先前保存的暫存器背景關系和堆疊,直接操作堆疊則基本沒有內核切換的開銷,可以不加鎖的訪問全域變數,所以背景關系的切換非常快, 協程在子程式內部可中斷的,然后轉而執行別的子程式,在適當的時候再回傳來接著執行,
區別
行程與執行緒的區別
行程是CPU資源分配的基本單位,執行緒是獨立運行和獨立調度的基本單位(CPU上真正運行的是執行緒),
行程擁有自己的資源空間,一個行程包含若干個執行緒,執行緒與CPU資源分配無關,多個執行緒共享同一行程內的資源,
執行緒的調度與切換比行程快很多,所以一個軟體或一大堆資料一般是多執行緒處理,
1.執行緒是行程的一部分,執行緒也被稱為輕量級行程,一個行程至少有一個執行緒(主執行緒),
2. 行程擁有獨立的記憶體單元,而屬于這個行程的執行緒共享該記憶體(包括代碼段、資料集、堆等)及一些行程級的資源(如打開檔案和信號),某行程內的執行緒在其它行程不可見;以及行程所擁有的全部資源(執行緒有自己的堆疊和區域變數,但執行緒之間沒有單獨的地址空間),
3. 一個行程崩潰不會影響其他行程,一個執行緒掛掉會影響其他執行緒甚至導致整個行程掛掉(因為2),
4. 行程切換開銷大,執行緒切換開銷小(因為2),
5. 行程的并發性沒有執行緒高
多執行緒程式只要有一個執行緒死掉,整個行程也死掉了 這是為什么?
每個行程都有自己的一個單獨的記憶體空間,屬于這個行程的執行緒共享行程記憶體,該執行緒如果持有互斥資源得不到釋放,會影響整個行程的正常執行,所以干脆就讓整個行程掛掉
為什么作業系統不用協程
無論是空間還是時間性能都比行程(執行緒)好這么多,那么Linus為啥不把它在作業系統里實作了多好?作業系統為了實作實時性更好的目的,對一些優先級比較高的行程是會搶占其它行程的CPU的,而協程無法實作這一點,還得依賴于擋前使用CPU的協程主動釋放,于作業系統的實作目的不相吻合,所以協程的高效是以犧牲可搶占性為代價的,
執行緒和協程區別

協程缺點:無法利用多核資源:協程的本質是個單執行緒,它不能同時將 單個CPU 的多個核用上,協程需要和行程配合才能運行在多CPU上.當然我們日常所撰寫的絕大部分應用都沒有這個必要,除非是cpu密集型應用,進行阻塞(Blocking)操作(如IO時)會阻塞掉整個程式
總結
一個軟體一般是開一個行程(因為開多個行程,那么通信過于麻煩,而且消耗大),所以一個行程里開多個執行緒,執行緒有個問題就是,可能同時訪問到同一個資料,所以有每個執行緒里又可以開多個協程,
首先,執行緒是為了實作并行,一個大資料,需要處理3小時,通過多執行緒處理后可能只需要1小時,但是會有一個問題,執行緒是搶奪式的,兩個執行緒可能會在同一時刻,訪問到同一個資料,那么會導致沖突,比如,A執行緒里執行到第2行代碼的時候下一步直接去執行了B執行緒的第2行資料,然后又訪問了A執行緒的第3行,這樣會出現很多問題,所以就出現了協程,協程是協作式的,不會像多個執行緒一樣,系統控制時間片,多個執行緒同一刻(多核)或者同一時間段(單核單執行緒)去瘋狂,他是由你控制的,你可以拿著不放,處理完自己的事情后再交出執行權,這樣就會避免那些問題
看一下這個文章
下面貼一個大神寫的東西,講解了行程、執行緒、協程的發展由來
行程、執行緒、協程的發展由來
這里直接粘貼一下
很多技術上的東西,你攔腰來一下或者在末梢上抓幾個典型,就容易簡單問題復雜化,這篇文章的問題就在于此:作者攔腰從“用協程優化執行緒”這個奇怪的中間應用場景開始,沒頭沒尾開始“揭露真相”;然后又有很多人從末梢開始,引入“協程本質上是狀態機”、“協程用于解決callback hell”、“協程在IO密集型業務中才能發揮威力”等莫名其妙卻又言之鑿鑿的結論,
于是,事情就越發復雜化,越發不可解決了,
其實,這類問題有一個很簡單的解決辦法,那就是——追根溯源,
徹底追到根子上,看看行程、執行緒、協程究竟是怎么回事——這看起來是繞了彎路,大家想解決實際問題呢,你這不識相的卻跑去挖什么純理論去了……
不過,如果您肯耐著性子,跟我到它們誕生的那個時刻看一看,一切就迎刃而解了,
行程是什么,我們都知道,這里就不多解釋了,
然后,我們還知道,早年的Windows 3.x是非搶奪式多任務,也叫協作式多任務,
這種多任務方式存在一個很大的弊端,那就是必須等一個行程主動讓出執行權,其它行程才有機會執行,
如果當前行程不讓(比如陷入死回圈、比如呼叫非阻塞API回圈死等總是不來的網路報文、比如用錯誤的介面回圈死等讀取硬體故障的磁盤),那么整個系統就會陷入癱瘓,
從Windows 95開始,微軟切換到了搶奪式多任務:每個行程給你一個時間片,用完就強制休眠;甚至時間片未到但更緊急的事件出現了,也會強制低優先級行程休眠,
搶奪式多任務的確比非搶奪式多任務可靠得多,一個水貨寫的程式把事情搞砸了,其他人可以不受影響,系統會強制剝奪它的執行權,確保正常程式仍然有機會執行,
亦因此,Windows 95是一個里程碑,它標志著一個真正支持多行程的作業系統出現了,
記住下面這兩個概念,它們很重要,
協作式多任務:多個行程獨立運行,但每個行程都要發揚風格,不執行規模過大的計算;或者執行規模較大的計算時,每隔一段時間主動呼叫一下OS提供的特定API,讓出控制權給其它行程,
總之,人之初,性本善,每個人都替別人著想,世界就會很美好,
那萬一出個惡人、病人呢?
世界崩塌了,
搶奪式多任務:系統里跑的程式,有的是壞人寫的,也有的會意外病倒,作業系統要監控所有行程,公平分配CPU時間片等資源給每個應用;如果一個應用用完了自己的份額,那么作業系統就要強制暫停它的執行,保存它的執行現場,把CPU安排給另一個行程——從而避免壞行程/病態行程影響系統的正常運行,
現在,作業系統把你們都當壞人防著,你就是故意寫流氓軟體,也不可能輕易就把別人“憋死”了,
無論是協作式多任務還是搶奪式多任務,外在表現上都是“用戶可以同時運行多個程式,可以前臺開著字處理軟體,后臺放著音樂,另外還有個聊天工具藏在幕后……”
但隨著計算機技術的發展,多CPU系統越發普及;就連桌面CPU都悄悄開始雙核化,
那么這時候,我們就會想到很多很酷的應用場景,比如說,可以一邊從網上下載電影,一邊開個視頻播放器觀看,
但這樣就出現了很多新問題:如果下載電影的行程速度更慢,視頻播放器讀到尚未填充有效資料的區域怎么辦?
或者,電影下載行程下了100k資料,一校驗,是壞的,結果視頻播放器快手快腳拿過去就放;剛放了一幀,電影下載行程作廢了這段資料,把讀指標跳回到100k前;而電影播放器行程呢,它還保留著一個無效的讀指標……
這時候,程式員就不得不做很多的同步作業,成本過高
這可不是一個小工程,你得先約定共享記憶體/共享檔案格式,約定控制資料存盤位置(當前有效資料首尾指標等資訊);做好約定,確保雙方都能找到鎖;鎖是讀寫鎖還是簡單的mutex……等等等等,
除非同一個團隊做,不然想要配合默契,顯然是極難極難的,
但既然是同一個團隊做,其實沒必要搞成兩個行程,沒必要動用復雜的行程間通訊機制——沒錯,兩個行程可以分別在兩個CPU核心上跑,更充分的利用硬體資源;但作業系統也可以允許一個行程存在兩個執行緒啊,
于是,執行緒誕生,
有了行程設計的經驗,執行緒自然一開始就搞的非常完善:行程和執行緒都要在OS里面注冊,這才能接受OS的調度,充分利用多顆CPU核心(或者,某些CPU有多執行緒支持,一顆CPU核心可以用不同的邏輯電路同時執行兩個執行緒),
兩者的區別是,行程持有資源,一旦退出,行程申請的各種資源都會被OS強制回收;而執行緒依附于行程,資源不和它系結,
不僅如此,從一開始,OS就汲取了過去的教訓,把執行緒也做成了搶奪式多任務,
但直接把搶奪式多任務思路延續到執行緒,問題就來了,
OS里面,不同行程是諸多水平參差不齊、目的各異(甚至本就存心做流氓軟體)的人設計的,因此設定一個硬杠杠,時機成熟強制剝奪其執行權,這是極其必要的,
而同一個行程里面的一組執行緒,它們必然來自于同一個設計團隊,哪怕他們用了第三方庫,其中的執行緒也全都在這個團隊控制之下,因此“水平良莠不齊”“存在惡意”也就無從談起了,
一旦不需要對付“水貨”和“壞分子”,搶奪式多任務帶來的好處就沒那么重要了;而“搶奪”造成的“執行時序紊亂”問題越發突出,
搶奪式多任務的壞處
比如,A執行緒負責從網上下載內容;每下載一段、校驗無誤,它就要更新一下共享記憶體(這可能僅僅是一個把一塊資料掛到鏈表末尾的操作)——如果不存在“搶奪”,那么什么時候它把內容更新完了,什么時候主動讓出控制權,那就不會有任何問題,
但一旦存在搶奪,A執行緒就可能在剛剛執行到“修改了鏈表的末指標、但尚未來得及修改最后一塊的前向指標”時,被OS強制剝奪執行權;而B執行緒負責播放,它剛讀到這塊資訊,用戶點了“回退5秒鐘”,于是它循著A執行緒尚未來得及修改正確的前向指標,跑不知哪里去了……
造成了資料沖突
哪怕在單核單執行緒CPU上跑,(注意多執行緒是可以new出來的,并不收到物理核心的限制,單核也可以在代碼中創出多執行緒,只是效果不是真正的多執行緒)這都會造成各種意想不到的執行序紊亂問題,
因此,程式員們不得不在使用共享資料時加鎖,確保自己不會把事情搞砸,
此時,非搶占式多任務的好處就出來了:大家都一家人,都想齊心合力把事情做好;因此,當“我”事情沒做完而且并不會耽誤太久時,你們就應該等我;而一旦我事情做完了、或者需要等待網路信號/磁盤準備好時,“我”也會痛快的主動交出控制權,
這個做法,使得協作式多任務之間執行權的交接點極為明晰;那么只要邏輯考慮清楚了,鎖就是完全沒必要的——反正不會搶奪嘛,事情沒告一段落我就不會交執行權;交執行權之前確保不存在“懸置的、未確定未提交的修改”,臟讀臟寫就杜絕了,
因此,協程這個概念的提出,使得程式邏輯更為清晰,執行更加可控,
協程實質上是一種在用戶空間實作的協作式多執行緒架構,
它不能讓OS知道自己的存在,無法利用多核CPU/CPU的多執行緒支持;但這恰恰是它的優點,
注意,我在這里的措辭是“協程不能讓OS知道自己的存在”,
這是因為,OS并沒有協程支持;如果你想讓OS知道你的存在,那么它就會把你當執行緒調度——于是搶占式多任務就又回來了,“協程”這個“協”字就名不副實了,
為什么說這個“無法在CPU上并行”的束縛恰恰是協程的優點呢?
因為它是協作式多任務,不存在執行緒紊亂的可能,
沒錯,每次執行中,協程之間的具體執行順序可能千變萬化;但協程執行權切換卻只會發生在用戶明確放棄執行權之后——比如你明確執行了yield陳述句時,
當然,如果你非要先修改鏈表后向指標、改完了yield一下然后才去修改鏈表前向指標,那誰都救不了你,
記住,除非你確定現在的共享資料不怕被其它協程查看/更改,否則不要在共享資料修改完成前隨便放棄你的執行權,
當然,多數情況下,使用協程是為了滿足“開個小差做點別的”的同時,不希望阻塞主要執行緒,這種簡單應用場景多半也沒有什么資料需要共享,
一旦挖到根子,是不是一下子所有的一切都清晰起來了呢?
現在,讓我們回頭看看這些討論吧,
1、如果資源存在相互依賴,執行緒是否有必要存在?
答:那要看什么依賴,
比如,我遇到過的一個案例:一組執行緒負責從磁盤上加載大量日志(可達數百G);第二組執行緒分頭分析日志;第三組執行緒把日志分析后得到的結果通過網路發送出去,
那么,在這個場景里,雖然執行緒組2嚴重依賴于執行緒組1載入的資料,執行緒組3又完全依賴于執行緒組2的輸出內容;但使用執行緒是絕對有必要的,
這是因為,執行緒組1是磁盤密集型任務,不占用多少CPU;而執行緒組2是CPU密集型任務,它和IO無關;最后的執行緒組3呢,它專心和網卡打交道……
在這個典型的生產者-消費者模型里,三組執行緒齊頭并進,就可以把服務器的磁盤、CPU、網卡同時利用起來,最大化執行效率,
當然,當初的設計者鬧了個大笑話,他讓執行緒組1先載入若干G資料,載入完畢之前禁止執行緒組2運行;等載入結束,執行緒組1停止運行,等執行緒組2分析資料;分析完,所有執行緒組2的執行緒全部停止執行了,才啟動執行緒組3發報;等執行緒組3忙完,這才再次啟動執行緒組1,
這個設計很可笑,
該并行的,他給弄的徹底不并行了,磁盤不忙完,CPU只能干瞪眼;CPU沒搞定,網卡只能空閑,
不該并行的,他卻強制并行了:磁盤讀取時,一大窩執行緒亂紛紛你搶我奪,嚴重拖慢效率;忙完了,不用搶了,讓磁盤閑著,交給執行緒組2,又是一窩蜂的爭奪記憶體/CPU訪問權;然后磁盤CPU都閑著,看一窩新的執行緒你爭我奪的折騰網卡……
所以你看,壓根不是執行緒好協程壞或者協程穿沒穿衣服的問題,
問題的關鍵點在于,究竟在哪些地方并行可以提高效率?哪些地方并行反而損失效率?如何做出一個精確、智能的設計,使得框架可以自動安排合理數目的執行緒,把磁盤、CPU、網卡同時利用起來?
顯然,多路IO場景下,協程已經可以同時發起多個讀取請求;那么如果系統有多塊網卡、多塊磁盤,OS自然會并行利用它——因為這些介面本來就是異步的(呼叫同步介面會導致整個行程被掛起,別這樣做),OS會自動給它排隊,能并行就安排并行了,
但是,想充分利用CPU核心,你就必須用執行緒,
比如前面的案例中,第一三兩組執行緒就可以用協程替代;但第二組執行緒就必須是執行緒,且一三兩組協程都應該在一個單獨的執行緒里,不能共享第二組執行緒,
2、回呼地獄問題
這貨和協程沒什么關系,也就是寫起來更好看罷了,
事實上,在這個示例中,改成協程反而會導致語意改變,引出時序相關bug來:
foreach session:
v1 = io()
if v1.is_good:
v2 = io()
if v2.is_good:
v3 = io()
if v3.is_good:
v4 = io()
if v4.is_good:
handle(v1, v2, v3, v4)
這段的語意本來是,v1先做io,得到good結果后,v2再做io,以此類推,
如果機械的改成協程,那么就成了v1~v4同時io,然后因為不滿足時序要求大量失敗,
除非v1~v4本就可以并行;但此時用執行緒/協程都一樣,只是協程寫起來更簡單一點罷了,
協程不是狀態機,除非你精心設計了它的狀態,“看起來像”和“是”差了十萬八千里,
總結:協程是一種拋棄了在CPU上并行執行能力的、協作式多任務的執行框架,
這個設計使得你可以像執行緒一樣使用它,卻無需擔心棘手的資料相關問題,
因為它的執行權交接在你的控制之下,你不交出控制權,別人就不能強插一腳,
借助“遇到等待主動交控制權”這個訣竅,你可以用協程避免一個單執行緒程式阻塞,
只要你記得在合適時機主動交出控制權,不要呼叫系統提供的、可能阻塞的API(而是使用協程庫提供的非阻塞版、或者使用協程庫推薦寫法),你甚至可以讓磁盤、CPU、網路等不同設備并行運行——這本就是作業系統給你提供一個虛擬的、可并行界面的背后原理,你的作業系統原理學的扎實,那么到這里就不會迷惑,
你可以用執行緒做“領班”,借助多執行緒充分利用CPU;同時又在每條執行緒內部,借助協程并行IO、或者無阻塞的執行互不相關的一組任務——比如,累加一大堆資料,同時每隔100ms更新主界面上的顯示,
但要注意,不要在不同執行緒間共享同一個協程控制器,那會把搶奪式多任務的“執行權隨時切換”這個“惡魔”帶進協程空間,破壞掉“協作式多任務”這個基本保證,
除非你的協程庫允許你這么做,
但哪怕協程庫允許,你最好還是不要這么做,因為為了保證協程語意,共享了協程控制器的執行緒們很可能被這個庫用鎖給“傳染”成“協程”——除非協程庫作者給你詳細說明,告訴你怎樣做才能既不受共享資料被破壞之害、又能享受真正的并行之利,
隨便提一句,不要用這種“強大”的協程庫,
這種庫的作者多半喜歡無意義的炫技,對真正有需求的人來說,自己造輪子可比用這種脫褲子放屁的“高級功能”簡單直白太多了,
抽象到這種程度,無論實作還是介面都會變得太過復雜,是對“依賴倒置原則”的嚴重違背——不僅不能體現其技術水平,反倒暴露出不懂介面設計的缺陷來,
還是開頭那段話:技術問題,最好返璞歸真,
離根子越近,花里胡哨的東西越少,封裝越簡單、越清晰、越質樸,它才越可靠、越好用,
反之,拉進來的東西越多,就代表這人的頭腦越不清醒,出問題的可能就越大——比如說協程拉進來狀態機/回呼地獄的,顯然就對協程的本質缺乏了解,
最后,出一道思考題,把它做出來,你才會真正明白執行緒和協程的本質區別,
我曾提到,寫一個網路代理軟體實質上就是簡單的把一個網卡過來的資料轉給另一個網卡(也可以是虛擬網路設備,比如tun/tap設備),而想要這個資料轉發高效、低延遲,就應該把它寫成單執行緒,
這是因為,如果你分別用多個執行緒處理多個網卡的收發,那么一旦網路繁忙,且CPU也比較忙的話,那么很可能其中一條執行緒就要滿負荷跑滿一個時間片;在這個執行緒被剝奪執行權之前,另一個執行緒可能得不到執行機會,于是造成資料經過代理后ping值不穩定問題,
而用單執行緒搞呢,你可以給它一個較高的優先級,使得有網路報文它就立即被喚醒執行;不把報文處理完就不交控制權,
那么,只要你程式寫對了,它就一定能用最高的效率完成資料轉發作業,
那么,這道思考題就是:不允許使用協程,你如何在一個普通的單執行緒C程式里,用一個while回圈,做到多塊網卡并行作業,既不阻塞自己、又會在沒有報文時主動交出執行權、不空耗時間片呢?
(一個拿了實時優先級的程式空耗時間片可是個非常非常嚴重的問題,隨時可能讓整個OS崩掉的那種,)
這個問題很簡單,查查socket相關資料,推敲推敲各個介面引數,你自然就知道該怎么辦了(提示:需要綜合硬體中斷原理、OS調度原理等知識),
但它極其重要,
能想通這個,關于協程的討論才會有的放矢,
看完上面的文章后,注意總結:
執行緒 的發明主要是實作 Parallelism(并行) 問題,減少資料處理時間,,而協程 的發明主要是為了解決 Concurrency(并發)引起的資料沖突問題 ,
這句話什么意思,首先,執行緒是為了實作并行,一個大資料,需要處理3小時,通過多執行緒處理后可能只需要1小時,但是會有一個問題,執行緒是搶奪式的,兩個執行緒可能會在同一時刻,訪問到同一個資料,那么會導致沖突,比如,A執行緒里執行到第2行代碼的時候下一步直接去執行了B執行緒的第2行資料,然后又訪問了A執行緒的第3行,這樣會出現很多問題,所以就出現了協程,協程是協作式的,不會像多個執行緒一樣,系統控制時間片,多個執行緒同一刻(多核)或者同一時間段(單核單執行緒)去瘋狂,他是由你控制的,你可以拿著不放,處理完自己的事情后再交出執行權,這樣就會避免那些問題
Unity中的協程、執行緒
執行緒
Unity3D是以生命周期主執行緒回圈進行游戲開發,
Unity3D中的子執行緒無法運行Unity SDK(開發者工具包,軟體包、軟體框架)和API(應用程式編程介面,函式庫),
限制原因:大多數游戲引擎都是主回圈結構,游戲中邏輯更新和畫面更新的時間點要求有確定性,必須按照幀序列嚴格保持同步,否則就會出現游戲中的物件不同步的現象,雖然多執行緒也能保證這個效果,但是參考多執行緒,會加大同步處理的難度與游戲的不穩定性,
但是多執行緒也是有好處的,如果不是畫面更新,也不是常規的邏輯更新(指包括AI、物理碰撞、角色控制這些),而是一些其他后臺任務,比如大量耗時的資料計算、網路請求、復雜密集的I/O操作,則可以將這個獨立出來做成一個作業執行緒,這需要寫Unity游戲的Native擴展,
協程
對于Unity3D,它是生命周期主執行緒回圈的設計,它更傾向于使用Time slicing(時間分片)的Coroutine(協程)去完成異步任務,融合到生命周期中,
執行緒是作業系統級別的概念,現代作業系統都支持并實作執行緒,執行緒的調度對應用開發者是透明的,開發者無法預期某執行緒在何時被調度執行,基于此,一般那種隨機出現的BUG,多與執行緒調度相關,
而協程Coroutine是編譯器級別的,本質是一個執行緒時間片去執行代碼段,它通過相關的代碼使得代碼段能夠實作分段式的執行,顯式呼叫yield函式后才被掛起,重新開始的地方是yield掛起的位置,每一次執行協程會跑到下一個yield陳述句,協程能保留上一次呼叫時的狀態(即所有區域狀態的一個特定組合),每次程序重入時,就相當于進入上一次呼叫的狀態,換種說法:進入上一次離開時所處邏輯流的位置,
在Unity3D中,協程是可自行停止運行 (yield),直到給定的 YieldInstruction 結束再繼續運行的函式,協程 (Coroutines) 的不同用途: ·
(1) yield return null - 這一幀到此暫停,下一幀再從暫停處繼續,常用于回圈中,
(2) yield return new WaitForEndOfFrame - 等到這一幀的cameras和GUI渲染結束后再從此處繼續,即等到這幀的末尾再往下運行,這行之后的代碼還是在當前幀運行,是在下一幀開始前執行,跟return null很相似,
(3) yield return new WaitForFixedUpdate - 在下一次執行FixedUpdate的時候繼續執行這段代碼,即等一次物理引擎的更新,
(4) yield return new WaitForSeconds(3.0f) - 等待3秒,然后繼續從此處開始,常用于做定時器,
(5) yield return WWW - 等待直至異步下載完成,
(6) yield return StartCoroutine(methodName) - 等待另一個協程執行完,這是把協程串聯起來的關鍵,常用于讓多個協程按順序逐個運行,
(7)yield break - 直接跳出協程,對某些判定失敗必須跳出的時候,比如加載AssetBundle的時候,WWW失敗了,后邊加載bundle沒有必要了,這時候可以yield break跳出,
值得注意的是 WaitForSeconds()受Time.timeScale影響,當Time.timeScale = 0f 時,yield return new WaitForSecond(x) 將不會滿足,
以下為Unity3D的生命周期回圈圖
c#代碼示例
Unity3D使用協程常需要用到輔助類Stopwatch,提供一組可用于準確地測量運行時間的方法和屬性,
private Stopwatch frameStopwatch;//用來記錄上一幀結束到現在所用的時間
private float targetFrameDuration;//自定義的每幀持續時間,防止協程過度消耗執行緒時間片
private void Awake()
{
frameStopwatch = new Stopwatch();
}
void Update()
{
//計算每一幀所用的時間Start()之后Elapsed會一直增加,Stop()之后Elapsed的值就不變
frameStopwatch.Stop();
frameStopwatch.Reset();
frameStopwatch.Start();
if(ChunkUpdateList.Count > 0)
{
StartCoroutine(ProcessChunkQueueLoop());//啟動協程處理ChunkUpdateList
}
}
private IEnumerator ProcessChunkQueueLoop()
{
while (ChunkUpdateList.Count > 0)
{
ProcessChunkUpdateList();//每次處理ChunkUpdateList中的一個資料
if (frameStopwatch.Elapsed.TotalSeconds >= targetFrameDuration)//這一幀已經運行的時間frameStopwatch.Elapsed.TotalSeconds已經超過自定義每幀的時間targetFrameDuration,則掛起直到這一幀結束再運行
{
yield return new WaitForEndOfFrame();
}
}
}
參考:
(1)游戲引擎Unity中的單執行緒與多執行緒
其他:
(1)游戲主回圈
(2)3D引擎多執行緒:渲染與邏輯分離
```
轉載于:https://www.cnblogs.com/DonYao/p/8571981.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/293257.html
標籤:其他
上一篇:MQTT協議與EMQ

