Golang併發之共享內存變量

原創做者,公衆號【程序員讀書】,歡迎關注公衆號,轉載文章請註明出處哦。程序員

應該說,不管使用哪種編程語言開發應用程序,併發編程都是複雜的,而Go語言內置的併發支持,則讓Go併發編程變得很簡單。編程

CSP

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)
}
複製代碼

上面的例子叫銀行存款問題,是演示併發的經典例子。併發

通常咱們認爲,這個例子的運行結果只有三種:編程語言

  1. 小王存600,小王讀取餘額爲600,小張再存500,總金額爲1100
  2. 小張存500,小王存600,小王讀取餘額爲500,總金額爲1100
  3. 小王存600,小張存500,小王讀取餘額爲500,總金額爲1100

上面的狀況,都是假設存款操做是順序的,可是,還存在一種狀況,也就是小王或小張併發執行存款操做,這時候會發生存款金額丟失的風險。函數

看到上面的例子以後,咱們知道數據競爭會產生嚴重的後果,那如何避免數據競爭呢?有三種:ui

  1. 經過channel串聯goroutine,達到順序執行的效果,避免競爭。
  2. 不在併發程序中修改共享變量,這固然是不太可能的狀況。
  3. 經過使用鎖,使用同一時間只有一個goroutine能夠修改內存中的變量,也就使用不一樣goroutine修改變量時發生互斥行爲。

sync.Mutex:互斥鎖

咱們可使用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()//釋放鎖
}
複製代碼

sync.RWMutex:讀寫鎖

當咱們使用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)
}
複製代碼

總結

固然,在實際項目併發編程的時候,咱們遇到的狀況要遠比上述例子複雜得多,所以還要多多練習,讓本身對併發有更學層次的理解。

你的關注,是我寫做路上最大的鼓勵!

相關文章
相關標籤/搜索