聊聊併發,程序通訊方式,go協程簡單應用場景
阿新 • • 發佈:2021-01-09
## 開篇提問
1. 知道併發,並行,執行緒,協程概念嗎?或者知道大概含義嗎?
2. 有執行緒為什麼還要有協程?區別是什麼?
3. 『程序』通訊方式知道幾種?有沒有超過3種?
4. golang『協程』通訊方式推薦?
5. 使用併發的目的是為什麼?是能幫我們解決什麼問題嗎?
## 概念
**併發,執行緒,協程**:概念是不可能概念的,google去吧。或者點選[這裡,檢視上一篇文章](https://juejin.cn/post/6906408822701719560)
- 並行:一般是指多個CPU例項或者多臺機器在『同一時刻』同時執行某個邏輯(方法)
## 『程序』通訊方式
| 名稱 | 特點 |
| --------------------------- | ------------------------------------------------------------ |
| 管道/匿名管道(**pipe**) | 管道的實質是一個核心緩衝區 |
| 有名管道(**FIFO**) | 先進先出(first in first out);
以有名管道的檔案形式存在於檔案系統中;
| | 訊號(**Signal**) | 無需知道該程序的狀態;
阻塞程序;
非同步通訊; | | 訊息佇列(**Message Queue**) | 放在核心中的訊息連結串列;
允許一個或多個程序向它寫入與讀取訊息;
克服了訊號承載資訊量少缺陷;
目前主要有兩種型別的訊息佇列:POSIX訊息佇列以及System V訊息佇列,系統V訊息佇列目前被大量使用; | | 共享記憶體(**share memory**) | 使得多個程序可以可以直接讀寫同一塊記憶體空間,是最快的可用IPC形式;
由於多個程序共享一段記憶體,因此需要依靠某種同步機制(如訊號量)來達到程序間的同步及互斥; | | 訊號量(**semaphore**) | 訊號量是一個計數器,用於多程序對共享資料的訪問,訊號量的意圖在於程序間同步只能通過兩個標準原子操作:wait(semap) , signal(semap) ;進行訪問
訊號量是非負整型變數
操作也被成為PV原語(P來源於荷蘭語proberen"測試",V來源於荷蘭語verhogen"增加",P表示通過的意思,V表示釋放的意思) | | 套接字(**socket**) | 套接字是支援TCP/IP的網路通訊的基本操作單元
套接字的特性由3個屬性確定,它們分別是:域、埠號、協議型別。
| ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a9ae2a7c1e874246b998c9318a8ccda2~tplv-k3u1fbpfcp-zoom-1.image) > **訊號量與互斥量之間的區別:** > (1)互斥量用於執行緒的互斥,訊號量用於執行緒的同步。這是互斥量和訊號量的根本區別,也就是互斥和同步之間的區別。 > **互斥:**是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。 > **同步:**是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。 > 在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源 > (2)互斥量值只能為0/1,訊號量值可以為非負整數。 > 也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多執行緒互斥問題。訊號量可以實現多個同類資源的多執行緒互斥和同步。當訊號量為單值訊號量是,也可以完成一個資源的互斥訪問。 > (3)互斥量的加鎖和解鎖必須由同一執行緒分別對應使用,訊號量可以由一個執行緒釋放,另一個執行緒得到。 ## go協程通訊使用 基本上就是推薦使用channel,這個是最推薦的使用形式; 還有就是使用`sync.Mutex`互斥鎖進行加鎖通訊; 更詳細的介紹以後寫; ## go使用協程一些應用場景,簡單舉例 1. **進行互不相干的『迴圈』,需要等待結果計算** 這種情況下,一般是不同『資料集合』需要進行『處理』,在處理的過程中兩個資料集合對『結果』造成的影響**沒有時序行**; 這種情況下,完全可以採用兩個資料單獨進行協程處理然後再進行後續運算; ```go // 虛擬碼 var result, data1, data2 int32 done1 := make(chan bool) done2 := make(chan bool) // 第一個資料集合,需要求和 go func() { for _, val := range dataset1 { data1 += val } done1 <- true } // 第二個資料集合,需要求和 go func() { for _, val := range dataset2 { data2 += val } done2 <- true } // 等待協程完成運算 <-done1 <-done2 // 結果進行相加 result = data1 + data2 ``` 2. **需要額外進其他不相干的業務,不耽誤『主協程』的返回值,不等待** 一般有些業務處理以後,有些『額外工作』需要處理但是不耽誤主協程返回資料,這個時候就可以開個協程去做,不用等待 ```go // 虛擬碼 result, err := processMethod() if err != nil { ..... } // 需要對結果進行寫快取等其他操作,不耽誤資料返回 go func() { err = saveRedis(result) if err != nil { ..... } } return result ``` 3. **對某些任務進行時間限制,『超時關閉』當前操作** 例如,通過管道channel傳送某些資料,若超時則自動放棄本次傳送,關閉通道。 ```go // 定義兩個有緩衝通道,容量分別為1 c1 := make(chan string, 1) c2 := make(chan string, 1) go func() { time.Sleep(time.Second * 1) // 隔1秒傳送資料 c1 <- "data1" }() go func() { time.Sleep(time.Second * 6) // 隔6秒傳送資料 c2 <- "data2" }() for i := 0; i < 2; i++ { // 給通道建立容忍時間,如果5s內無法讀寫,就即刻返回 tm := time.NewTimer(time.Second * 5) // 使用select來獲取這兩個通道的值,然後輸出 select { case data1 := <-c1: // 接收c1通道資料(消費資料) fmt.Println(msg1) case data2 := <-c2: // 接收c2通道資料(消費資料) fmt.Println(msg2) case <-tm.C: fmt.Println("timeout!") } } ``` ## 篇末提問 1. 使用過協程嗎?知道協程與執行緒的區別嗎? 2. 如果是單核CPU,開協程會有用嗎? 3. 本文由於沒有幫助你提升code能力? 4. 程序通訊方式有沒有一點點了解? 5. 你會使用本文的協程案例提升執行速
以有名管道的檔案形式存在於檔案系統中;
| | 訊號(**Signal**) | 無需知道該程序的狀態;
阻塞程序;
非同步通訊; | | 訊息佇列(**Message Queue**) | 放在核心中的訊息連結串列;
允許一個或多個程序向它寫入與讀取訊息;
克服了訊號承載資訊量少缺陷;
目前主要有兩種型別的訊息佇列:POSIX訊息佇列以及System V訊息佇列,系統V訊息佇列目前被大量使用; | | 共享記憶體(**share memory**) | 使得多個程序可以可以直接讀寫同一塊記憶體空間,是最快的可用IPC形式;
由於多個程序共享一段記憶體,因此需要依靠某種同步機制(如訊號量)來達到程序間的同步及互斥; | | 訊號量(**semaphore**) | 訊號量是一個計數器,用於多程序對共享資料的訪問,訊號量的意圖在於程序間同步只能通過兩個標準原子操作:wait(semap) , signal(semap) ;進行訪問
訊號量是非負整型變數
操作也被成為PV原語(P來源於荷蘭語proberen"測試",V來源於荷蘭語verhogen"增加",P表示通過的意思,V表示釋放的意思) | | 套接字(**socket**) | 套接字是支援TCP/IP的網路通訊的基本操作單元
套接字的特性由3個屬性確定,它們分別是:域、埠號、協議型別。
| ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a9ae2a7c1e874246b998c9318a8ccda2~tplv-k3u1fbpfcp-zoom-1.image) > **訊號量與互斥量之間的區別:** > (1)互斥量用於執行緒的互斥,訊號量用於執行緒的同步。這是互斥量和訊號量的根本區別,也就是互斥和同步之間的區別。 > **互斥:**是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。 > **同步:**是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。 > 在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源 > (2)互斥量值只能為0/1,訊號量值可以為非負整數。 > 也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多執行緒互斥問題。訊號量可以實現多個同類資源的多執行緒互斥和同步。當訊號量為單值訊號量是,也可以完成一個資源的互斥訪問。 > (3)互斥量的加鎖和解鎖必須由同一執行緒分別對應使用,訊號量可以由一個執行緒釋放,另一個執行緒得到。 ## go協程通訊使用 基本上就是推薦使用channel,這個是最推薦的使用形式; 還有就是使用`sync.Mutex`互斥鎖進行加鎖通訊; 更詳細的介紹以後寫; ## go使用協程一些應用場景,簡單舉例 1. **進行互不相干的『迴圈』,需要等待結果計算** 這種情況下,一般是不同『資料集合』需要進行『處理』,在處理的過程中兩個資料集合對『結果』造成的影響**沒有時序行**; 這種情況下,完全可以採用兩個資料單獨進行協程處理然後再進行後續運算; ```go // 虛擬碼 var result, data1, data2 int32 done1 := make(chan bool) done2 := make(chan bool) // 第一個資料集合,需要求和 go func() { for _, val := range dataset1 { data1 += val } done1 <- true } // 第二個資料集合,需要求和 go func() { for _, val := range dataset2 { data2 += val } done2 <- true } // 等待協程完成運算 <-done1 <-done2 // 結果進行相加 result = data1 + data2 ``` 2. **需要額外進其他不相干的業務,不耽誤『主協程』的返回值,不等待** 一般有些業務處理以後,有些『額外工作』需要處理但是不耽誤主協程返回資料,這個時候就可以開個協程去做,不用等待 ```go // 虛擬碼 result, err := processMethod() if err != nil { ..... } // 需要對結果進行寫快取等其他操作,不耽誤資料返回 go func() { err = saveRedis(result) if err != nil { ..... } } return result ``` 3. **對某些任務進行時間限制,『超時關閉』當前操作** 例如,通過管道channel傳送某些資料,若超時則自動放棄本次傳送,關閉通道。 ```go // 定義兩個有緩衝通道,容量分別為1 c1 := make(chan string, 1) c2 := make(chan string, 1) go func() { time.Sleep(time.Second * 1) // 隔1秒傳送資料 c1 <- "data1" }() go func() { time.Sleep(time.Second * 6) // 隔6秒傳送資料 c2 <- "data2" }() for i := 0; i < 2; i++ { // 給通道建立容忍時間,如果5s內無法讀寫,就即刻返回 tm := time.NewTimer(time.Second * 5) // 使用select來獲取這兩個通道的值,然後輸出 select { case data1 := <-c1: // 接收c1通道資料(消費資料) fmt.Println(msg1) case data2 := <-c2: // 接收c2通道資料(消費資料) fmt.Println(msg2) case <-tm.C: fmt.Println("timeout!") } } ``` ## 篇末提問 1. 使用過協程嗎?知道協程與執行緒的區別嗎? 2. 如果是單核CPU,開協程會有用嗎? 3. 本文由於沒有幫助你提升code能力? 4. 程序通訊方式有沒有一點點了解? 5. 你會使用本文的協程案例提升執行速