成都創(chuàng)新互聯(lián)公司長期為成百上千家客戶提供的網(wǎng)站建設服務,團隊從業(yè)經(jīng)驗10年,關注不同地域、不同群體,并針對不同對象提供差異化的產(chǎn)品和服務;打造開放共贏平臺,與合作伙伴共同營造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為閬中企業(yè)提供專業(yè)的網(wǎng)站制作、成都網(wǎng)站建設,閬中網(wǎng)站改版等技術服務。擁有十年豐富建站經(jīng)驗和眾多成功案例,為您定制開發(fā)。
坐標上海松江高科技園,誠聘高級前端工程師/高級 Java 工程師,有興趣的看 JD:https://www.lagou.com/jobs/6361564.html
在 《Awesome Interviews》 歸納的常見面試題中,無論前后端,并發(fā)與異步的相關知識都是面試的中重中之重,《并發(fā)編程》系列即對于面試中常見的并發(fā)知識再進行回顧總結;你也可以前往 《Awesome Interviews》,在實際的面試題考校中了解自己的掌握程度。也可以前往《Java 實戰(zhàn)》、《Go 實戰(zhàn)》等了解具體編程語言中的并發(fā)編程的相關知識。
在未配置 OS 的系統(tǒng)中,程序的執(zhí)行方式是順序執(zhí)行,即必須在一個程序執(zhí)行完后,才允許另一個程序執(zhí)行;在多道程序環(huán)境下,則允許多個程序并發(fā)執(zhí)行。程序的這兩種執(zhí)行方式間有著顯著的不同。也正是程序并發(fā)執(zhí)行時的這種特征,才導致了在操作系統(tǒng)中引入進程的概念。進程是資源分配的基本單位,線程是資源調(diào)度的基本單位。
應用啟動體現(xiàn)的就是靜態(tài)指令加載進內(nèi)存,進而進入 CPU 運算,操作系統(tǒng)在內(nèi)存開辟了一段棧內(nèi)存用來存放指令和變量值,從而形成了進程。早期的操作系統(tǒng)基于進程來調(diào)度 CPU,不同進程間是不共享內(nèi)存空間的,所以進程要做任務切換就要切換內(nèi)存映射地址。由于進程的上下文關聯(lián)的變量,引用,計數(shù)器等現(xiàn)場數(shù)據(jù)占用了打段的內(nèi)存空間,所以頻繁切換進程需要整理一大段內(nèi)存空間來保存未執(zhí)行完的進程現(xiàn)場,等下次輪到 CPU 時間片再恢復現(xiàn)場進行運算。
這樣既耗費時間又浪費空間,所以我們才要研究多線程。一個進程創(chuàng)建的所有線程,都是共享一個內(nèi)存空間的,所以線程做任務切換成本就很低了?,F(xiàn)代的操作系統(tǒng)都基于更輕量的線程來調(diào)度,現(xiàn)在我們提到的“任務切換”都是指“線程切換”。
本部分節(jié)選自 《Linux 與操作系統(tǒng)/進程管理》。
在未配置 OS 的系統(tǒng)中,程序的執(zhí)行方式是順序執(zhí)行,即必須在一個程序執(zhí)行完后,才允許另一個程序執(zhí)行;在多道程序環(huán)境下,則允許多個程序并發(fā)執(zhí)行。程序的這兩種執(zhí)行方式間有著顯著的不同。也正是程序并發(fā)執(zhí)行時的這種特征,才導致了在操作系統(tǒng)中引入進程的概念。進程是資源分配的基本單位,線程是資源調(diào)度的基本單位。
進程是操作系統(tǒng)對一個正在運行的程序的一種抽象,在一個系統(tǒng)上可以同時運行多個進程,而每個進程都好像在獨占地使用硬件。所謂的并發(fā)運行,則是說一個進程的指令和另一個進程的指令是交錯執(zhí)行的。無論是在單核還是多核系統(tǒng)中,可以通過處理器在進程間切換,來實現(xiàn)單個 CPU 看上去像是在并發(fā)地執(zhí)行多個進程。操作系統(tǒng)實現(xiàn)這種交錯執(zhí)行的機制稱為上下文切換。
操作系統(tǒng)保持跟蹤進程運行所需的所有狀態(tài)信息。這種狀態(tài),也就是上下文,它包括許多信息,例如 PC 和寄存器文件的當前值,以及主存的內(nèi)容。在任何一個時刻,單處理器系統(tǒng)都只能執(zhí)行一個進程的代碼。當操作系統(tǒng)決定要把控制權從當前進程轉移到某個新進程時,就會進行上下文切換,即保存當前進程的上下文、恢復新進程的上下文,然后將控制權傳遞到新進程。新進程就會從上次停止的地方開始。
在虛擬存儲管理一節(jié)中,我們介紹過它為每個進程提供了一個假象,即每個進程都在獨占地使用主存。每個進程看到的是一致的存儲器,稱為虛擬地址空間。其虛擬地址空間最上面的區(qū)域是為操作系統(tǒng)中的代碼和數(shù)據(jù)保留的,這對所有進程來說都是一樣的;地址空間的底部區(qū)域存放用戶進程定義的代碼和數(shù)據(jù)。
程序代碼和數(shù)據(jù),對于所有的進程來說,代碼是從同一固定地址開始,直接按照可執(zhí)行目標文件的內(nèi)容初始化。
堆,代碼和數(shù)據(jù)區(qū)后緊隨著的是運行時堆。代碼和數(shù)據(jù)區(qū)是在進程一開始運行時就被規(guī)定了大小,與此不同,當調(diào)用如 malloc 和 free 這樣的 C 標準庫函數(shù)時,堆可以在運行時動態(tài)地擴展和收縮。
共享庫:大約在地址空間的中間部分是一塊用來存放像 C 標準庫和數(shù)學庫這樣共享庫的代碼和數(shù)據(jù)的區(qū)域。
棧,位于用戶虛擬地址空間頂部的是用戶棧,編譯器用它來實現(xiàn)函數(shù)調(diào)用。和堆一樣,用戶棧在程序執(zhí)行期間可以動態(tài)地擴展和收縮。
在現(xiàn)代系統(tǒng)中,一個進程實際上可以由多個稱為線程的執(zhí)行單元組成,每個線程都運行在進程的上下文中,并共享同樣的代碼和全局數(shù)據(jù)。進程的個體間是完全獨立的,而線程間是彼此依存的。多進程環(huán)境中,任何一個進程的終止,不會影響到其他進程。而多線程環(huán)境中,父線程終止,全部子線程被迫終止(沒有了資源)。
而任何一個子線程終止一般不會影響其他線程,除非子線程執(zhí)行了 exit()
系統(tǒng)調(diào)用。任何一個子線程執(zhí)行 exit()
,全部線程同時滅亡。多線程程序中至少有一個主線程,而這個主線程其實就是有 main 函數(shù)的進程。它是整個程序的進程,所有線程都是它的子線程;我們通常把具有多線程的主進程稱之為主線程。
線程共享的環(huán)境包括:進程代碼段、進程的公有數(shù)據(jù)、進程打開的文件描述符、信號的處理器、進程的當前目錄、進程用戶 ID 與進程組 ID 等,利用這些共享的數(shù)據(jù),線程很容易的實現(xiàn)相互之間的通訊。線程擁有這許多共性的同時,還擁有自己的個性,并以此實現(xiàn)并發(fā)性:
線程 ID:每個線程都有自己的線程 ID,這個 ID 在本進程中是唯一的。進程用此來標識線程。
寄存器組的值:由于線程間是并發(fā)運行的,每個線程有自己不同的運行線索,當從一個線程切換到另一個線程上時,必須將原有的線程的寄存器集合的狀態(tài)保存,以便 將來該線程在被重新切換到時能得以恢復。
線程的堆棧:堆棧是保證線程獨立運行所必須的。線程函數(shù)可以調(diào)用函數(shù),而被調(diào)用函數(shù)中又是可以層層嵌套的,所以線程必須擁有自己的函數(shù)堆棧, 使得函數(shù)調(diào)用可以正常執(zhí)行,不受其他線程的影響。
錯誤返回碼:由于同一個進程中有很多個線程在同時運行,可能某個線程進行系統(tǒng)調(diào)用后設置了 errno 值,而在該 線程還沒有處理這個錯誤,另外一個線程就在此時 被調(diào)度器投入運行,這樣錯誤值就有可能被修改。 所以,不同的線程應該擁有自己的錯誤返回碼變量。
線程的信號屏蔽碼:由于每個線程所感興趣的信號不同,所以線程的信號屏蔽碼應該由線程自己管理。但所有的線程都共享同樣的信號處理器。
當線程在用戶空間下實現(xiàn)時,操作系統(tǒng)對線程的存在一無所知,操作系統(tǒng)只能看到進程,而不能看到線程。所有的線程都是在用戶空間實現(xiàn)。在操作系統(tǒng)看來,每一個進程只有一個線程。過去的操作系統(tǒng)大部分是這種實現(xiàn)方式,這種方式的好處之一就是即使操作系統(tǒng)不支持線程,也可以通過庫函數(shù)來支持線程。
在這在模型下,程序員需要自己實現(xiàn)線程的數(shù)據(jù)結構、創(chuàng)建銷毀和調(diào)度維護。也就相當于需要實現(xiàn)一個自己的線程調(diào)度內(nèi)核,而同時這些線程運行在操作系統(tǒng)的一個進程內(nèi),最后操作系統(tǒng)直接對進程進行調(diào)度。
這樣做有一些優(yōu)點,首先就是確實在操作系統(tǒng)中實現(xiàn)了真實的多線程,其次就是線程的調(diào)度只是在用戶態(tài),減少了操作系統(tǒng)從內(nèi)核態(tài)到用戶態(tài)的切換開銷。這種模式最致命的缺點也是由于操作系統(tǒng)不知道線程的存在,因此當一個進程中的某一個線程進行系統(tǒng)調(diào)用時,比如缺頁中斷而導致線程阻塞,此時操作系統(tǒng)會阻塞整個進程,即使這個進程中其它線程還在工作。還有一個問題是假如進程中一個線程長時間不釋放 CPU,因為用戶空間并沒有時鐘中斷機制,會導致此進程中的其它線程得不到 CPU 而持續(xù)等待。
內(nèi)核線程就是直接由操作系統(tǒng)內(nèi)核(Kernel)支持的線程,這種線程由內(nèi)核來完成線程切換,內(nèi)核通過操縱調(diào)度器(Scheduler)對線程進行調(diào)度,并負責將線程的任務映射到各個處理器上。每個內(nèi)核線程可以視為內(nèi)核的一個分身,這樣操作系統(tǒng)就有能力同時處理多件事情,支持多線程的內(nèi)核就叫做多線程內(nèi)核(Multi-Threads Kernel)。
程序員直接使用操作系統(tǒng)中已經(jīng)實現(xiàn)的線程,而線程的創(chuàng)建、銷毀、調(diào)度和維護,都是靠操作系統(tǒng)(準確的說是內(nèi)核)來實現(xiàn),程序員只需要使用系統(tǒng)調(diào)用,而不需要自己設計線程的調(diào)度算法和線程對 CPU 資源的搶占使用。
在這種混合實現(xiàn)下,即存在用戶線程,也存在輕量級進程。用戶線程還是完全建立在用戶空間中,因此用戶線程的創(chuàng)建、切換、析構等操作依然廉價,并且可以支持大規(guī)模的用戶線程并發(fā)。而操作系統(tǒng)提供支持的輕量級進程則作為用戶線程和內(nèi)核線程之間的橋梁,這樣可以使用內(nèi)核提供的線程調(diào)度功能及處理器映射,并且用戶線程的系統(tǒng)調(diào)用要通過輕量級進程來完成,大大降低了整個進程被完全阻塞的風險。在這種混合模式中,用戶線程與輕量級進程的數(shù)量比是不定的,即為 N:M 的關系:
Golang 的協(xié)程就是使用了這種模型,在用戶態(tài),協(xié)程能快速的切換,避免了線程調(diào)度的 CPU 開銷問題,協(xié)程相當于線程的線程。
在 Linux 2.4 版以前,線程的實現(xiàn)和管理方式就是完全按照進程方式實現(xiàn)的;在 Linux 2.6 之前,內(nèi)核并不支持線程的概念,僅通過輕量級進程(Lightweight Process)模擬線程;輕量級進程是建立在內(nèi)核之上并由內(nèi)核支持的用戶線程,它是內(nèi)核線程的高度抽象,每一個輕量級進程都與一個特定的內(nèi)核線程關聯(lián)。內(nèi)核線程只能由內(nèi)核管理并像普通進程一樣被調(diào)度。這種模型最大的特點是線程調(diào)度由內(nèi)核完成了,而其他線程操作(同步、取消)等都是核外的線程庫(Linux Thread)函數(shù)完成的。
為了完全兼容 Posix 標準,Linux 2.6 首先對內(nèi)核進行了改進,引入了線程組的概念(仍然用輕量級進程表示線程),有了這個概念就可以將一組線程組織稱為一個進程,不過內(nèi)核并沒有準備特別的調(diào)度算法或是定義特別的數(shù)據(jù)結構來表征線程;相反,線程僅僅被視為一個與其他進程(概念上應該是線程)共享某些資源的進程(概念上應該是線程)。在實現(xiàn)上主要的改變就是在 task_struct 中加入 tgid 字段,這個字段就是用于表示線程組 id 的字段。在用戶線程庫方面,也使用 NPTL 代替 Linux Thread,不同調(diào)度模型上仍然采用 1 對 1
模型。
進程的實現(xiàn)是調(diào)用 fork 系統(tǒng)調(diào)用:pid_t fork(void);
,線程的實現(xiàn)是調(diào)用 clone 系統(tǒng)調(diào)用:int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...)
。與標準 fork()
相比,線程帶來的開銷非常小,內(nèi)核無需單獨復制進程的內(nèi)存空間或文件描寫敘述符等等。這就節(jié)省了大量的 CPU 時間,使得線程創(chuàng)建比新進程創(chuàng)建快上十到一百倍,能夠大量使用線程而無需太過于操心帶來的 CPU 或內(nèi)存不足。無論是 fork、vfork、kthread_create 最后都是要調(diào)用 do_fork,而 do_fork 就是根據(jù)不同的函數(shù)參數(shù),對一個進程所需的資源進行分配。
內(nèi)核線程是由內(nèi)核自己創(chuàng)建的線程,也叫做守護線程(Deamon),在終端上用命令 ps -Al
列出的所有進程中,名字以 k 開關以 d 結尾的往往都是內(nèi)核線程,比如 kthreadd、kswapd 等。與用戶線程相比,它們都由 do_fork()
創(chuàng)建,每個線程都有獨立的 task_struct 和內(nèi)核棧;也都參與調(diào)度,內(nèi)核線程也有優(yōu)先級,會被調(diào)度器平等地換入換出。二者的不同之處在于,內(nèi)核線程只工作在內(nèi)核態(tài)中;而用戶線程則既可以運行在內(nèi)核態(tài)(執(zhí)行系統(tǒng)調(diào)用時),也可以運行在用戶態(tài);內(nèi)核線程沒有用戶空間,所以對于一個內(nèi)核線程來說,它的 0~3G 的內(nèi)存空間是空白的,它的 current->mm
是空的,與內(nèi)核使用同一張頁表;而用戶線程則可以看到完整的 0~4G 內(nèi)存空間。
在 Linux 內(nèi)核啟動的最后階段,系統(tǒng)會創(chuàng)建兩個內(nèi)核線程,一個是 init,一個是 kthreadd。其中 init 線程的作用是運行文件系統(tǒng)上的一系列”init”腳本,并啟動 shell 進程,所以 init 線程稱得上是系統(tǒng)中所有用戶進程的祖先,它的 pid 是 1。kthreadd 線程是內(nèi)核的守護線程,在內(nèi)核正常工作時,它永遠不退出,是一個死循環(huán),它的 pid 是 2。
協(xié)程是用戶模式下的輕量級線程,最準確的名字應該叫用戶空間線程(User Space Thread),在不同的領域中也有不同的叫法,譬如纖程(Fiber)、綠色線程(Green Thread)等等。操作系統(tǒng)內(nèi)核對協(xié)程一無所知,協(xié)程的調(diào)度完全有應用程序來控制,操作系統(tǒng)不管這部分的調(diào)度;一個線程可以包含一個或多個協(xié)程,協(xié)程擁有自己的寄存器上下文和棧,協(xié)程調(diào)度切換時,將寄存器上細紋和棧保存起來,在切換回來時恢復先前保運的寄存上下文和棧。
協(xié)程的優(yōu)勢如下:
比如 Golang 里的 go 關鍵字其實就是負責開啟一個 Fiber,讓 func 邏輯跑在上面。而這一切都是發(fā)生的用戶態(tài)上,沒有發(fā)生在內(nèi)核態(tài)上,也就是說沒有 ContextSwitch 上的開銷。協(xié)程的實現(xiàn)庫中筆者較為常用的譬如 Go Routine、node-fibers、Java-Quasar 等。
Go 線程模型屬于多對多線程模型,在操作系統(tǒng)提供的內(nèi)核線程之上,Go 搭建了一個特有的兩級線程模型。Go 中使用使用 Go 語句創(chuàng)建的 Goroutine 可以認為是輕量級的用戶線程,Go 線程模型包含三個概念:
G: 表示 Goroutine,每個 Goroutine 對應一個 G 結構體,G 存儲 Goroutine 的運行堆棧、狀態(tài)以及任務函數(shù),可重用。G 并非執(zhí)行體,每個 G 需要綁定到 P 才能被調(diào)度執(zhí)行。
P: Processor,表示邏輯處理器,對 G 來說,P 相當于 CPU 核,G 只有綁定到 P(在 P 的 local runq 中)才能被調(diào)度。對 M 來說,P 提供了相關的執(zhí)行環(huán)境(Context),如內(nèi)存分配狀態(tài)(mcache),任務隊列(G)等,P 的數(shù)量決定了系統(tǒng)內(nèi)最大可并行的 G 的數(shù)量(物理 CPU 核數(shù) >= P 的數(shù)量),P 的數(shù)量由用戶設置的 GOMAXPROCS 決定,但是不論 GOMAXPROCS 設置為多大,P 的數(shù)量最大為 256。
在 Go 中每個邏輯處理器(P)會綁定到某一個內(nèi)核線程上,每個邏輯處理器(P)內(nèi)有一個本地隊列,用來存放 Go 運行時分配的 goroutine。多對多線程模型中是操作系統(tǒng)調(diào)度線程在物理 CPU 上運行,在 Go 中則是 Go 的運行時調(diào)度 Goroutine 在邏輯處理器(P)上運行。
Go 的棧是動態(tài)分配大小的,隨著存儲數(shù)據(jù)的數(shù)量而增長和收縮。每個新建的 Goroutine 只有大約 4KB 的棧。每個棧只有 4KB,那么在一個 1GB 的 RAM 上,我們就可以有 256 萬個 Goroutine 了,相對于 Java 中每個線程的 1MB,這是巨大的提升。Golang 實現(xiàn)了自己的調(diào)度器,允許眾多的 Goroutines 運行在相同的 OS 線程上。就算 Go 會運行與內(nèi)核相同的上下文切換,但是它能夠避免切換至 ring-0 以運行內(nèi)核,然后再切換回來,這樣就會節(jié)省大量的時間。
在 Go 中存在兩級調(diào)度:
使用 Go 語句創(chuàng)建一個 Goroutine 后,創(chuàng)建的 Goroutine 會被放入 Go 運行時調(diào)度器的全局運行隊列中,然后 Go 運行時調(diào)度器會把全局隊列中的 Goroutine 分配給不同的邏輯處理器(P),分配的 Goroutine 會被放到邏輯處理器(P)的本地隊列中,當本地隊列中某個 Goroutine 就緒后待分配到時間片后就可以在邏輯處理器上運行了。
目前,JVM 本身并未提供協(xié)程的實現(xiàn)庫,像 Quasar 這樣的協(xié)程框架似乎也仍非主流的并發(fā)問題解決方案,在本部分我們就討論下在 Java 中是否有必要一定要引入?yún)f(xié)程。在普通的 Web 服務器場景下,譬如 Spring Boot 中默認的 Worker 線程池線程數(shù)在 200(50 ~ 500) 左右,如果從線程的內(nèi)存占用角度來考慮,每個線程上下文約 128KB,那么 500 個線程本身的內(nèi)存占用在 60M,相較于整個堆棧不過爾爾。而 Java 本身提供的線程池,對于線程的創(chuàng)建與銷毀都有非常好的支持;即使 Vert.x 或 Kotlin 中提供的協(xié)程,往往也是基于原生線程池實現(xiàn)的。
從線程的切換開銷的角度來看,我們常說的切換開銷往往是針對于活躍線程;而普通的 Web 服務器天然會有大量的線程因為請求讀寫、DB 讀寫這樣的操作而掛起,實際只有數(shù)十個并發(fā)活躍線程會參與到 OS 的線程切換調(diào)度。而如果真的存在著大量活躍線程的場景,Java 生態(tài)圈中也存在了 Akka 這樣的 Actor 并發(fā)模型框架,它能夠感知線程何時能夠執(zhí)行工作,在用戶空間中構建運行時調(diào)度器,從而支持百萬級別的 Actor 并發(fā)。
實際上我們引入?yún)f(xié)程的場景,更多的是面對所謂百萬級別連接的處理,典型的就是 IM 服務器,可能需要同時處理大量空閑的鏈接。此時在 Java 生態(tài)圈中,我們可以使用 Netty 去進行處理,其基于 NIO 與 Worker Thread 實現(xiàn)的調(diào)度機制就很類似于協(xié)程,可以解決絕大部分因為 IO 的等待造成資源浪費的問題。而從并發(fā)模型對比的角度,如果我們希望能遵循 Go 中以消息傳遞方式實現(xiàn)內(nèi)存共享的理念,那么也可以采用 Disruptor 這樣的模型。
Java 線程在 JDK1.2 之前,是基于稱為“綠色線程”(Green Threads)的用戶線程實現(xiàn)的,而到了 JDK1.2 及以后,JVM 選擇了更加穩(wěn)健且方便使用的操作系統(tǒng)原生的線程模型,通過系統(tǒng)調(diào)用,將程序的線程交給了操作系統(tǒng)內(nèi)核進行調(diào)度。因此,在目前的 JDK 版本中,操作系統(tǒng)支持怎樣的線程模型,在很大程度上決定了 Java 虛擬機的線程是怎樣映射的,這點在不同的平臺上沒有辦法達成一致,虛擬機規(guī)范中也并未限定 Java 線程需要使用哪種線程模型來實現(xiàn)。線程模型只對線程的并發(fā)規(guī)模和操作成本產(chǎn)生影響,對 Java 程序的編碼和運行過程來說,這些差異都是透明的。
對于 Sun JDK 來說,它的 Windows 版與 Linux 版都是使用一對一的線程模型實現(xiàn)的,一條 Java 線程就映射到一條輕量級進程之中,因為 Windows 和 Linux 系統(tǒng)提供的線程模型就是一對一的。也就是說,現(xiàn)在的 Java 中線程的本質(zhì),其實就是操作系統(tǒng)中的線程,Linux 下是基于 pthread 庫實現(xiàn)的輕量級進程,Windows 下是原生的系統(tǒng) Win32 API 提供系統(tǒng)調(diào)用從而實現(xiàn)多線程。
在現(xiàn)在的操作系統(tǒng)中,因為線程依舊被視為輕量級進程,所以操作系統(tǒng)中線程的狀態(tài)實際上和進程狀態(tài)是一致的模型。從實際意義上來講,操作系統(tǒng)中的線程除去 new 和 terminated 狀態(tài),一個線程真實存在的狀態(tài),只有:
ready
:表示線程已經(jīng)被創(chuàng)建,正在等待系統(tǒng)調(diào)度分配 CPU 使用權。running
:表示線程獲得了 CPU 使用權,正在進行運算。waiting
:表示線程等待(或者說掛起),讓出 CPU 資源給其他線程使用。對于 Java 中的線程狀態(tài):無論是 Timed Waiting ,Waiting 還是 Blocked,對應的都是操作系統(tǒng)線程的 waiting(等待)狀態(tài)。而 Runnable 狀態(tài),則對應了操作系統(tǒng)中的 ready 和 running 狀態(tài)。Java 線程和操作系統(tǒng)線程,實際上同根同源,但又相差甚遠。
您可以通過以下導航來在 Gitbook 中閱讀筆者的系列文章,涵蓋了技術資料歸納、編程語言與理論、Web 與大前端、服務端開發(fā)與基礎架構、云計算與大數(shù)據(jù)、數(shù)據(jù)科學與人工智能、產(chǎn)品設計等多個領域:
知識體系:《Awesome Lists | CS 資料集錦》、《Awesome CheatSheets | 速學速查手冊》、《Awesome Interviews | 求職面試必備》、《Awesome RoadMaps | 程序員進階指南》、《Awesome MindMaps | 知識脈絡思維腦圖》、《Awesome-CS-Books | 開源書籍(.pdf)匯總》
編程語言:《編程語言理論》、《Java 實戰(zhàn)》、《JavaScript 實戰(zhàn)》、《Go 實戰(zhàn)》、《Python 實戰(zhàn)》、《Rust 實戰(zhàn)》
軟件工程、模式與架構:《編程范式與設計模式》、《數(shù)據(jù)結構與算法》、《軟件架構設計》、《整潔與重構》、《研發(fā)方式與工具》
Web 與大前端:《現(xiàn)代 Web 開發(fā)基礎與工程實踐》、《數(shù)據(jù)可視化》、《iOS》、《Android》、《混合開發(fā)與跨端應用》
服務端開發(fā)實踐與工程架構:《服務端基礎》、《微服務與云原生》、《測試與高可用保障》、《DevOps》、《Node》、《Spring》、《信息安全與***測試》
分布式基礎架構:《分布式系統(tǒng)》、《分布式計算》、《數(shù)據(jù)庫》、《網(wǎng)絡》、《虛擬化與編排》、《云計算與大數(shù)據(jù)》、《Linux 與操作系統(tǒng)》
數(shù)據(jù)科學,人工智能與深度學習:《數(shù)理統(tǒng)計》、《數(shù)據(jù)分析》、《機器學習》、《深度學習》、《自然語言處理》、《工具與工程化》、《行業(yè)應用》
產(chǎn)品設計與用戶體驗:《產(chǎn)品設計》、《交互體驗》、《項目管理》
此外,你還可前往 xCompass 交互式地檢索、查找需要的文章/鏈接/書籍/課程;或者在 MATRIX 文章與代碼索引矩陣中查看文章與項目源代碼等更詳細的目錄導航信息。最后,你也可以關注微信公眾號:『某熊的技術之路』以獲取最新資訊。
當前名稱:并發(fā)面試必備系列之進程、線程與協(xié)程
本文URL:http://muchs.cn/article44/ipgohe.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供自適應網(wǎng)站、外貿(mào)建站、移動網(wǎng)站建設、響應式網(wǎng)站、靜態(tài)網(wǎng)站、網(wǎng)站建設
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉載內(nèi)容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)