日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Go channel 的妙用

發(fā)布時(shí)間:2024/4/11 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Go channel 的妙用 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

昨天在內(nèi)網(wǎng)上看到一篇講數(shù)據(jù)庫連接的文章,列出了一些 sql 包的一些源碼,我注意到其中取用、歸還連接的方式非常有意思——通過臨時(shí)創(chuàng)建的 channel 來傳遞連接。

在 sql.DB 結(jié)構(gòu)體里,使用 freeConn 字段來表示當(dāng)前所有的連接,也就是一個(gè)連接池。

type?DB?struct?{freeConn?????[]*driverConn }

當(dāng)需要拿連接的時(shí)候,從 freeConn 中取出第一個(gè)元素:

conn?:=?db.freeConn[0] copy(db.freeConn,?db.freeConn[1:]) db.freeConn?=?db.freeConn[:numFree-1] conn.inUse?=?true

取 slice 切片的第一個(gè)元素,然后將 slice 后面的元素往前挪,最后通過截?cái)鄟怼搬尫拧弊詈笠粋€(gè)元素。

當(dāng)然,能進(jìn)行上述操作的前提是切片 db.freeConn 長度大于 0,即有空閑連接存在。如果當(dāng)前沒有空閑連接,那如何處理呢?接下來就是 channel 的妙用的地方。

sql.DB 結(jié)構(gòu)體里還有另一個(gè)字段 connRequests,它用來存儲當(dāng)前有哪些“協(xié)程”在申請連接:

type?DB?struct?{freeConn?????[]*driverConnconnRequests?map[uint64]chan?connRequest }

connRequests 的 key 是一個(gè) uint64類型,其實(shí)就是一個(gè)遞增加 1 的 key;而 connRequest 表示申請一個(gè)新連接的請求:

type?connRequest?struct?{conn?*driverConnerr??error }

這里的 conn 正是需要的連接。

當(dāng)連接池中沒有空閑連接的時(shí)候:

req?:=?make(chan?connRequest,?1) reqKey?:=?db.nextRequestKeyLocked() db.connRequests[reqKey]?=?req

先是構(gòu)建了一個(gè) chan connRequest,同時(shí)拿到了一個(gè) reqKey,將它和 req 綁定到 connRequests 中。

接下來,在 select 中等待超時(shí)或者從 req 這個(gè) channel 中拿到空閑連接:

select?{case?<-ctx.Done():case?ret,?ok?:=?<-req:if?!ok?{return?nil,?errDBClosed}return?ret.conn,?ret.err }

可以看到,select 有兩個(gè) case,第一個(gè)是通過 context 控制的 <-Done;第二個(gè)則是前面構(gòu)造的 <-req,如果從 req 中讀出了元素,那就相當(dāng)于獲得了連接:ret.conn。

那什么時(shí)候會向 req 中發(fā)送連接呢?答案是在向連接池歸還連接的時(shí)候。

前面提到,空閑連接是一個(gè)切片,歸還的時(shí)候直接 append 到這個(gè)切片就可以了:

func?(db?*DB)?putConnDBLocked(dc?*driverConn,?err?error)?bool?{db.freeConn?=?append(db.freeConn,?dc) }

但其實(shí)在 append 之前,還會去檢查當(dāng)前 connRequests 中是否有申請空閑連接的請求:

if?c?:=?len(db.connRequests);?c?>?0?{var?req?chan?connRequestvar?reqKey?uint64for?reqKey,?req?=?range?db.connRequests?{break}delete(db.connRequests,?reqKey)?//?Remove?from?pending?requests.if?err?==?nil?{dc.inUse?=?true}req?<-?connRequest{conn:?dc,err:??err,}return?true }?

如果有請求的話,直接將當(dāng)前連接“塞到” req channel 里去了。另一邊,申請連接的 goroutine 就可以從 req channel 中讀出 conn。

于是,通過 channel 就實(shí)現(xiàn)了一次“連接傳輸”的功能。

這讓我想到不久之前芮神寫的一篇《高并發(fā)服務(wù)遇redis瓶頸引發(fā)time-wait事故》,文中提到了將多個(gè) redis command 組裝為一個(gè) pipeline:

調(diào)用方把 redis command 和接收結(jié)果的 chan 推送到任務(wù)隊(duì)列中,然后由一個(gè) worker 去消費(fèi),worker 組裝多個(gè) redis cmd 為 pipeline,向 redis 發(fā)起請求并拿回結(jié)果,拆解結(jié)果集后,給每個(gè)命令對應(yīng)的結(jié)果 chan 推送結(jié)果。調(diào)用方在推送任務(wù)到隊(duì)列后,就一直監(jiān)聽傳輸結(jié)果的 chan。

redis commnd 組裝成 pipeline

這里的用法就和本文描述的 channel 用法一致。

細(xì)想一下,以上提到的 channel 用法很神奇嗎?我們平時(shí)沒有接觸過嗎?

我用過最多的是“生產(chǎn)者-消費(fèi)者”模式,先啟動 N 個(gè) goroutine 消費(fèi)者,讀某個(gè) channel,之后,生產(chǎn)者再在某個(gè)時(shí)候向 channel 中發(fā)送元素:

for?i?:=?0;?i?<?engine.workerNum;?i++?{go?func()?{for?{work?=?<-engine.workChan}} }

另外,我還會用 channel 充當(dāng)一個(gè) “ready” 的信號,用來指示某個(gè)“過程”準(zhǔn)備好了,可以接收結(jié)果了:

func?(j?*Job)?Finished()?<-chan?bool?{return?j.finish }

前面提到的“生產(chǎn)者-消費(fèi)者”和 “ready” 信號這兩種 channel 用法和本文的 channel 用法并沒有什么本質(zhì)區(qū)別。唯一不同的點(diǎn)是前者的 channel 是事先創(chuàng)建好的,并且是“公用”的;而本文中用到的 channel 實(shí)際上是“臨時(shí)”創(chuàng)建的,并且只有這一個(gè)請求使用。

最后,用曹大最近在讀者群里說的話結(jié)尾:

  • 抄代碼是很好的學(xué)習(xí)方式。

  • 選一兩個(gè)感興趣的方向,自己嘗試實(shí)現(xiàn)相應(yīng)的 feature list,實(shí)現(xiàn)完和標(biāo)準(zhǔn)實(shí)現(xiàn)做對比。

  • 先積累再創(chuàng)造,別一上來就想著造輪子,看的多了碰上很多東西就有新思路了。

  • 總結(jié)

    以上是生活随笔為你收集整理的Go channel 的妙用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。