golang併發程式設計

2022-07-18 19:06:18 字數 2776 閱讀 4307

在早期,cpu都是以單核的形式順序執行機器指令。c語言、php正是這種順序程式語言的代表,即所有的指令都是以序列的方式執行,在相同的時刻有且僅有乙個cpu在順序執行程式的指令。隨著處理器技術的發展,單核時代以提公升處理器頻率來提高執行效率的方式遇到了瓶頸。單核cpu的發展的停滯,給多核cpu的發展帶來了機遇。相應地,程式語言也開始逐步向並行化的方向發展。go語言正是在多核和網路化的時代背景下誕生的原生支援併發的程式語言。

goroutine 是 go 語言特有的併發體,是一種輕量級的執行緒,由go關鍵字啟動。在真實的go語言的實現中,goroutine 和系統執行緒也不是等價的。儘管兩者的區別實際上只是乙個量的區別,但正是這個量變引發了 go 語言併發程式設計質的飛躍。

package main

import "fmt"

func main()

每個系統級執行緒都會有乙個固定大小的棧(一般預設可能是8mb),這個棧主要用來儲存函式遞迴呼叫時引數和區域性變數。固定了棧的大小導致了兩個問題:一是對於很多只需要很小的棧空間的執行緒來說是乙個巨大的浪費,二是對於少數需要巨大棧空間的執行緒來說又面臨棧溢位的風險。相反,乙個 goroutine 會以乙個很小的棧啟動(可能是2kb或4kb),當遇到當前棧空間不足時, goroutine 會根據需要動態地伸縮棧的大小。因為啟動的代價很小,所以我們可以輕易地啟動成千上萬個 goroutine 。

go的排程器使用了一些技術手段,可以在n個作業系統執行緒上多任務排程m個 goroutine 。只有在當前 goroutine 發生阻塞時才會導致排程,同時發生在使用者態,切換的代價要比系統執行緒低得多。執行時有乙個runtime.gomaxprocs變數,用於控制當前執行正常非阻塞 goroutine 的系統執行緒數目。在go語言中啟動乙個 goroutine 不僅和呼叫函式一樣簡單,而且 goroutine 之間排程代價也很低,這些因素極大地促進了併發程式設計的流行和發展。

在併發程式設計中,對共享資源的正確訪問需要精確的控制,在目前的絕大多數語言中,都是通過加鎖等執行緒同步方案來解決這一問題。而go語言卻另闢蹊徑,它將共享的值通過channel傳遞,資料競爭從設計層面上就被杜絕了。通過通道來傳值是go語言推薦的做法,雖然像引用計數這類簡單的併發問題通過原子操作或互斥鎖就能很好地實現,但是通過channel來控制訪問能夠讓你寫出更簡潔正確的程式。

建立通道

//非緩衝通道

ch1 := make(chan int)

//緩衝通道

ch2 := make(chan int, 1)

非緩衝通道必須確保有協程正在嘗試讀取當前通道,否則寫操作就會阻塞直到有其它協程來從通道中讀東西。

讀寫通道

//從通道讀,

data, ok := <-ch1

data := <-ch1

//往通道寫

ch2 <-data

//使用range讀,通道沒資料for就會阻塞,通道關閉就會退出for

for v := range ch1

//多路通道

for

}

通道滿了,寫操作就會阻塞,協程就會進入休眠,直到有其它協程讀通道挪出了空間,協程才會被喚醒。通道空了,讀操作就會阻塞,協程也會進入睡眠,直到有其它協程寫信道裝進了資料才會被喚醒。

//關閉通道

close(ch1)

讀取乙個已經關閉的通道會立即返回通道型別的「零值」,而寫乙個已經關閉的通道會拋異常。使用for range讀取時用完要記得關閉通道,否則會阻塞。

根據 go 語言規範,main 函式退出時程式結束,不會等待任何後台執行緒。因為 goroutine 的執行和 main 函式的返回事件是併發的,誰都有可能先發生,所以什麼時候列印,能否列印都是未知的。

sleep

func main() 

}

不可靠,因為實際協程執行時間未知

互斥鎖

func main() ()

mu.lock()

}

主攜程中第二次獲取鎖時阻塞

通道

func main() ()

<-ch

}

從ch取值,由於通道為空所以會阻塞直到有資料寫入

原子等待組

func main() (i)

}//等待協程完成

wg.wait()

}

如果不把i作為引數傳入閉包函式,閉包go協程裡面引用的是變數i的位址,所有的go協程啟動後等待呼叫,很可能在for迴圈完成之後才被呼叫,所以輸出結果很多都是10

控制併發數

雖然啟動乙個攜程代價很小,但是也不能無限制地建立攜程,否則導致cpu占用過高

func main() ()

}for {}

}

超時處理

當限制併發數的時候,如果有大量寫信道,會造成通道阻塞過長

func main() 

}

生產者消費者例項

例如在tcp程式設計中,乙個 goroutine 用來讀,乙個 goroutine 用來寫,讀寫 goroutine 間用通道傳遞訊息

func main()  

}func write(conn net.conn, ch <-chan string) }}

func read(conn net.conn, ch chan<- string)

ch <- string(msg[:n])

}}

Golang 併發程式設計

目錄傳送者 通道 有可能有資料阻塞 接受者 package main import fmt time func main time.sleep 2 time.second 主協程取資料 for i 0 i 3 i fmt.println 主協程結束 傳送者 通道 資料 資料 接受者 func mai...

Golang 併發程式設計

目錄傳送者 通道 有可能有資料阻塞 接受者 package main import fmt time func main time.sleep 2 time.second 主協程取資料 for i 0 i 3 i fmt.println 主協程結束 傳送者 通道 資料 資料 接受者 func mai...

golang併發程式設計 01

一 併發程式設計模板 func main wg.add 1 go func wg.wait 二 鎖住共享資源 1.原子函式 var counter int64 多個goroutine都會增加其值的變數 如果在goroutine中要對counter執行加法,那麼要用原子操作 atomic.addint...