我們的業務可能用不上攜程池,很多同事也不是很懂,之前和同事們溝通了一下,很多人對單例都理解不了,任務沒有必要,甚至用PHP寫單例都不會寫,也從沒想過使用,那可想而知,其他的設計模式一概不知了。這就是一直做PHP,沒做過其他語言的弊端,在此也建議其他人多多接觸下其他語言的概念,尤其是一些通用型的,業務上用不上,我們可以給在自己增加難度的來使用,不能總是守著一門指令碼做個五六年不放吧。雖然用不上,還是寫上,比如一些耗時的操作,我這邊會當作優化來處理。目前IOC(控制反轉) 、DI(依賴注入),等都沒用上過,這次的改寫都用上。之前做的Go開發工作都是改改bug,做些邊邊角料的活,這次除了系統的熟悉Go程式碼之外,也對一些常用的設計思想都接觸一下,一句話:沒難度給自己增加難度也要上,不然工作得多無趣,這也是一個成長的過程嘛。
鑑於我們用不上,先寫個Demo用下,後續優化的時候具體的再補上,但是對於Go而言,這一塊也是重點所在吧。昨天文章有大佬說濫用了程式碼塊,我也不是什麼老師,所以,這次我就我程式碼一次性貼出來,順便加上一個對外的函式介面,程式碼上我加上儘量詳細的註釋。參考網上的,不知道是誰寫的,但是 比我寫的要清楚多了,在基礎上我修改了部分程式碼。
//定義攜程池型別
type Pool struct {
//對外接收Task的入口
CommonTaskChan chan *CommonTask
//協程池最大工作數量,限定Goroutine的個數
ChannelNum int
//協程池內部的任務就緒佇列
JobChannels chan *CommonTask
}
//定義任務CommonTask型別,每一個任務CommonTask都可以抽象成一個函式
type CommonTask struct {
thread func() error //一個無參的函式型別
}
//通過CreateTask來建立一個CommonTask
func CreateTask(f func() error) *CommonTask {
t := CommonTask{
thread: f,
}
return &t
}
//執行CommonTask任務的方法
func (t *CommonTask) Execute() {
err:= t.thread() //呼叫任務所繫結的函式
if err !=nil {
fmt.Println("Execute err", err.Error())
}
}
//建立一個協程池
func NewGoPool(cap int) *Pool {
p := Pool{
CommonTaskChan: make(chan *CommonTask),
ChannelNum: cap,
JobChannels: make(chan *CommonTask),
}
return &p
}
//協程池建立一個協程並且開始工作
func (p *Pool) start(taskId int) {
//worker不斷的從JobChannels內部任務佇列中拿任務
for task := range p.JobChannels {
//如果拿到任務,則執行任務
task.Execute()
fmt.Println("taskId ID ", taskId, " 執行完畢任務")
}
}
//讓協程池Pool開始工作
func (p *Pool) Run() {
//1,首先根據協程池的協程數量限定,開啟固定數量的協程,
for i := 0; i < p.ChannelNum; i++ {
go p.start(i)
}
// 2、從協程池入口取外界傳遞過來的任務並且將任務送進JobChannels中
for task := range p.CommonTaskChan {
p.JobChannels <- task
}
//3, 執行完畢需要關閉JobChannels
close(p.JobChannels)
//4, 執行完畢需要關閉
close(p.CommonTaskChan)
}
func CallDemo() {
//建立一個Task
t := CreateTask(func() error {
fmt.Println(time.Now())
return nil
})
//建立一個協程池,最大開啟3個協程worker
p := NewGoPool(3)
//開一個協程 不斷的向 Pool 輸送列印一條時間的task任務
go func() {
for {
p.CommonTaskChan <- t
time.Sleep(10 * time.Second)
}
}()
//啟動協程池p
p.Run()
}
其中,CallDemo就是呼叫的方式。這裡,實際上行就是一個10秒中的定時任務。結果顯示是:
對於PHP而言,做一步操作,或者定時任務,只能藉助第三方的工具,重啟一個程式來定時執行介面,或者swoole的work程式來實現,藉助的是C擴充套件。上面建立協程池的時候,其實就可以看出,這是一個10秒鐘的定時任務,時間可以修改。go裡面其實有內建的time包來處理定時任務,直接上示例:
ticker := time.NewTicker(time.Minute * 1)
go func() {
for _ = range ticker.C {
fmt.Printf("ticked at %v", time.Now())
}
}()
這個可以因人而異,根據業務需求來做,其實第三方的就比較穩定,設定的定時格式也是相當豐富的,重點是不需要做保活或者其他的確認機制等。比如:supervisor,我們目前用的就是這個玩意,沒有守護程式之類的開發,水平不高,基本屬於寫業務的能手。這個有時間可以來研究一波,畢竟對於多程式的概念,我要不是做Android開發的,也是很少接觸這個概念的。當然,移動端不一定都會接觸多程式,筆者剛好有這個業務需求,所以研究過一段時間。裝逼了,不好意思。
我們目前討論的並沒有決定使用,因為我們之前的專案已經使用了,所以就簡單的寫上,後續決定使用的框架再來重新封裝。使用起來比我們想象的要簡單的多。
engine := gin.Default()
//loginGroup := engine.Group("/index.php/Login")
loginGroup := engine.Group("/Login")
loginGroup.POST("/login", controller.Login)
err := engine.Run(":8001")
if err != nil {
log.Fatal(err)
}
注意介面的對映,我們有時候會加上index.php,這個是可以設定處理的哈。Group是設定請求組,方便統一處理,也可以對組編寫過濾器和驗證器。埠最好放ymal配置檔案,上篇已經解釋過了。沒錯,
上面簡短的程式碼就可以直接使用了。簡單看下login的邏輯程式碼,沒什麼用處,只是做個演示。
func Login(context *gin.Context) {
var login LoginInfo
var loginParams LoginParams
header := context.Request.Header
accessToken := header.Get("accessToken")
fmt.Println("---header/---" + accessToken)
err := context.ShouldBind(&loginParams)
if err != nil {
utils.ReturnError(context, "登入失敗: "+err.Error())
return
}
sqlStr := "select id,LoginId,Pwd,FullName,DepartmentId,deleted ,session_id from c_login where LoginId=?"
rowObj := utils.DBClientPool.GetDB("callout").QueryRow(sqlStr, loginParams.Name)
err = rowObj.Scan(&login.id, &login.LoginId, &login.Pwd, &login.FullName, &login.DepartmentId, &login.deleted, &login.sessionId)
if err != nil {
utils.ReturnError(context, "登入失敗: "+err.Error())
return
}
fmt.Println(login)
if login.deleted == 1 {
panic("deleted")
}
utils.ReturnSuccess(context, "登入成功")
}
目前準備好這幾種工具就可以準備開始寫業務了,因為也寫業務的過程中會有很多細節上的問題需要時間解決。當然,也有點迫不及待改寫了,目測400+ 的介面數量,年前寫完時間上怕是不夠,主要是和各系統之間的互動問題需要重新改寫,優化的點也是個頭痛的問題,畢竟資料庫的優化除了加索引之外就剩下sql語句的優化了,而這次換成Go,我想的全程不涉及原始sql語句,使用模型和預編譯方式處理,這也是規範的必要性。後續寫程式碼的過程中需要新增的工具或者第三方的,再來詳細描述。
本作品採用《CC 協議》,轉載必須註明作者和本文連結