golang 限制同一時(shí)間的并發(fā)量操作
go的并發(fā)量是很厲害的,goroutine創(chuàng)建的代價(jià)極小,其中一個(gè)重要的原因是因?yàn)間o采用了分段棧技術(shù),每一個(gè)goroutine只占極小的空間。與此同時(shí),goroutine是語言層面的,減少了內(nèi)核態(tài)到用戶態(tài)的切換開銷,并且goroutine摒棄了一些golang用不到的一些os thread的系統(tǒng)調(diào)用,創(chuàng)建代價(jià)小。
我們可以一瞬間創(chuàng)建很多個(gè)goroutine,這是相當(dāng)容易的。
乍一看,這與題目完全不符,前面說了那么多,難道不是鼓勵(lì)我們多創(chuàng)建goroutine嗎?不不不,goroutine確實(shí)很好用,但是如果不加以限制,很有可能出現(xiàn)其他的不可預(yù)料的錯(cuò)誤。
比如在web領(lǐng)域中, 一個(gè)連接,在linux/unix下就相當(dāng)于是打開了一個(gè)文件,占用一個(gè)文件描述符。但是系統(tǒng)會(huì)規(guī)定文件描述符的上限,我們可以使用ulimit -n來進(jìn)行查看,如果我們遵循量大就好的話,那么一擁而上的請(qǐng)求連接會(huì)瞬間報(bào)錯(cuò)。
2018/06/30 10:09:54 dial tcp :8080: socket: too many open files
上面這條報(bào)錯(cuò)信息源于我寫的一個(gè)循環(huán)請(qǐng)求的工具
package main import ( "sync" "net" "strconv" "fmt" "log" ) const ( MAX_CONCURRENCY = 10000 ) var waitGroup sync.WaitGroup func main(){ concurrency() waitGroup.Wait() } //進(jìn)行網(wǎng)絡(luò)io func request(currentCount int){ fmt.Println("request" + strconv.Itoa(currentCount) + "\r") conn, err := net.Dial("tcp",":8080") if err != nil { log.Fatal(err) } defer conn.Close() defer waitGroup.Done() } //并發(fā)請(qǐng)求 func concurrency(){ for i := 0;i < MAX_CONCURRENCY;i++ { waitGroup.Add(1) go request(i) } }
用go建立一個(gè)服務(wù)端很簡(jiǎn)單,我這里簡(jiǎn)單的貼下server的代碼
package main import ( "io" "os" "fmt" "net" ) func checkErr(err error){ if err != nil { fmt.Fprintln(os.Stderr, err) } } func main() { listener, err := net.Listen("tcp",":8080") checkErr(err) for { conn, err := listener.Accept() checkErr(err) go func(conn net.Conn){ _, err := io.WriteString(conn, "welcome!") checkErr(err) defer conn.Close() }(conn) } }
現(xiàn)在回到主題,我們可以看到一擁而上其實(shí)也有壞處,想要解決這一問題,我們可以限制同一時(shí)間的并發(fā)數(shù)量,可以利用channel來達(dá)到這一點(diǎn),這有點(diǎn)類似于信號(hào)量(Semaphore)
創(chuàng)建一個(gè)帶緩存的channel,其中CHANNEL_CACHE為同一時(shí)間的最大并發(fā)量
想簡(jiǎn)單的說一下為什么這里chan的類型要用一個(gè)空的struct,這是因?yàn)樵谶@個(gè)場(chǎng)景下(限制同一時(shí)間的并發(fā)量),通過channel傳輸?shù)臄?shù)據(jù)的類型并不重要,我們只需要通過做一個(gè)通知效果就行了(就像你通知你朋友起床,你只用閃個(gè)電話,而不用實(shí)際的接通,省去了電話費(fèi)的開銷),這里的空的struct實(shí)際上是不占任何空間的,因此這里選用空的struct
const ( CHANNEL_CACHE = 200 ) var tmpChannel = make(chan struct{}, CHANNEL_CACHE)
在與服務(wù)器建立連接的地方這樣寫(是不是很類似于信號(hào)量)
tmpChan <- struct{}{} conn, err := net.Dial("tcp",":8080") <- tmpChan
這樣同一時(shí)間的并發(fā)量就由CHANNEL_CACHE限制下來
經(jīng)過循環(huán)開啟的goroutine在請(qǐng)求服務(wù)器之前會(huì)向channel發(fā)送消息,如果緩存滿了,那么說明已經(jīng)有CHANNEL_CACHE個(gè)goroutine在進(jìn)行與服務(wù)器的連接,接著就會(huì)阻塞在這里,等待其中一個(gè)goroutine處理完之后,從channel中讀出一個(gè)空的struct,這時(shí)阻塞的地方向channel發(fā)送一個(gè)空struct,就可以與服務(wù)器建立連接了
下面貼一下全部的代碼
package main import ( "sync" "net" "strconv" "fmt" "log" ) const ( MAX_CONCURRENCY = 10000 CHANNEL_CACHE = 200 ) var tmpChan = make(chan struct{}, MAX_CONCURRENCY) var waitGroup sync.WaitGroup func main(){ concurrency() waitGroup.Wait() } //進(jìn)行網(wǎng)絡(luò)io func request(currentCount int){ fmt.Println("request" + strconv.Itoa(currentCount) + "\r") tmpChan <- struct{}{} conn, err := net.Dial("tcp",":8080") <- tmpChan if err != nil { log.Fatal(err) } defer conn.Close() defer waitGroup.Done() } //并發(fā) func concurrency(){ for i := 0;i < MAX_CONCURRENCY;i++ { waitGroup.Add(1) go request(i) } }
這樣就可以愉快的進(jìn)行并發(fā)了!??!
補(bǔ)充:Golang限制N個(gè)并發(fā)同時(shí)運(yùn)行
我就廢話不多說了,大家還是直接看代碼吧~
package main import ( "fmt" "sync" "time" ) var wg sync.WaitGroup func main() { var wg sync.WaitGroup sem := make(chan struct{}, 2) // 最多允許2個(gè)并發(fā)同時(shí)執(zhí)行 taskNum := 10 for i := 0; i < taskNum; i++ { wg.Add(1) go func(id int) { defer wg.Done() sem <- struct{}{} // 獲取信號(hào) defer func() { <-sem }() // 釋放信號(hào) // do something for task time.Sleep(time.Second * 2) fmt.Println(id, time.Now()) }(i) } wg.Wait() }
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
使用go語言解析xml的實(shí)現(xiàn)方法(必看篇)
下面小編就為大家?guī)硪黄褂胓o語言解析xml的實(shí)現(xiàn)方法(必看篇)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-06-06一文詳細(xì)談?wù)凣oLang的panic和error
說是初識(shí),并不是說第一次使用error和panic包,而是第一次特地去了解golang中的這兩個(gè)機(jī)制,下面這篇文章主要給大家介紹了關(guān)于如何通過一文詳細(xì)談?wù)凣oLang中panic和error的相關(guān)資料,需要的朋友可以參考下2022-12-12golang函數(shù)的返回值實(shí)現(xiàn)
本文主要介紹了golang函數(shù)的返回值實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03通過手機(jī)案例理解Go設(shè)計(jì)模式之裝飾器模式的功能屬性
這篇文章主要為大家介紹了Go設(shè)計(jì)模式之裝飾器模式的功能屬性,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05