Go語言基礎學習三-簡單的代碼分析(併發)
有關於go語言基礎學習的第二篇和第三篇都是直接通過分析代碼來對go語言的一些特點進行認識,具體的針對性的一些go語言特性方面的細節會在之後的每一章節有細緻的描述,我覺得學習一門語言,帶着閱讀代碼的經驗,從實際的語言使用中去有一個初步的認識,對於之後的每一章節語言特性細節上的學習會更加深入,所以,第二章和第三章的內容希望大家能夠儘量理解這些代碼的含義,我也是一個go語言方面的新手,和大家一起交流是最愉快的,如果實在不能理解也沒關係,之後的章節會有針對性的講解。
這一章節主要通過代碼示例講go語言的一個關鍵特性,那就是其充分利用現代計算機的多處理器和多核的功能,完全無需任何顯示瑣就可以寫出許多併發程序。這在於go語言的兩大特性使得使用它做併發編程非常輕鬆:
1.無需繼承“線程” ,即可輕易創建goroutine(實際上是非常輕量級的線程)
2.通道爲gorouting之間提供了類型安全的單向或雙向通信,這也可以用來同步goroutine
go語言處理併發的方式是通過傳遞數據而非共享數據,這也就比傳統的線程的鎖方式來編寫併發編程更爲簡單。
下面的示例是通過一個管道來獲得從極座標到笛卡爾座標的轉換。
首先介紹兩個結構體:
type polar struct {
radius float64
θ float64//角度
}
type cartesian struct {
x float64
y float64
}
go語言的結構體是一種能夠用來保存一個或多個數據字段的類型這些字段可以是內置類型(float64),結構體,接口(實際上就是一個指向任意類型的指針),因爲go語言支持UTF-8編碼,所以可以使用任何Unicode字符作爲標示符,如角度。
const result = "Polar radius=%.02f θ=%.02f° → Cartesian x=%.02f y=%.02f\n"
var prompt = "Enter a radius and an angle (in degrees), e.g., 12.5 90, " +
"or %s to quit."
func init() {/*首先進入init()初始化程序,來根據不同的平臺做不同的操作,runtime包提供了一個字符串常量GOOS來表示運行的操作系統常用值有:darwin,freebsd,linux,windows*/
if runtime.GOOS == "windows" {
/*若運行平臺爲windows,則輸出按Ctrl+Z退出*/
prompt = fmt.Sprintf(prompt, "Ctrl+Z, Enter")
} else { // Unix-like
/*若運行平臺爲其他,則輸出按Ctrl+D退出*/
prompt = fmt.Sprintf(prompt, "Ctrl+D")
}
}
通道:go語言中的通道是基於Unix上管道的思想而被設計出來的,它提供了雙向或單向數據通信模式,通道的行爲跟FIFO隊列一樣,通道中的數據不能被刪除
1.通道的創建:message := make(chan string,10) 之前我們說過數據的映射,通道及切片都是通過make()函數來創建的,這裏通道聲明的語法爲chan Type,make函數的第二個參數爲通道緩衝區的大小,緩衝區滿時會發生阻塞。
2.向通道寫入數據:messages <- “Leader” 將“Leader”寫入通道
3.獲取通道數據:messages1 := <-messages 得到messages通道中的”Leader”
func main() {/*進入主程序main(),首先創建一個接收數據polar類型的通道*/
questions := make(chan polar)
defer close(questions)/*保證通道使用完畢後關閉通道*/
answers := createSolver(questions)/*根據問題通道創建相關的回答通道*/
defer close(answers)
interact(questions, answers)/*利用這兩個相關的問題及回答通道,建立與用戶的交互模型*/
}
func createSolver(questions chan polar) chan cartesian {/*根據問題通道創建相關的回答通道*/
answers := make(chan cartesian)/*創建回答通道*/
go func() {/*go fun()該go語句會創建一個異步goroutine來執行這個函數,return answer依然會執行,但同時該goroutine
也在異步執行等待通道數據的處理*/
for {
polarCoord := <-questions/*獲取問題通道中的極座標數據結構*/
θ := polarCoord.θ * math.Pi / 180.0 // degrees to radians
x := polarCoord.radius * math.Cos(θ)
y := polarCoord.radius * math.Sin(θ)
answers <- cartesian{x, y}/*經過對極座標的數據處理得到結果,將結果寫入回答通道*/
}
}()
return answers
}
func interact(questions chan polar, answers chan cartesian) {/*利用這兩個相關的問題及回答通道,建立與用戶的交互模型*/
reader := bufio.NewReader(os.Stdin)
fmt.Println(prompt)/*輸出打印提示符提示用戶輸入什麼*/
for {/*這裏的for循環會持續讓用戶輸入下一個極座標*/
fmt.Printf("Radius and angle: ")
line, err := reader.ReadString(‘\n’)/*只讀到一個轉行符則退出程序*/
if err != nil {
break
}
var radius, θ float64
/*獲得用戶輸入*/
if _, err := fmt.Sscanf(line, "%f %f", &radius, &θ); err != nil {
fmt.Fprintln(os.Stderr, "invalid input")
continue
}
/*將獲得的用戶輸入構建爲polar,寫入到問題通道*/
questions <- polar{radius, θ}
/*之前建立的goroutine異步線程會根據問題通道的數據進行處理並直接寫入回答通道,這裏直接從回答通道即可得到轉換後的笛卡爾座標,在沒計算出結果前該通道會阻塞*/
coord := <-answers
fmt.Printf(result, radius, θ, coord.x, coord.y)
}
fmt.Println()
}