淺談併發、進程通信方式、Go協程三者的簡單應用場景

一、概念

(1)併發的概念及其重要性

在早期,CPU都是以單核的形式順序執行機器指令。Go語言的祖先C語言正是這種順序編程語言的代表。順序編程語言中的順序是指:所有的指令都是以串行的方式執行,在相同的時刻有且僅有一個CPU在順序執行程序的指令。

隨着處理器技術的發展,單核時代以提升處理器頻率來提高運行效率的方式遇到了瓶頸,目前各種主流的CPU頻率基本被鎖定在了3GHZ附近。單核CPU的發展的停滯,給多核CPU的發展帶來了機遇。相應地,編程語言也開始逐步向並行化的方向發展。Go語言正是在多核和網絡化的時代背景下誕生的原生支持併發的編程語言。

著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
併發:併發程序指同時進行多個任務的程序。在操作系統中,是指一個時間段中有幾個程序都處於已啓動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行,但任一個時刻點上只有一個程序在處理機上運行。

(2)線程和協程概念的概念及其重要性

線程:線程是操作系統能夠進行運算調度的最小單位。一個進程可以包含多個線程,是進程中的實際運作單位。

協程:又稱微線程。協程是一種用戶態的輕量級線程。協程擁有自己的寄存器上下文和棧。協程調度切換時,將寄存器上下文和棧保存到其他地方,在切回來的時候,恢復先前保存的寄存器上下文和棧。

爲什麼會誕生協程?

雖然多線程在前互聯網世代已經足夠使用,但是線程的侷限性也比較明顯,線程數量有限,一般不會很多,線程佔據的資源通常比我們需要的多得多,造成浪費,每個系統級線程開闢都會佔用空間,這個空間可能是MB級別,但是我們如果使用的線程只需要傳遞KB級別數據,那麼線程看起來就會比較浪費,但是又不可避免。而且線程之間的切換也會佔用一些額外開銷。爲了解決上面的矛盾問題,協程誕生了:更小的資源開支,動態調配資源,比線程更輕量。

協程的一些優點:

因爲子程序切換不是線程切換,而是由程序自身控制,因此,沒有線程切換的開銷,和多線程比,線程數量越多,協程的性能優勢就越明顯。
不需要多線程的鎖機制,因爲只有一個線程,也不存在同時寫變量衝突,在協程中控制共享資源不加鎖,只需要判斷狀態就好了,所以執行效率比多線程高很多。

二、進程通信方式

  1. 管道/匿名管道(pipe):管道的實質是一個內核緩衝區
  2. 有名管道(FIFO):先進先出(first in first out),以有名管道的文件形式存在於文件系統中
  3. 信號(Signal) :無需知道該進程的狀態、阻塞進程、異步通信
  4. 消息隊列(Message Queue):放在內核中的消息鏈表,允許一個或多個進程向它寫入與讀取消息。克服了信號承載信息量少缺陷,目前主要有兩種類型的消息隊列:POSIX消息隊列以及SystemV消息隊列,系統V消息隊列目前被大量使用。
  5. 共享內存(share memory):使得多個進程可以可以直接讀寫同一塊內存空間,是最快的可用IPC形式,由於多個進程共享一段內存,因此需要依靠某種同步機制(如信號量)來達到進程間的同步及互斥
  6. 信號量(semaphore):信號量是一個計數器,用於多進程對共享數據的訪問,信號量的意圖在於進程間同步只能通過兩個標準原子操作:wait(semap)
    , signal(semap),進行訪問信號量是非負整型變量操作也被成爲PV原語(P來源於荷蘭語proberen"測試",V來源於荷蘭語verhogen"增加",P表示通過的意思,V表示釋放的意思)。
  7. 套接字(socket) :套接字是支持TCP/IP的網絡通信的基本操作單元,套接字的特性由3個屬性確定,它們分別是:域、端口號、協議類型。

在這裏插入圖片描述
信號量與互斥量之間的區別:
(1)互斥量用於線程的互斥,信號量用於線程的同步。這是互斥量和信號量的根本區別,也就是互斥和同步之間的區別。

互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。
在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源

(2)互斥量值只能爲0/1,信號量值可以爲非負整數。
也就是說,一個互斥量只能用於一個資源的互斥訪問,它不能實現多個資源的多線程互斥問題。信號量可以實現多個同類資源的多線程互斥和同步。當信號量爲單值信號量是,也可以完成一個資源的互斥訪問。
(3)互斥量的加鎖和解鎖必須由同一線程分別對應使用,信號量可以由一個線程釋放,另一個線程得到。

三、Go協程通訊使用

基本上就是推薦使用channel,這個是最推薦的使用形式;

還有就是使用sync.Mutex互斥鎖進行加鎖通訊;

四、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、需要額外進其他不相干的業務,不耽誤『主協程』的返回值,不等待
一般有些業務處理以後,有些『額外工作』需要處理但是不耽誤主協程返回數據,這個時候就可以開個協程去做,不用等待

// 僞代碼

result, err := processMethod()
if err != nil {
   
   
    .....
}
// 需要對結果進行寫緩存等其他操作,不耽誤數據返回
go func() {
   
   
    err = saveRedis(result)
    if err != nil {
   
   
    	.....
	}
}

return result

3、對某些任務進行時間限制,『超時關閉』當前操作
例如,通過管道channel發送某些數據,若超時則自動放棄本次發送,關閉通道。

// 定義兩個有緩衝通道,容量分別爲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!")
    }
}

還等什麼小編推薦自己的linuxC/C++語言交流羣:【1106675687】整理了一些個人覺得比較好的學習書籍、視頻資料共享在羣文件裏面,有需要的可以自行添加哦!前100名進羣領取,額外贈送一份價值199的C/C++、linux資料包含(視頻教程、電子書、實戰項目及代碼),下面部分展示。
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章