go的協程及channel與web開發的一點小實踐

lightTrace發表於2018-10-11

前言

之前在網上看go的協程和channel的東西,感覺都是一個main方法闖天下,並沒有很好的體會到channel的美妙,然後自己花了點時間寫了一點簡單的demo,然後不斷的變換,去看效果和輸出。

思路

我用channel去實現產生累積的web請求,然後用sleep去休眠消費者協程造成處理業務場景的假象,然後開多個消費者協程去處理web請求。

程式碼

package main

import (
	"fmt"
	"log"
	"net/http"
	"strings"
	"time"
)
//實際的業務處理
func sayhelloName(w http.ResponseWriter, r *http.Request) {
    param := r.URL.Query()
    id := param.Get("id")
    idArr := strings.Split(id,",")
    //呼叫生產者
	go produceIdToChannel(idArr)
	//由於消費者用了sleep去休眠造成處理業務的假象,所以這裡用多協程的方式去處理,大家可以試著取消迴圈來起多個執行緒的方法,就用一個執行緒來跑,看看花費的時間是多少?
	for i:=0;i<len(idArr);i++{
    go consumIdFromChannel()
	}

	fmt.Fprintf(w, "Hello first webchannel!") //這個寫入到w的是輸出到客戶端的
}

func main() {
	http.HandleFunc("/", sayhelloName) //設定訪問的路由
	err := http.ListenAndServe(":9090", nil) //設定監聽的埠
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}
//宣告一個channel模擬佇列,如果這裡用一個緩衝的chan呢,看看話費的時間又是多少呢?
var picIdChannel chan string = make(chan string)

func produceIdToChannel(id []string){
	t := time.Now()
	for i:=0;i<len(id);i++  {
		picIdChannel <- id[i]
		fmt.Println("生產picId",id[i])
	}
	fmt.Println("花費時間",time.Since(t))   
}

func consumIdFromChannel(){

	for{
		time.Sleep(time.Second * 10)
		e,_ := <-picIdChannel
		fmt.Println("消費picId",e)
	}

}

web請求

http://localhost:9090/sayhelloName?id=1,2,3,4,5,6,7,8,9,10   

會發現迴圈來起多個執行緒的方法話費的時間

花費時間 10.011475s

假如去掉迴圈就一個協程來跑話費的時間

花費時間 60.0036594s

這就是起多個協程的威力,多個協程分別處理掉了每個業務裡面接收通道值之前的業務(在這裡為模擬的 time.Sleep(time.Second * 10)
),後面通道釋放立馬跟上處理通道資料即可!

如果是改為緩衝的chan,會發現花費時間為0,這就很好理解為什麼不緩衝的chan是阻塞的,消費掉當前的訊息下一個訊息才會進來!

如果多次連續請求這個url,會依次累積處理。

大家和還可以自己隨心改動,自己去體驗思考這樣才會對channel和協程有更清晰的認識!

問題

其實我這裡也有一些問題,比如消費者開一個for迴圈來處理chan裡面的值是否合適?有沒有更好的解決方法?select在我這個demo裡怎麼用最合適?協程的數量一般多少為好?

應該還有很多問題我沒想到的,希望後面的學習實踐中這些問題都會有答案,也希望有大佬給我指正答疑。

相關文章