1. 程式人生 > >GO語言高併發學習心得體會

GO語言高併發學習心得體會

訊號

sigRecv1:=make(chan os.Signal,1)
sigs1:=[]os.Signal{syscall.SIGINT,syscall.SIGQUIT}
signal.Notify(sigRecv1,sigs1...)


sigRecv2:=make(chan os.Signal,1)
sigs2:=[]os.Signal{syscall.SIGINT,syscall.SIGQUIT}
signal.Notify(sigRecv2,sigs2...)

// 接著我們用兩個for迴圈來接收訊息.考慮到主程式會退出,所以用waitgroup等待完成後退出.

var wg sync.WaitGroup
wg.Add(2) go func(){ for sig:=range sigRecv1{ fmt.Printf("Received a signal from sigRecv1%v",sig) } fmt.Printf("End. [sigRecv1]\n") }() go func(){ for sig:=range sigRecv2{ fmt.Printf("Received a signal from sigRecv1%v",sig) } fmt.Printf("End. [sigRecv2]\n") }() wg.Wait
()

int socket(int domain,int type,int protocol)
image
socket type 型別
image

TCP UDP

tcp 連結型別

image
可能會傳回io.EOF,表明連線結束

var dataBuffer bytes.Buffer
b:=make([byte,10])

for {

    n,err:=conn.Read(b)
    if err!=nil{
        if err==io.EOF{
            fmt.Println("The connection is closed.")
            conn.Close
() } else{ fmt.Printf("Read Error:%s\n",err) } break } dataBuffer.Write(b[:n]) }

bufio 是 Buffered I/O縮寫.

由於net.Conn型別實現了介面型別io.Reader中的Read介面,所以該介面的型別的一個實現型別。因此,我們可以使用bufio.NewReader函式來包裝變數conn,像這樣:
reader:=bufio.NewReader(conn)
可以呼叫reader變數的ReadBytes(“\n”)來接受一個byte型別的引數.
當然很多情況下並不是查詢一個但直接字元那麼簡單。比如,http協議中規定,訊息頭部的資訊的末尾是兩個空行,即是字串”\r\n\r\n”,
writer:=bufio.NewWriter(conn)來寫入資料
### close()
關閉連線

### LocalAddr & RemoteAddr 方法
conn.RmeoteAddr().Network() .String()

### SetDeadline, SetReadDeadline,SetWrteDeadline

b:=make([]byte,10)
conn.SetDeadline(time.Now().Add(2*time.Second))
for {
n,err:=conn.Read(b)
}

我們通過呼叫time.Now()方法當前絕對時間加2s.2s後的那一刻.假設之後的第二次迭代時間超過2s.則第三次迭代尚未開始,就已經超時了,所以改為.
“`
b:=make([]byte,10)
for {
conn.SetDeadline(time.Now().Add(2*time.Second))

n,err:=conn.Read(b)

}
“`

### 一個server 的簡單實現
image

緩衝器中的快取機制,在很多時候,它會讀取比足夠多更多一點的資料到其中的緩衝器。會產生提前讀取的問題.

net/http在 net/tcp的基礎上構建了非常好用的介面,除此以外,標準庫,net/rcp中api為我們提供了兩個go程式之間建立通訊和交換資料的另外一種方式。
遠端過程呼叫(remote procedure call)也是基於TCP/IP協議棧的。
Unix系統中,POSIX標準中定義的執行緒及其屬性和操作方法被廣泛認可.
Linux中稱為NPTL。

## 多執行緒

執行緒一個id, TID.
編寫高併發程式的建議:
1. 控制臨界區的純度。臨界區中的僅僅包含操作共享資料的程式碼。
1. 控制臨界區的粒度。
1. 減少臨界區中程式碼的執行耗時。
1. 避免長時間的持有互斥變數。
1. 優先使用院子操作而不是互斥量。

GO語言是作業系統提供的核心執行緒之上搭建了一個特有的兩級執行緒模型。
Goroutine代表的正確的含義:
不要用共享記憶體的方式來通訊。作為替代,應該用通訊作為手段來共享記憶體。
把資料放在共享記憶體區域中供多個執行緒中的程式訪問的這種方式雖然在基本思想上非常簡單,但是卻讓併發訪問控制變得異常複雜。只有做好了很多約束和限制,才有可能讓這些簡單的方法正確的實施。但是正確性的同時,也需要有可伸縮性。
Go語言不推薦用共享記憶體區的方式傳遞資料。作為替代,優先使用Channel。作為多個Goroutine之間的傳遞資料,並且還會保證其過程的同步。
GO語言執行緒3個必須知道的核心元素。
M:machine.一個M代表了一個核心執行緒。
P:processor.一個P代表了M所需的上下文Content.
G:Goroutine.一個G代表了對一段需要被併發執行的GO語言程式碼的封裝。

GO併發程式設計.

type IntChan chan int.
元素型別為int 的通道型別。
這樣的通道是雙向型別的。

如果向一個nil(被關閉的channel)傳送訊息,則這個程序會被永久阻塞。
需要注意的是:當我們向一個通道傳送訊息的時候,我們得到的是一個值的copy,當這個copy形成之後,之後對原來的值的修改都不會影響通道中的值。

select例子:

select語句和switch語句不同的是,跟在每個case 後面的只能是針對某個通道的傳送語句或者接受語句。
針對每個case 都有初始化了一個通道。通道的型別不受任何約束。元素的型別和容量是任意的。

分支選擇規則

重做到右,從上到下。
但是當系統發現第一個滿足選擇條件的case時,執行時系統就會執行該case所包含的語句。這也意味著其他case 會被忽略。如果同時有多個case滿足條件,那麼執行時,系統會通過一個偽隨機演算法決定那個case會被執行。例如下面的程式碼,傳送5個範圍在【1,3】的整數:

package main

import (
    "fmt"
)

func main() {
    chanCap := 5
    ch7 := make(chan int, chanCap)
    for i := 0; i < chanCap; i++ {
        select {
        case ch7 <- 1:
        case ch7 <- 2:
        case ch7 <- 3:
        }

    }
    for i := 0; i < chanCap; i++ {
        fmt.Printf("%v\n", <-ch7)
    }
}

但是,如果所有的case 都不滿足(並且沒有default case),就會阻塞住。直到某個case 被觸發。
所以通常情況下,default case 都是必要的.且default只能包含一個default case.
兩個變數賦值, e,ok:=<-chan
第二個變數指的是通道是否被關閉。

package main

import (
    "fmt"
)

func main() {
    go func(){
        for {
            select{
            case e,ok:=<-ch11:
                if !ok{
                    fmt.Println("End.")
                    break
                }else{
                    fmt.Printf("%d\n",e)ß
                }

            }
        }
    }
}


//修改方案如下,不然會死鎖。
func main(){
    go func(){
        var e int
        ok:=true   //宣告在外,方便外層使用
        for {
            select{
            case e,ok=<-ch11:
                if !ok{
                    fmt.Println("End.")
                    break

                }else{
                    fmt.Printf("%d\n",e)
               }
            }
        if !ok{
            break
        }
        }
    }
}

有的時候我們需要早點關閉流程。這裡我們新增新的超時行為。

timeout:=make(chan bool,1)
go func(){
    time.Sleep(time.Millisecond)
    timeout<-false
}

在原來的基礎上,新增

go func(){
    var e int
    ok := true
    for {
       select{
       case e,ok=<-ch11:
        ...
        case ok=<-timeout:
        fmt.Println("Timeout.")
        break
       }
}
    if !ok{
     break
    }
}

非緩衝的Channel

圖片

緩衝通道:由於元素的傳遞是非同步的,所以傳送操作在成功向通道傳送元素值之後就會立即結束。
非緩衝通道:等待能夠接受該元素值的那個接收操作。並且只有確保成功接收,才會真正的完成執行。

package main

import (
    "fmt"
    "time"
)

func main() {
    unbufChan := make(chan int)
    go func() {
        fmt.Printf("Sleep a second ...\n")
        time.Sleep(time.Second)
        num := <-unbufChan
        fmt.Printf("Received a integer %d.\n", num)

    }()
    num := 1
    fmt.Printf("Send integer %d ...\n", num)
    unbufChan <- num
    fmt.Printf("Done")
}

select 語句與非緩衝通道

在使用select語句向某個非緩衝通道傳送元素的時候,我們需要打起雞血。因為,與操作緩衝通道的select語句相比,它被阻塞的概率非常之大。其基本原因依然是非緩衝通道會以同步的方式傳遞元素值。

time包與Channel

定時器

首先,結構體型別,time.Timer. new的兩種方法:

time.NewTimer(Duration) & time.AfterFunc.

Duration=3*time.Hour+36*.time.Minute

Timer{
    Reset()
    Stop()
}
//到達時間,是通過欄位C(chan time.Time)緩衝通道。 時間一道向自己傳送一個time.Time型別元素。絕對到期時間。

重構之前的結構:

case <-time.NewTimer(time.Millisecond).C:
    fmt.Println("Timeout")
    ok=false
    break

定時器是可以被複用的。所以在case中的接受雨具中初始化是浪費的。下面的使用方式更好:

go func(){
   var timer *time.Timer
   for{
        select{
            case <-func()<-chan time.Time{
                if timer==nil{
                    timer=time.NewTimer(time.Millisecond)
                }else{
                    timer.Reset(time.Millisedcond)
                }
                return timer.C
            }():
            fmt.Println("Timeout")
            ok=false
            break
        }
   }
}

斷續器

斷續器與定時器的使用場景不同,當作超時觸發器是不合適的。因為它對到期事件的傳達雖然可能被放棄,當絕對不會被延誤。斷續器一旦被初始化,它所有的到期時間都是固定的了。
固定不變的到期時間恰恰使斷續器非常適合被作為定時任務的觸發器。
例如要求兩次執行之間的最短間隔時間為10分鐘。我們可以這樣編寫滿足這一需求的程式碼:

高併發負載均衡器

QPS(Query Per Second,每秒查詢量) & TPS(Transactions Per Second,每秒事物處理量)。

切片和陣列都不是併發安全的。所以用一個chan來儲存結果。