原創做者,公衆號【程序員讀書】,歡迎關注公衆號,轉載文章請註明出處哦。程序員
應該說,不管使用哪種編程語言開發應用程序,併發編程都是複雜的,而Go語言內置的併發支持,則讓Go併發編程變得很簡單。編程
CSP,即順序通訊進程,是Go語言中原生支持的併發模型,通常使用goroutine和channel來實現,CSP的編程思想是「經過通訊共享內存,而不是經過共享內存來通訊」,所以使用CSP思想來開發併發程序,通常是使用channel串聯多個goroutine,最終達到多個goroutine順序執行的目的。安全
咱們知道,單個的goutine代碼是順序執行,而併發編程時,建立多個goroutine,但咱們並不能肯定不一樣的goroutine之間的執行順序,多個goroutine之間大部分狀況是代碼交叉執行,在執行過程當中,可能會修改或讀取共享內存變量,這樣就會產生數據競爭
,發生一些意外以外的結果。bash
package main
var balance int
//存款
func Deposit(amount int) {
balance = balance + amount
}
//讀取餘額
func Balance() int {
return balance
}
func main(){
//小王:存600,並讀取餘額
go func(){
Deposit(600)
fmt.Println(Balance())
}()
//小張:存500
go func(){
Deposit(500)
}()
time.Sleep(time.Second)
fmt.Println(balance)
}
複製代碼
上面的例子叫銀行存款問題,是演示併發的經典例子。併發
通常咱們認爲,這個例子的運行結果只有三種:編程語言
上面的狀況,都是假設存款操做是順序的,可是,還存在一種狀況,也就是小王或小張併發執行存款操做,這時候會發生存款金額丟失的風險。函數
看到上面的例子以後,咱們知道數據競爭會產生嚴重的後果,那如何避免數據競爭呢?有三種:ui
咱們可使用Go語言提供的互斥鎖來避免上述的數據競爭行爲的發生,能夠把代碼進行相應的修改:spa
mu sync.Mutex // 聲明一個互斥鎖
func Deposit(amount int) {
mu.Lock()//獲取鎖
balance = balance + amount
mu.Unlock()//釋放鎖
}
//讀取餘額
func Balance() int {
mu.Lock()//獲取鎖
return balance
mu.Unlock()//釋放鎖
}
複製代碼
當咱們使用Mutex互斥鎖的時候,那麼不管是讀取仍是修改,都須要等待其餘goroutine釋放鎖,可是讀取相對修改來是,是安全的操做,Go提供了另一種鎖,sync.RWMutex,讀寫鎖,這種鎖,多個讀取的時候,不會鎖,只有修改時候,須要等到全部讀取的鎖釋放,才能修改,因此咱們能夠把Balance()函數修改成:code
rmu sync.RWMutex
func Balance() int {
rmu.RLock()//獲取讀鎖
return balance
rmu.RUnlock()//釋放讀鎖
}
複製代碼
上面的例子中,咱們都是在函數後面釋放鎖的,但實際開發中,函數的代碼很長,有各類判斷,咱們沒法保證函數能執行到最後,併成功釋放鎖,若是中發生錯誤,沒法釋放鎖,就形成其餘goroutine的阻塞,所以可使用defer關鍵字,讓函數不管如何都會釋放鎖。
package main
import "sync"
var balance int
mu sync.Mutex // 聲明一個互斥鎖
rmu sync.RWMutex
//存款
func Deposit(amount int) {
mu.Lock()//獲取鎖
balance = balance + amount
mu.Unlock()//釋放鎖
}
//讀取餘額
func Balance() int {
rmu.RLock()//獲取讀鎖
return balance
rmu.RUnlock()//釋放讀鎖
}
func main(){
//小王:存600,並讀取餘額
go func(){
Deposit(600)
fmt.Println(Balance())
}()
//小張:存500
go func(){
Deposit(500)
}()
time.Sleep(time.Second)
fmt.Println(balance)
}
複製代碼
固然,在實際項目併發編程的時候,咱們遇到的狀況要遠比上述例子複雜得多,所以還要多多練習,讓本身對併發有更學層次的理解。
你的關注,是我寫做路上最大的鼓勵!