探究go語言的併發和並行原理

goroutine基於線程池的P:M:G協程模型

首先說明一下go可以有兩種併發方式

  • csp也就是最常使用的go併發模式,這中模式無信息的直接交換,所以go中運用了chanel來交換數據
  • 共享內存通常意義上可以理解為通過共享了內存進而來通信的併發方式,例如加lock 這種模式可以直接交換數據,但是為了併發安全需要加鎖

只有cpu中線程之間的數據交換才可以是共享內存,進程之間是無法進行信息交換的

首先我們談談關於cpu和操作系統線程,進程的那些事: 我們通常都聽過這個一個詞,cpu 4核8線程,這裡的意思就是cpu實際內核是4核,但是在操作系統看來是8核cpu,這裡的8線程就是指的是8個虛擬內核。然後再操作系統層面,劃分為進程和線程,這裡的進程是cpu資源分配(io儲存等)的基本單位,在線程出現之前它也是cpu進行調度分配的基本單位,注意這裡的線程是操作系統的概念,跟4核8線程裡的概念不是一回事,而協程也就是coroutine是編程語言層面上的最近才有的一個東西,go裡面的goroutine也可以看做是能實現併發的協程

我們知道,在操作系統的層面上而言,實現併發就是多個線程在一個cpu核心裡接替執行,如果是並行呢,就是多個線程在多個cpu內核裡同時執行,這裡的同時才是真同時,而併發是"肉眼可見的同時但 是光速裡的交替執行"如果是高cpu密集計算形式的任務其實不需要那麼多個線程,只需要幾個線程然後將他們分配到多個核心進行計算,這樣上下文調度的時間就少了非常多了,多io形式的不需要多核,單核多線程就足夠了,因為在跨線程之間的數據交換上下文切換花費的時間也不少。

go實現併發的模式是PMG

如圖:

探究go語言的併發和並行原理


p就是上下文context m就是machine也就是對應著操作系統線程 g就是goroutine 我們來看看對應的關係

首先m對應了一個kse也就是操作系統中的一個線程,然後這個m下面有一個p就是上下文調度,然後這個p上面有一個初始的g goroutine和一個隊列,這個隊列裡是一批的goroutine,這個就是PMG模型。這裡有無數個想這樣的單位,如果誰的活幹完了那麼它就會去搶奪別的隊列的東西,並且分走一半的任務。

其實 goroutine 用到的就是線程池的技術,當 goroutine 需要執行時,會從 thread pool 中選出一個可用的 M 或者新建一個 M。而 thread pool 中如何選取線程,擴建線程,回收線程,Go 調度器 進行了封裝,對程序透明,只管調用就行,從而簡化了thread pool 的使用,它是定義在proc.c中,它維護有存儲M和G的隊列以及調度器的一些狀態信息

  • 問 什麼時候會創建另一個kse呢?(操作系統的線程)

我們可以這麼看這個模型,一個地鼠推著一個車子,車子上是磚頭,地鼠就是m車子就是p磚頭就是g而m對應了一個kse,那麼什麼時候會創建另一個m呢?runtime什麼時候創建線程?磚(G)太多了,地鼠(M)又太少了,實在忙不過來,剛好還有空閒的小車(P)沒有使用 當這個地鼠發現自己的活太多的時候,調度器就會再啟動一個m也就是線程池的概念,在操作系統層面從這個池中重新分配一個kse讓他繼續幹活。如果一個m發現自己沒活了,那麼它會主動去攬活兒,如果發現沒活了那麼它就會去偷取同伴m的g,直接拿走一半,如果同伴也沒了,那麼它就去睡覺了,也就是sleep了,

  • go的GOMAXPROCS 是幹嘛的?

在go語言啟動的時候會首先查看gomaxprocs,它會根據設置的數量來創建一批p,然後將他們儲存在調度器裡,以鏈表的方式儲存。它就是小推車呀

  • 解析goroutine協程和kse的關係

上面說了輕量級kse(操作系統線程)才是cpu調度的基本單位,你goroutine算什麼?,然後剛說了mpg,m就是這個kse p就是上下文,g就是goruntine,當然除此之外,go還有一個調度器然後go在這個kse中模擬了線程執行的過程,讓p負責管理,這個時候p沒辦法主動去取消g,只能g執行完了主動告訴p說我執行完了然後p把狀態保存到棧上,然後執行另一個g,等到所有的g都執行完都返回了,就跟剛才說的一樣這個m開始去取新的任務了,或者就是將p還給調度器然後自己休息了。

  • cpu執行的時候能同時執行多個進程嗎?

答案是不行,cpu去執行進程的時候只能去操作這個進程中的多個線程,然後將這些個線程分配到不同的cpu內核中。它是無法去執行多個進程的 因為cpu同時只能執行一個進程。

  • cpu同時只能執行一個進程,那麼我的計算機裡為什麼可以同時運行多個程序呢?

操作系統調度器(區分於go中的調度器哦) 拆分CPU為一段段時間的運行片,輪流分配給不同的程序。這樣的話就彷彿可以同時執行不同的程序進程了,還記得你使用kill命令的時候嗎?殺的就是進程,這些進程不能同時運行,他們被分配到不同的cpu片段裡。

  • 為什麼除了操作系統調度器go還有一個自己的調度器

單獨的開發一個GO得調度器,可以是其知道在什麼時候內存狀態是一致的,也就是說,當開始垃圾回收時,運行時只需要為當時 正在CPU核上運行的那個線程等待即可,而不是等待所有的線程。不然如果只有os的調度器,那gc的時候就要全部停下了。

綜上所述:

  • cpu同時只能執行一個程序(操作系統kse進程)(例如同時執行一個go程序然後接下來再單獨執行一個qq)但是操作系統將cpu分為了多時間片段,並且速度夠快,所以你看起來就是同時執行嘍
  • cpu可以將這個進程中的很多線程分配到不同的cpu核心裡。這樣就實現了並行,因為它只能識別一個進程,但是進程中有很多線程。
  • 線程池的概念在go中主要是用於分配那個m,並且是go自己的調度器來分配的
  • GOMAXPROCS的功能是為了分配p也就是車子,分配好了車子就儲存在調度器中,m可多可少,但是車是一定的。
  • 在這個車子中g的數量可多可少,可以非常多,那麼m也就是這個kse是根據GOMAXPROCS來定的,他的數量<= GOMAXPROCS指定的數量,但是最多不能超過256(不論你的gomaxpocs設置的是多少)
  • cpu將這個m也就是操作系統線程分配到多個cpu內核中
  • 如果GOMAXPROCS設置是1 那麼只能講這個線程分配到一個cpu內核中了,因為只有一個線程,(p只有一個,當然m也是隻有一個)所以就是併發不能並行了
  • 上下文context 也就是p 管理著這裡面一個隊列的的很多個的goroutine,所以這麼說來,p是控制局部G的調度器也可以這麼說,但是它不能主動控制g,是g說我執行完了,告訴它而已。但是和go自己的本身的調度器不是一回事兒。go本身的調度器實現了很多功能例如說分配M.
  • go單獨於os的調度器也就是區別於os的調度器 它是管理這個線程池的 --- 管理如何分配m


分享到:


相關文章: