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. 如下:

<code>go 函數名 (參數列表)
go f(a int, b int, c int){
fmt.Println(a+b+c)
}(1,2,3)/<code>

2.2 channel 通道

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

<code>make(chan int)
make(chan int, 10)
chan /<code>

無緩衝通道: 要求接受和發送數據的 goroutine 同時準備好,否則將會阻塞.

有緩衝通道: 給予通道一個容量值,只要有值便可以接受數據,有空間便可以發送數據,可以不阻塞的完成.

單向通道: 默認情況通道是雙向的,可以接受及發送數據. 也可以創建單向通道,只能收或者發數據. 如下是單向接受通道

<code>var ch chan
/<code>

2.3 select

select: 可以監聽 channel 上的輸入/輸出操作, 類似於 select、epoll、poll 使得通道支持多路複用. select 是專門通道 channel 設計的. 它可以結合通道實現超時處理、判斷緩衝通道是否阻塞、退出信號量處理,如下:

<code>// 1. 超時機制
select {
case case fmt.Println("timeout 01")}​// 2. 退出信號量處理select {    case /<code>

2.4 內置函數 close()close() 函數用於關閉通道 channel 的,close 之後的 channel 還可以讀取數據,close() 函數由以下幾點使用要點:

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

示例:

<code>ch := make(chan int, 1)
ch close(ch) // 關閉ch
v, ok := v2,ok := /<code>

3. 阻塞、同步與異步

3.1 阻塞與非阻塞

golang 併發編程

阻塞: 阻塞是進程(也可以是線程、協程)的狀態之一(新建、就緒、運行、阻塞、終止). 指的是當數據未準備就緒,這個進程(線程、協程)一直等待,這就是阻塞.

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


3.2 同步與異步

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

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


3.3 四種組合

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

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


4. 鎖與定時器

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: 週期性定時器. 會按照初設定的時間重複計時.

示例:

<code>// timer
for {
timer.Reset(time.Second)
// 重設後才有效
}

// ticker
for {
}/<code>


5. 結語


5.1 思考題

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

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

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

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


1. Golang 併發編程 [https://www.cnblogs.com/konghui/p/10703615.html#close]

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


分享到:


相關文章: