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

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

併發≠並行

假如咱們有一段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密集型的程序中依舊有種很重要的意義。ide

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模型的調度策略。

相關文章
相關標籤/搜索