學習Golang差很少半年了,go中的併發,通道,通道同步單個來說都不陌生,可是結合在一塊兒運用的時候就有些懵逼,同時也不知道爲什麼要這麼作。我想這是初學者都會遇到的困惑,在這裏講下本身的理解。緩存
看一段代碼bash
func main() {
var a int
for i := 0; i < 10; i++ {
go func() {
for i := 0; i < 100; i++ {
a++
}
}()
}
time.Sleep(1 * time.Second)
fmt.Print(a)
}
// 運行結果
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
1000
複製代碼
從運行結果來看,主線程是能夠跟協程共享變量的,同時10個協程分別自加100次,獲得1000的結果與預期結果同樣併發
如今增長每一個協程的運算量,再看一下運行結果異步
func main() {
var a int
for i := 0; i < 10; i++ {
go func() {
for i := 0; i < 100000; i++ {
a++
}
}()
}
time.Sleep(1 * time.Second)
fmt.Print(a)
}
// 輸出結果
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
213897
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
206400
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
211926
複製代碼
能夠看到每一個協程由100的自加變爲100000的自加,此時輸出結果每次都不一樣而且與1000000的預期結果相差很大,我的沒有深刻研究只是簡單推測因爲併發的異步特性,同一時間有多個協程執行了自增,實際cpu只計算了一次,這種偏差會隨着併發協程的數量和各自計算量的增多而變大。(後來有人補充cpu核數限制爲1核就不會發生這種並行的狀況)學習
func main() {
var ch = make(chan int, 10)
for i := 0; i < 10; i++ {
go func() {
var a int
for i := 0; i < 100000; i++ {
a++
}
ch <- a
}()
}
var sum int
func() {
for i := 0; i < 10; i++ {
sum += <- ch
}
}()
fmt.Print(sum)
}
// 輸出結果
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
1000000
複製代碼
大體思路仍是開啓10個協程,同時將原來定義在主線程中的變量a定義到每一個協程中,在主線程中定義有10個緩衝的通道。這時每一個協程各自處理本身的運算結果互不干擾,只在最後將各自運算結果寫入到通道中。主線程再遍歷通道進行讀操做,只有當協程中有數據被寫入時才能讀取到數據而且彙總結果。因爲讀操做是在主線程中會發生阻塞,因此此時能夠去掉睡眠,程序依然能正確執行,這就是通道同步。spa
func main() {
var ch = make(chan int, 10)
for i := 0; i < 10; i++ {
go func() {
var a int
for i := 0; i < 100000; i++ {
a++
}
ch <- a
}()
}
var sum int
go func() {
for i := 0; i < 10; i++ {
sum += <- ch
}
}()
fmt.Print(sum)
}
// 輸出結果
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
0
複製代碼
很明顯若是讀操做也開協程,此時主線程不會發生阻塞,主線程不等協程結束直接結束了,想要獲得正確結果,主要主線程等待就好了。這樣作的優勢就是讀操做也是併發的,不須要同步等待。線程
仍是這段代碼,加上時間等待。code
func main() {
var ch = make(chan int, 10)
for i := 0; i < 10; i++ {
go func() {
var a int
for i := 0; i < 100000; i++ {
a++
}
ch <- a
}()
}
var sum int
go func() {
for i := 0; i < 10; i++ {
sum += <- ch
}
}()
time.Sleep(1 * time.Second)
fmt.Print(sum)
}
// 輸出結果
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
1000000
複製代碼
細心觀察,能夠發現併發通道讀操做的結果使用了主線程的變量sum,程序按預期正確執行。這就說明了協程是能夠跟主線程共享變量的,只是使用的前提是這個變量只被一個協程使用,若是被多個協程使用就可能出現文章開頭出現的問題。協程
func main() {
var a int
go func() {
for i := 0; i < 1000000; i++ {
a++
}
}()
for i := 0; i < 1000000; i++ {
a++
}
time.Sleep(1 * time.Second)
fmt.Print(a)
}
// 輸出
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
1079312
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
1003960
PS C:\Users\mayn\go\src\test_5_5> go run .\main.go
1021828
複製代碼
發現即便只有單一的協程與主線程共享變量,也是會發生問題。結論:協程間儘可能不要共享變量,很難保證不出問題。說這麼多隻是體現通道的做用與優勢。同步
以上所有內容只是我的的一點摸索,不表明徹底正確。