Go 語言學(xué)習(xí)筆記-select、鎖和條件變量

select

  • select 的作用:

    通過 select 可以監(jiān)聽 channel 上的數(shù)據(jù)流動。

  • select 的用法:

    與 switch case 語法類似。但是 case 后面必須是 IO 操作,不可以人一些判斷表達(dá)式。

    select {
    case <-chan1:
      // 如果 chan1 成功讀到數(shù)據(jù),則進(jìn)行該 case 處理語句
    case chan2 <- 1:
      //如果成功向 chan2 寫入數(shù)據(jù),則進(jìn)行該 case 處理語句
    default:
      // 如果上面都沒有成功,則進(jìn)入 default 處理流程
    }
    
  • 注意:

    • 監(jiān)聽的 case 中,沒有滿足監(jiān)聽條件,阻塞。
    • 監(jiān)聽的 case 中,有多個滿足監(jiān)聽條件,任選一個執(zhí)行。
    • 可以使用 default 來處理所有case 都不滿足監(jiān)聽條件的狀況。通常不用(會產(chǎn)生忙輪詢)。
    • select 自身不帶有循環(huán)機制,需借助外層 for 來循環(huán)監(jiān)聽。
    • break 跳出 select 中的 case 選項。類似于 switch 中的用法。

超時處理

有時候會出現(xiàn) goroutine 阻塞的情況,可以使用 select 來設(shè)置超時,通過如下的方式實現(xiàn):

func main() {
  c := make(chan int)
  o := make(chan bool)
  go func() {
    for {
      select {
      case v := <-c:
        fmt.Println(v)
      case <time.After(5 * time.Second):
        fmt.Println("timeout"):
        o <- true
        break
      }
    }
  }()
  // c <-66  // 注釋掉,引發(fā) timeout
  <-o
}

鎖和條件變量

什么是鎖

就是某個協(xié)程(線程)在訪問某個資源時先鎖住,防止其他協(xié)程的訪問,等訪問完畢解鎖后其他協(xié)程再來加鎖進(jìn)行訪問。

死鎖

死鎖不是鎖的一種,是一種錯誤使用鎖導(dǎo)致的現(xiàn)象。

死鎖是指兩個或兩個以上的進(jìn)程在進(jìn)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,他們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖。

  • 常見的場景:
    • 單 go 程自己死鎖
package main

import "fmt"

func main() {
  ch := make(chan int)
  ch <- 1
  fmt.Println("send")
  go func() {
    <- ch
    fmt.Println("received")
  }()
  fmt.Println("over")
}

互斥鎖

每個資源都對應(yīng)一個可稱為 “互斥鎖” 的標(biāo)記,這個標(biāo)記用來保證在任意時刻,只能有一個協(xié)程(線程)訪問該資源。其它的協(xié)程只能等待。

互斥鎖是傳統(tǒng)并發(fā)編程對共享資源進(jìn)行訪問控制的主要手段。它由標(biāo)準(zhǔn)庫 sync 中的 Mutex 結(jié)構(gòu)體類型表示。 sync.Mutex 類型只有兩個公開的指針方法,Lock 和 Unlock。Lock 鎖定當(dāng)前的共享資源,Unlock 進(jìn)行解鎖。

在使用互斥鎖時,一定要注意:對資源操作完成后,一定要解鎖,否則會出現(xiàn)流程執(zhí)行異常、死鎖等問題。通常借助 defer。鎖定后,立即使用 defer 語句保證互斥鎖及時解鎖。如下所示:

var mutex sync.Mutex

func write() {
  mutex.Lock()
  defer mutex.Unlock()
}

讀寫鎖

互斥鎖的本質(zhì)是當(dāng)一個 goroutine 訪問的時候,其他 goroutine 都不能訪問。這樣在資源同步,避免競爭的同時也降低了程序的并發(fā)性能。程序由原來的并行執(zhí)行變成了串行執(zhí)行。

讀寫鎖可以讓多個讀操作并發(fā),同時讀取,但是對于寫操作是完全互斥的。也就是說,當(dāng)一個 goroutine 進(jìn)行寫操作的時候,其他 goroutine 既不能進(jìn)行讀操作,也不能進(jìn)行寫操作。

讀時共享,寫時獨占。寫鎖優(yōu)先級比讀鎖高。

  • “寫鎖定”和“讀鎖定”

    func (*RWMutex) Lock()
    func (*RWMutex) Unlock()
    
  • “讀鎖定”和“寫鎖定”

    func (*RWMutex) RLock()
    func (*RWMutex) RUnlock()
    

條件變量

條件變量的作用并不能保證在同一時刻僅有一個協(xié)程(線程)訪問某個共享的數(shù)據(jù)資源,而是在對應(yīng)的共享數(shù)據(jù)的狀態(tài)發(fā)生變化時,通知阻塞在某個條件上的協(xié)程(線程)。條件變量不是鎖,在并發(fā)中不能達(dá)到同步的目的,因此條件變量總是與鎖一塊使用

Go 標(biāo)準(zhǔn)庫中的 sys.Cond 類型代表了條件變量。條件變量要與鎖(互斥鎖或者讀寫鎖)一起使用。成員變量 L 代表與條件變量搭配使用的鎖。

type Cond struct {
  noCopy noCopy
  L Locker
  notify notifyList
  checker copyChecker
}
  • 對應(yīng)的 3 個常用方法,Wait、Signal、Broadcast:
    • func (c *Cond) Wait()
      該函數(shù)的作用:
      • 阻塞等待條件變量滿足
      • 釋放已掌握的互斥鎖,相當(dāng)于 cond.Unlock()。注意:兩步為原子操作(不會被線程調(diào)度機制打斷的操作;這種操作一旦開始,就一直運行到結(jié)束)。
      • 當(dāng)被喚醒,Wait() 函數(shù)返回時,解除阻塞并重新獲取互斥鎖。相當(dāng)于 cond.Lock()
    • func (c *Cond) Signal()
      單發(fā)通知,給一個正等待(阻塞)在該條件變量上的 goroutine(線程)發(fā)送通知。
    • func (c *Cond) Broadcast()
      廣播通知,給正在等待(阻塞)在該條件變量上的所有 goroutine(線程)發(fā)送通知。
  • 使用流程:
    1. 創(chuàng)建條件變量:var cond sync.Cond
    2. 指定條件變量用的鎖:cond.L = new(sync.Mutex)
    3. cond.Lock() 給公共區(qū)加鎖(互斥量)
    4. 判斷是否達(dá)到阻塞條件(緩沖區(qū)滿/空) --- for 循環(huán)判斷
    5. 訪問公共區(qū) ---讀、寫數(shù)據(jù)、打印
    6. 解鎖條件變量用的鎖 cond.L.Unlock()
    7. 喚醒阻塞在條件變量上的對端。signal() Broadcast()
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容