瞅一眼就會使用GO的併發編程分享

[TOC]shell

GO的併發編程分享

以前咱們分享了網絡編程,今天咱們來看看GO的併發編程分享,咱們先來看看他是個啥編程

啥是併發編程呢?

指在一臺處理器上同時處理多個任務緩存

此處說的同時,可不是同一個時間一塊兒手拉手作同一件事情安全

併發是在同一實體上的多個事件,而這個事件在同一時間間隔發生的,同一個時間段,有多個任務執行,但是同一個時間點,只有一個任務在執行網絡

爲啥要有併發編程?

隨着互聯網的普及,互聯網用戶人數原來越多,這對系統的性能帶來了巨大的挑戰。多線程

咱們要經過各類方式來高效利用硬件的性能(壓榨),從而提升系統的性能進而提高用戶體驗,提高團隊或者企業的競爭力。併發

併發是爲了解決什麼問題?目的是啥?函數

充分的利用好處理器的每個核,以達到最高的處理性能,儘量的運用好每一塊磚高併發

但是因爲如今咱們使用的CPU,內存,IO三者之間速度不盡相同性能

咱們爲了提升系統性能,計算機系統會將這三者速度進行平衡,以達到最優的效果,都有以下措施:

  • 操做系統增長了進程線程,以分時複用 CPU,進而均衡 CPUI/O 設備的速度差別;
  • CPU 增長了緩存,以均衡與內存的速度差別;
  • 編譯程序優化指令執行次序,使得緩存可以獲得更加合理地利用。

說到進程和線程,他們都是幹啥的呢,我們順帶說一下?

  • 進程是程序在操做系統中的一次執行過程

是 系統進行資源分配和調度的一個獨立單位

  • 線程是進程的一個執行實體

是 CPU 調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位。

  • 一個進程能夠建立和撤銷多個線程, 而且同一個進程中的多個線程之間能夠併發執行。

講到併發編程不得不說併發和並行有啥區別?是否是老是有小夥伴弄不清楚他們究竟是啥區別,好像同樣,又好像不同

併發和並行的區別

一言蔽之,區別以下:

併發

多線程程序在一個核的 CPU 上運行

並行

多線程程序在多個核的 CPU 上運行

併發就像多個小夥伴跑接力,同一個時間點只會有一個小夥伴在跑,互相有影響

並行就像是多個小夥伴同一個起點一塊兒跑,互不干擾

咱們須要記住一點,再強調一波:

併發不是並行

併發主要由切換時間片來實現"同時"運行

並行則是直接利用多核實現多線程的運行,

在 GO 能夠設置使用核數,以發揮多核計算機的能力,不過設置核數都是依賴於硬件的

那麼,講到GO的併發編程,就必須上咱們的主角,那就是協程

協程 goroutine 是啥?

協程是一種程序組件

是由子例程(過程、函數、例程、方法、子程序)的概念泛化而來的

子例程只有一個入口點且只返回一次,而協程容許多個入口點,能夠在指定位置掛起和恢復執行。

協程和線程分別有啥特色嘞

  • 協程

獨立的棧空間,共享堆空間,調度由用戶本身控制

本質上有點相似於用戶級線程,這些用戶級線程的調度也是本身實現的。

  • 線程

一個線程上能夠跑多個協程,協程是輕量級的線程

GO 高併發的緣由是啥?

  • goroutine 奉行經過通訊來共享內存
  • 每一個一個GO的實例有4~5KB的棧內存佔用,而且因爲 GO 實現機制而大幅減小的建立和銷燬開銷
  • Golang 在語言層面上就支持協程 goroutine

GOLANG併發編程涉及哪些知識點呢?

  • 基本協程的原理,實現方式,雖說,GO中使用協程很方便,能夠咱們必需要知其然而值其因此然
  • Goroutine 池
  • runtime 包的使用
  • Channel 通道
  • 定時器
  • 併發且安全的鎖
  • 原子操做
  • select 多路複用
  • 等等...

Goroutine的那些事

咱們寫C/C++的時候,咱們必然也是要實現併發編程

咱們一般須要本身維護一個線程池,而且須要本身去包裝一個又一個的任務,同時須要本身去調度線程執行任務並維護上下文切換

且作線程池的時候,咱們須要本身作一個線程管理的角色,靈活動態壓縮和擴容

但是能不能有這樣一種機制,咱們只須要定義多個任務,讓系統去幫助咱們把這些任務分配到CPU上實現併發執行

GO裏面就正好有這樣的機制

goroutine 的概念相似於線程

goroutine 是由Go的運行時(runtime)調度和管理的

Go程序會智能地將 goroutine 中的任務合理地分配給每一個CPU

Go 在語言層面已經內置了調度和上下文切換的機制

寫 GO 比較爽的一個地方是:

在GO裏面,你不須要去本身寫進程、線程、協程

咱們可使用 goroutine 包

如何使用 goroutine ?

咱們須要讓某個任務併發執行的時候,只須要把這個任務包裝成一個函數

專門開啓一個 goroutine 協程 去執行這個函數就能夠了 , GO一個協程,很方便

一個 goroutine 一定對應一個函數,能夠建立多個 goroutine 去執行相同的函數,只是多個協程都是作同一個事情罷了

咱們先來使用一下協程,再來拋磚引玉,適當的分享一下

啓動單個協程

func Hi() {
    fmt.Println("this is Hi Goroutine!")
}
func main() {
    Hi()
    fmt.Println("main goroutine!")
}

咱們通常調用函數是如上這個樣子的,效果以下

this is Hi Goroutine!
main goroutine!

其實咱們調用協程的話,也與上述相似

咱們可使用 go 後面加上函數名字,來開闢一個協程,專門作函數須要執行的事情

func main() {
    go Hi() // 啓動一個goroutine 協程 去執行 Hi 函數
    fmt.Println("main goroutine!")

實際效果咱們能夠看到,程序只打印了 main goroutine!

main goroutine!

在程序啓動的時候,Go 程序就會爲 main() 函數建立一個默認的 goroutine 協程

當 main() 函數返回的時候,剛開闢的另一個 goroutine 協程 就結束了

全部在 main() 函數中啓動的 goroutine 協程 會一同結束,老大死了,其他的傀儡也灰飛煙滅了

img

咱們也可讓主協程等等必定子協程,待子協程處理完本身的事情,退出後,主協程再本身退出,這和咱們寫C/C++進程 和 線程的時候,相似

簡單的,咱們可使用 time.sleep 函數來讓主協程阻塞等待

咱們也可使用 上述提到的 使用 select{} 來達到目的

固然也有其餘的方式,後續文章會慢慢的分享到

多個協程

那麼多個協程又是怎麼玩的呢?

咱們使用 sync.WaitGroup 來實現goroutine 協程的同步

package main

import (
    "fmt"
    "sync"
)

var myWg sync.WaitGroup

func Hi(i int) {
    // goroutine 協程 結束就 記錄 -1
    defer myWg.Done()
    fmt.Println("Hello Goroutine! the ", i)
}
func main() {

    for i := 0; i < 10; i++ {
        // 啓動一個goroutine 協程 就記錄 +1
        myWg.Add(1)
        go Hi(i)
    }

    // 等待全部記錄 的goroutine 協程 都結束
    myWg.Wait() 
}

會有以下輸出,每個協程打印的數字並非按照順序來的:

Hello Goroutine! the  9
Hello Goroutine! the  4
Hello Goroutine! the  2
Hello Goroutine! the  3
Hello Goroutine! the  6
Hello Goroutine! the  5
Hello Goroutine! the  7
Hello Goroutine! the  8
Hello Goroutine! the  1
Hello Goroutine! the  0

仍是一樣的, 若是是主協程先退出,那麼子協程還行繼續運行嗎?

毋庸置疑,主協程退出,子協程也會跟着退出

GO 中的 協程

分享以下幾個點

GO中的棧是可增加的

通常都有固定的棧內存(一般爲2MB),goroutine 的棧不是固定的,goroutine 的棧大小能夠擴展到1GB

goroutine 是如何調度

這就不得不提 GPM

GPM是Go語言運行時(runtime)層面實現的,咱們先簡單瞭解一下GPM分別表明啥

G

就是個 goroutine ,裏面除了存放本 goroutine 信息外 還有與所在P的綁定等信息

P

Processor 管理着一組 goroutine 隊列

P 裏面會存儲當前 goroutine 運行的上下文環境(函數指針,堆棧地址及地址邊界)

P 會對本身管理的 goroutine 隊列作一些調度(好比把佔用CPU時間較長的 goroutine 暫停、運行後續的 goroutine)

當本身的隊列消費完了就去全局隊列裏取,若是全局隊列裏也消費完了會去其餘P的隊列裏搶任務。

M(machine)

是 Go 運行時(runtime)對操做系統內核線程的虛擬

M 與內核線程通常是一一映射的關係, 一個 groutine 最終是要放到 M上執行

這裏的 P 與 M 通常也是一一對應的

P 管理着一組G 掛載在 M 上運行

當一個 G 長久阻塞在一個 M 上時,runtime 會新建一個M,

阻塞 G 所在的 P 會把其餘的 G 掛載在新建的M上

這個時候,當舊的 G 阻塞完成或者認爲其已經掛了的話,就會回收舊的 M

還有一點

P 的個數是經過 runtime.GOMAXPROCS 設定(最大256),這個數字也依賴於本身的硬件,在併發量大的時候會增長一些 P 和 M ,但不會太多

總結

  • 分享了併發和並行
  • 分享了GO 的併發,協程的簡單使用
  • 簡單分享了GO可伸縮擴展的棧內存

關於GO的併發編程知識點涉及面多,對於他的調度原理,真實感興趣的話,能夠看上述提到的GO併發涉及的知識點,一點一點的深刻下去,今天就到這裏,大致瞭解GO協程的使用

歡迎點贊,關注,收藏

朋友們,你的支持和鼓勵,是我堅持分享,提升質量的動力

好了,本次就到這裏,下一次GO的鎖和原子操做分享

技術是開放的,咱們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~

相關文章
相關標籤/搜索