Golang並發原理及GPM調度策略(一)


其實從一開始了解到go的goroutine概念就應該想到,其實go應該就是在內核級線程的基礎上做了一層邏輯上的虛擬線程(用戶級線程)+ 線程調度系統,如此分析以后,goroutine也就不再那么神秘了。

並發≠並行

假如我們有一段CPU密集型任務,我們創建2000個gorountine是否真的可以將其性能提高2000倍,答案必然是不能,因為我們只是進行了2000次的並發(concurrency),而並沒有真正做到並行(parallelism)。

並發其實所指的是我們的程序執行邏輯,傳統單線程應用的程序邏輯是順序執行的,在任何時刻,程序只能處理同一個邏輯,而並發指的是,我們同時執行多個獨立的程序邏輯,若干個程序邏輯在執行時可以是同時進行的(但並不代表同時進行處理)。實際上,不論我們並發多少個程序邏輯,若我們僅僅將其運行在一個單核單線程的CPU上,都不能讓你的程序在性能上有所提升,因為最終所有任務都排隊等待CPU資源(時間片)。

而並行才能讓我們的程序真正的同時處理多個任務,但並行並不是編程語言能夠帶我們的特性,他需要硬件支持。上面說到單核CPU所有資源都要等待同一個CPU的資源,那么其實我們只要將CPU增多就能真正的讓我們實現並行。我們可以使用多核CPU或用多台服務器組成服務集群,均可實現真正的並行,能夠並行處理的任務數量也就是我們的CPU數量。

引用Rob Pikie大神在PPT《Concurrency is not Parallelism》中的一段總結,大意就是並發不同於並行,但並發也許可以讓程序實現並行化。

Concurrency vs. parallelism
Concurrency is about dealing with lots of things at once.

Parallelism is about doing lots of things at once.

Not the same, but related.

Concurrency is about structure, parallelism is about execution.

Concurrency provides a way to structure a solution to solve a problem that may (but not necessarily) be parallelizable.

CPU密集&I/O密集

那么是不是說我們在單核CPU的機器上使用編程語言所提供的多線程就沒有意義了呢?

如果說我們的程序屬於CPU密集型,使用並發編程,可能確實無法提升我們的程序性能,甚至可能因為大量計算資源花在了創建線程本身,導致程序性能進一步下降。

但不同的是,如果說我們的程序屬於IO密集型,當你在進行程序壓測的過程中可能發現CPU占用率很低,但性能卻到了瓶頸,原因是程序將大量的時間花在了等待IO的過程中,如果我們可以在等待IO的時候繼續執行其他的程序邏輯即可提高CPU利用率,從而提高我們的程序性能,這時並發編程的好處就出來了,例如Python因為GIL的存在實際上並不能實現真正的並行,但他的多線程依舊在IO密集型的程序中依舊有種很重要的意義。

Goroutine(Golang Coroutine)

上面說到了使用多核CPU實現並行處理,使應用在多核cpu實現並行處理的方案主要是多進程與多線程兩種方式,多進程模型相對簡單,但是有着資源開銷大及進程間通信成本高的問題。多線程模型相對復雜,會有死鎖,線程安全,模型復雜等問題,但卻因為資源開銷及易於管理等優點適用於對於性能要求較高的應用。

Golang采用的是多線程模型,更詳細的說他是一個兩級線程模型,但它對系統線程(內核級線程)進行了封裝,暴露了一個輕量級的協程goroutine(用戶級線程)供用戶使用,而用戶級線程到內核級線程的調度由golang的runtime負責,調度邏輯對外透明。

goroutine的優勢在於上下文切換在完全用戶態進行,無需像線程一樣頻繁在用戶態與內核態之間切換,節約了資源消耗。

同時,啟動一個gorountine非常簡單,而且寫法很cool~

go function()

僅僅需要在調用函數時在前面加上關鍵字go即可創建一個goroutine並創建其上下文對象。

G·P·M

G(Goroutine) :我們所說的協程,為用戶級的輕量級線程,每個Goroutine對象中的sched保存着其上下文信息

M(Machine) :對內核級線程的封裝,數量對應真實的CPU數(真正干活的對象)

P(Processor) :即為G和M的調度對象,用來調度G和M之間的關聯關系,其數量可通過GOMAXPROCS()來設置,默認為核心數

每個Processor對象都擁有一個LRQ(Local Run Queue),未分配的Goroutine對象保存在GRQ(Global Run Queue )中,等待分配給某一個P的LRQ中,每個LRQ里面包含若干個用戶創建的Goroutine對象,同時Processor作為橋梁對Machine和Goroutine進行了解耦,也就是說Goroutine如果想要使用Machine需要綁定一個Processor才行,上圖中共有兩個M和兩個P也就是說我們可以同時並行處理兩個goroutine。

這一篇主要講解了一些並行與並發的區別Golang並發模型的優勢,下一篇會詳細說明GPM模型的調度策略。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM