天天看點

golang select default continue_golang 并發程式設計

golang select default continue_golang 并發程式設計
并發是 golang 的優勢之一,使用關鍵字 go 可以很友善的開啟一個協程. go 語言中,常常用 go、chan、select 及 sync 庫完成并發操作,處理同步、異步、阻塞、非阻塞任務.

1. 概要

go 語言的并發程式設計,以下是需要了解的基礎知識點,也是本文主要介紹的内容. 可以對照看看這些是否已經可以熟練運用了.

  • 阻塞 : 阻塞是程序(也可以是線程、協程)的狀态之一(建立、就緒、運作、阻塞、終止). 指的是當資料未準備就緒,這個程序(線程、協程)一直等待,這就是阻塞.
  • 非阻塞 : 當資料為準備就緒,該程序(線程、協程)不等待可以繼續執行,這就是非阻塞.
  • 同步 : 在發起一個調用時,在沒有得到結果之前,這個調用就不傳回,這個調用過程一直在等待. 這是同步.
  • 異步 : 在發起調用後,就立刻傳回了,這次調用過程就結束了. 等到有結果了被調用方主動通知調用者結果. 這是異步.
  • go(協程) : 通過關鍵字 go 即可建立一個協程.
  • chan : golang 中用于并發的通道,用于協程的通信.
    • 有緩沖通道
    • 無緩沖通道
    • 單向通道
  • select : golang 提供的多路複用機制.
  • close() : golang 的内置函數, 可以關閉一個通道.
  • sync : golang 标準庫之一,提供了鎖.
  • 定時器 : golang 标準庫 time 提供的重要功能, 提供了定時器功能,可用于逾時處理.
    • Timer
    • Ticker

2. go 并發程式設計

go 的并發程式設計采用的 CSP (Communicating Sequential Process) 模型,主要基于協程 goroutine 和通道 channel .

2.1 協程 go

在 go 語言中,并發程式設計使用關鍵字 go 即可快速啟動一個并發運作的 goroutine. 如下:

go 函數名 (參數清單)
go f(a int, b int, c int){
  fmt.Println(a+b+c)
}(1,2,3)
           
2.2 channel 通道

golang 提供了通道類型 chan,用于在并發操作時的通信,它本身就是并發安全的. 通過 chan 可以建立無緩沖、緩沖通道,滿足不同需求. 寫法如下:

make(chan int)
make(chan int, 10)
<- chan
chan <-
           
無緩沖通道:

要求接受和發送資料的 goroutine 同時準備好,否則将會阻塞.

有緩沖通道:

給予通道一個容量值,隻要有值便可以接受資料,有空間便可以發送資料,可以不阻塞的完成.

單向通道

: 預設情況通道是雙向的,可以接受及發送資料. 也可以建立單向通道,隻能收或者發資料. 如下是單向接受通道

var ch chan
  <- float64
           
2.3 select select:

可以監聽 channel 上的輸入/輸出操作, 類似于 select、epoll、poll 使得通道支援多路複用. select 是專門通道 channel 設計的. 它可以結合通道實作

逾時處理、判斷緩沖通道是否阻塞、退出信号量處理

,如下:

// 1. 逾時機制
select {
  case <-ch:
  case <-timeout:
  fmt.Println("timeout 01")}​// 2. 退出信号量處理select {    case <- quitChan:        return    default:}​// 3. 判斷緩沖通道是否已滿ch := make(chan int, 5)ch <- 1select {    case ch <- 2:        fmt.Println("channel value is", <-ch)    default:        fmt.Println("channel blocking")}
           
2.4 内置函數 close()

close() 函數用于關閉通道 channel 的,close 之後的 channel 還可以讀取資料,close() 函數由以下幾點使用要點:

  1. 隻能關閉雙向通道或者發送通道
  2. 它應該由發送者使用,而不應該由接受者調用
  3. 當通道關閉後,接受者都不再阻塞,
  4. 關閉通道後,依然可以從通道中讀取值
  5. 所有元素讀取完後,将傳回通道元素的零值,并且讀取檢測值也是 false

示例:

ch := make(chan int, 1)
ch <- 3
close(ch) // 關閉ch
v, ok := <- ch // 3,true
v2,ok := <- ch // 0,false
           

3. 阻塞、同步與異步

3.1 阻塞與非阻塞
golang select default continue_golang 并發程式設計

程序狀态

阻塞

: 阻塞是程序(也可以是線程、協程)的狀态之一(建立、就緒、運作、阻塞、終止). 指的是當資料未準備就緒,這個程序(線程、協程)一直等待,這就是阻塞.

非阻塞

: 當資料為準備就緒,該程序(線程、協程)不等待可以繼續執行,這就是非阻塞.

3.2 同步與異步 同步

: 在發起一個調用時,在沒有得到結果之前,這個調用就不傳回,這個調用過程一直在等待. 這是同步.

異步

: 在發起調用後,就立刻傳回了,這次調用過程就結束了. 等到有結果了被調用方主動通知調用者結果. 這是異步.

3.3 四種組合

同步、異步、阻塞、非阻塞可以組合成四種并發方式:

  • 同步阻塞調用 :得不到結果不傳回,線程進入阻塞态等待。
  • 同步非阻塞調用 :得不到結果不傳回,線程不阻塞一直在CPU運作。
  • 異步阻塞調用 :去到别的線程,讓别的線程阻塞起來等待結果,自己不阻塞。
  • 異步非阻塞調用 :去到别的線程,别的線程一直在運作,直到得出結果。

4. golang 标準庫的鎖與定時器

4.1 鎖與 sync 庫

并發程式設計中,為了確定并發安全,可以使用鎖機制. golang 提供了标準庫 sync ,它實作了并發需要的各種鎖. 包括:

  • Mutex : 互斥鎖,有倆個方法 Lock() Unlock() , 它隻能同時被一個 goroutine 鎖定,其它鎖再次嘗試鎖定将被阻塞,直到解鎖.
  • RWMutex : 讀寫鎖,有四個方法, Lock() 寫鎖定、 Unlock() 寫解鎖、 RLock() 讀鎖定、 RUnlock() 讀解鎖,讀鎖定和寫鎖定隻能同時存在一個. 隻能有一個協程處于寫鎖定狀态,但是可以有多個協程處于讀鎖定狀态. 即寫的時候不可讀,讀的時候不可寫. 隻能同時有一個寫操作確定資料一緻性. 而可以多個協程同時讀資料,確定讀操作的并發性能.

此外在 go 的并發程式設計中,還會常用到 sync 的以下内容:

  • sync.Map : 并發安全的字典 map
  • sync.WaitGroup : 用來等待一組協程的結束,常常用來阻塞主線程.
  • sync.Once : 用于控制函數隻能被使用一次,
  • sync.Cond : 條件同步變量. 可以通過 Wait()方法阻塞協程,通過 Signal()、Broadcast() 方法喚醒協程.
  • sync.Pool : 一組臨時對象的集合,是并發安全的. 它主要是用于存儲配置設定但還未被使用的值,避免頻繁的重新配置設定記憶體,減少 gc 的壓力.
3.2 time 庫的定時器

golang 的标準庫 time 中提供了定時器功能,并提供通道 channel 變量進行定時通知. time 庫中提供了兩種定時器:

  • time.Timer: 定時器 timer 在建立指定時間後,向通道 time.Timer.C 發送資料. 之後需要使用 Reset 設定定時器時間.
  • time.Ticker: 周期性定時器. 會按照初設定的時間重複計時.
示例:
// timer
for {
  <- timer.C
  timer.Reset(time.Second)
  // 重設後才有效
}

// ticker
for {
  <- ticker.C // 周期性有效
}
           

5. 結語

5.1 思考題

1. golang 中 select 的多個 case 同時成立,那麼選擇的是哪一個?

2. golang 中除了使用 sync 鎖,還可以如何保證并發安全? atomic 是什麼?

3. sync.Map 對鍵的類型有什麼要求麼?

4. 如何避免死鎖? golang 中如何檢測死鎖?

5.2 參考資料

1. Golang 并發程式設計 [https://www.cnblogs.com/konghui/p/10703615.html#close]

2. 深入了解并發/并行,阻塞/非阻塞,同步/異步[https://cloud.tencent.com/developer/article/1339622]