Go高級特性之并發(fā)處理http詳解
引言
在當(dāng)今高度互聯(lián)的世界中,Web 應(yīng)用程序的性能和響應(yīng)能力變得至關(guān)重要。大多數(shù) Web 應(yīng)用程序需要與多個外部服務(wù)進(jìn)行通信,例如數(shù)據(jù)庫、API、第三方服務(wù)等。并發(fā)發(fā)送 HTTP 請求是提高應(yīng)用程序性能并保持響應(yīng)能力的關(guān)鍵。Golang 作為一種高效的編程語言,提供了多種方法來實(shí)現(xiàn)并發(fā)發(fā)送 HTTP 請求。本文將深入探討 Golang 中并發(fā)發(fā)送 HTTP 請求的最佳技術(shù)和實(shí)踐。
使用 Goroutines 的基本方法
當(dāng)談到在 Golang 中實(shí)現(xiàn)并發(fā)時,最直接的方法是使用 routine。這些是 Go 中并發(fā)的構(gòu)建塊,提供了一種簡單而強(qiáng)大的并發(fā)執(zhí)行函數(shù)的方法。
Goroutine 入門
要啟動一個 routine,只需在函數(shù)調(diào)用前加上``關(guān)鍵字即可。這會將函數(shù)作為 routine 啟動,從而允許主程序繼續(xù)獨(dú)立運(yùn)行。這就像開始一項(xiàng)任務(wù)并繼續(xù)前進(jìn)而不等待它完成。
例如,考慮發(fā)送 HTTP 請求的場景。通常,你會調(diào)用類似 的函數(shù)sendRequest(),并且你的程序?qū)⒌却摵瘮?shù)完成。使用 routine,你可以同時執(zhí)行此操作:
package main
import (
"fmt"
"net/http"
)
func main() {
urls := []string{"http://example.com", "http://example.org"}
for _, url := range urls {
go func(url string) {
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
// 處理響應(yīng)內(nèi)容或其他邏輯
}(url)
}
// 等待所有請求完成
// ...
}
這個循環(huán)為每個 URL 啟動一個新的 routine,大大減少了程序發(fā)送所有請求所需的時間。通過創(chuàng)建多個 goroutine,每個 goroutine 發(fā)送一個 HTTP 請求,我們可以實(shí)現(xiàn)并發(fā)地發(fā)送多個請求。
并發(fā) HTTP 請求的方法
由于沒有限制 goroutine 數(shù)量,如果我們把全部任務(wù)都放到并發(fā) Goroutine 中去執(zhí)行,雖然效率比較高。但當(dāng)不加控制的 goroutine 瘋狂創(chuàng)建時候,服務(wù)器系統(tǒng)資源使用率飆升宕機(jī),直到進(jìn)程被自動 kill 不然無法提供任何其它服務(wù)。
上面的案例是我們不加控制 goroutine 數(shù)量限制從而導(dǎo)致宕機(jī)的,因此只要我們控制了 goroutine 數(shù)量就能避免這種問題!
WaitGroup
要確保所有并發(fā)請求完成后再繼續(xù)執(zhí)行后續(xù)操作,可以使用 sync.WaitGroup。sync 包提供的 WaitGroup 類型可以幫助我們等待一組并發(fā)操作的完成。
package main
import (
"fmt"
"net/http"
"sync"
)
func main() {
urls := []string{"http://example.com", "http://example.org"}
var wg sync.WaitGroup
for _, url := range urls {
wg.Add(1)
go func(url string) {
defer wg.Done()
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
// 處理響應(yīng)內(nèi)容或其他邏輯
}(url)
}
wg.Wait() // 等待所有請求完成
// ...
}
通過調(diào)用 sync.WaitGroup 的 Wait() 方法,我們可以確保所有的 goroutine 執(zhí)行完成后再繼續(xù)執(zhí)行后續(xù)操作。
Channels
我們可以使用Channels來控制并發(fā)請求數(shù)量。通過創(chuàng)建一個有容量限制的通道,可以實(shí)現(xiàn)對并發(fā)請求數(shù)量的有效控制
package main
import (
"fmt"
"net/http"
)
func main() {
urls := []string{"http://example.com", "http://example.org"}
concurrency := 5
sem := make(chan bool, concurrency)
for _, url := range urls {
sem <- true // 發(fā)送信號控制并發(fā)數(shù)量
go func(url string) {
defer func() { <-sem }() // 釋放信號
resp, err := http.Get(url)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
// 處理響應(yīng)內(nèi)容或其他邏輯
}(url)
}
// 等待所有請求完成
// ...
}
通過設(shè)置通道的容量為并發(fā)數(shù)量,我們可以確保同時發(fā)送的請求數(shù)量不超過設(shè)定的限制。這種方法對于控制并發(fā)請求數(shù)量非常有效。
Worker Pools
Worker Pools 是一種常見的并發(fā)模式,它可以有效地控制并發(fā)發(fā)送 HTTP 請求的數(shù)量。通過創(chuàng)建一組固定數(shù)量的 worker goroutines,并將請求任務(wù)分配給它們來處理,我們可以控制并發(fā)請求數(shù)量,減輕資源競爭和過載的壓力。
package main
import (
"fmt"
"net/http"
"sync"
)
type Worker struct {
ID int
Request chan string
Responses chan string
}
func NewWorker(id int) *Worker {
return &Worker{
ID: id,
Request: make(chan string),
Responses: make(chan string),
}
}
func (w *Worker) Start(wg *sync.WaitGroup) {
go func() {
defer wg.Done()
for url := range w.Request {
resp, err := http.Get(url)
if err != nil {
w.Responses <- fmt.Sprintf("Error: %s", err)
continue
}
defer resp.Body.Close()
// 處理響應(yīng)內(nèi)容或其他邏輯
w.Responses <- fmt.Sprintf("Worker %d: %s", w.ID, "Response")
}
}()
}
func main() {
urls := []string{"http://example.com", "http://example.org"}
concurrency := 5
var wg sync.WaitGroup
wg.Add(concurrency)
workers := make([]*Worker, concurrency)
for i := 0; i < concurrency; i++ {
workers[i] = NewWorker(i)
workers[i].Start(&wg)
}
go func() {
for _, url := range urls {
for _, worker := range workers {
worker.Request <- url
}
}
for _, worker := range workers {
close(worker.Request)
}
}()
for _, worker := range workers {
for response := range worker.Responses {
fmt.Println(response)
}
}
wg.Wait()
}
通過使用 Worker Pools,我們可以控制并發(fā)請求數(shù)量,并發(fā)請求的數(shù)量不超過 Worker Pools 中的 worker 數(shù)量。
更深入學(xué)習(xí)請查看探究 Go 的高級特性之 【處理1分鐘百萬請求】
使用信號量限制 Goroutines
sync/semaphore 包提供了一種干凈有效的方法來限制并發(fā)運(yùn)行的 routine 數(shù)量。當(dāng)你想要更系統(tǒng)地管理資源分配時,此方法特別有用。
package main
import (
"context"
"fmt"
"golang.org/x/sync/semaphore"
"net/http"
)
func main() {
// 創(chuàng)建請求者并加載配置
requester := http.DefaultClient
// 定義要處理的 URL 列表
urls := []string{"http://example.com", "http://example.org", "http://example.net"}
maxConcurrency := int64(2) // 設(shè)置最大并發(fā)請求數(shù)量
// 創(chuàng)建一個帶權(quán)重的信號量
sem := semaphore.NewWeighted(maxConcurrency)
ctx := context.Background()
// 遍歷 URL 列表
for _, url := range urls {
// 在啟動 goroutine 前獲取信號量權(quán)重
if err := sem.Acquire(ctx, 1); err != nil {
fmt.Printf("無法獲取信號量:%v\n", err)
continue
}
go func(url string) {
defer sem.Release(1) // 在完成時釋放信號量權(quán)重
// 使用請求者獲取 URL 對應(yīng)的響應(yīng)
res, err := requester.Get(url)
if err != nil {
fmt.Printf("請求失?。?v\n", err)
return
}
defer res.Body.Close()
fmt.Printf("%s: %d\n", url, res.StatusCode)
}(url)
}
// 等待所有 goroutine 釋放它們的信號量權(quán)重
if err := sem.Acquire(ctx, maxConcurrency); err != nil {
fmt.Printf("等待時無法獲取信號量:%v\n", err)
}
}
那么,最好的方法是什么
在探索了 Go 中處理并發(fā) HTTP 請求的各種方法之后,問題出現(xiàn)了:最好的方法是什么?正如軟件工程中經(jīng)常出現(xiàn)的情況一樣,答案取決于應(yīng)用程序的具體要求和約束。讓我們考慮確定最合適方法的關(guān)鍵因素:
評估你的需求
- 請求規(guī)模:如果你正在處理大量請求,工作池或基于信號量的方法可以更好地控制資源使用。
- 錯誤處理:如果強(qiáng)大的錯誤處理至關(guān)重要,那么使用通道或信號量包可以提供更結(jié)構(gòu)化的錯誤管理。
- 速率限制:對于需要遵守速率限制的應(yīng)用程序,使用通道或信號量包限制 routine 可能是有效的。
- 復(fù)雜性和可維護(hù)性:考慮每種方法的復(fù)雜性。雖然渠道提供了更多控制,但它們也增加了復(fù)雜性。另一方面,信號量包提供了更直接的解決方案。
錯誤處理
由于 Go 中并發(fā)執(zhí)行的性質(zhì),routines 中的錯誤處理是一個棘手的話題。由于 routine 獨(dú)立運(yùn)行,管理和傳播錯誤可能具有挑戰(zhàn)性,但對于構(gòu)建健壯的應(yīng)用程序至關(guān)重要。以下是一些有效處理并發(fā) Go 程序中錯誤的策略:
集中誤差通道
一種常見的方法是使用集中式錯誤通道,所有 routine 都可以通過該通道發(fā)送錯誤。然后,主 routine 可以監(jiān)聽該通道并采取適當(dāng)?shù)牟僮鳌?/p>
func worker(errChan chan<- error) {
// 執(zhí)行任務(wù)
if err := doTask(); err != nil {
errChan <- err // 將任何錯誤發(fā)送到錯誤通道
}
}
func main() {
errChan := make(chan error, 1) // 用于存儲錯誤的緩沖通道
go worker(errChan)
if err := <-errChan; err != nil {
// 處理錯誤
log.Printf("發(fā)生錯誤:%v", err)
}
}
或者你可以在不同的 routine 中監(jiān)聽 errChan。
func worker(errChan chan<- error, job Job) {
// 執(zhí)行任務(wù)
if err := doTask(job); err != nil {
errChan <- err // 將任何錯誤發(fā)送到錯誤通道
}
}
func listenErrors(done chan struct{}, errChan <-chan error) {
for {
select {
case err := <-errChan:
// 處理錯誤
case <-done:
return
}
}
}
func main() {
errChan := make(chan error, 1000) // 存儲錯誤的通道
done := make(chan struct{}) // 用于通知 goroutine 停止的通道
go listenErrors(done, errChan)
for _, job := range jobs {
go worker(errChan, job)
}
// 等待所有 goroutine 完成(具體方式需要根據(jù)代碼的實(shí)際情況進(jìn)行實(shí)現(xiàn))
done <- struct{}{} // 通知 goroutine 停止監(jiān)聽錯誤
}
Error Group
lang.org/x/sync/errgroup 包提供了一種便捷的方法來對多個 routine 進(jìn)行分組并處理它們產(chǎn)生的任何錯誤。errgroup.Group確保一旦任何 routine 發(fā)生錯誤,所有后續(xù)操作都將被取消。
import "golang.org/x/sync/errgroup"
func main() {
g, ctx := errgroup.WithContext(context.Background())
urls := []string{"http://example.com", "http://example.org"}
for _, url := range urls {
// 為每個 URL 啟動一個 goroutine
g.Go(func() error {
// 替換為實(shí)際的 HTTP 請求邏輯
_, err := fetchURL(ctx, url)
return err
})
}
// 等待所有請求完成
if err := g.Wait(); err != nil {
log.Printf("發(fā)生錯誤:%v", err)
}
}
這種方法簡化了錯誤處理,特別是在處理大量 routine 時。
以上就是Go高級特性之并發(fā)處理http詳解的詳細(xì)內(nèi)容,更多關(guān)于Go并發(fā)處理http的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
golang生成RSA公鑰和密鑰的實(shí)現(xiàn)方法
本文主要介紹了golang生成RSA公鑰和密鑰的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08
go語言實(shí)現(xiàn)Elasticsearches批量修改查詢及發(fā)送MQ操作示例
這篇文章主要為大家介紹了go語言實(shí)現(xiàn)Elasticsearches批量修改查詢及發(fā)送MQ操作示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04
go語言題解LeetCode1122數(shù)組的相對排序
這篇文章主要為大家介紹了go語言題解LeetCode1122數(shù)組的相對排序,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12
一文帶你了解Go中跟蹤函數(shù)調(diào)用鏈的實(shí)現(xiàn)
這篇文章主要為大家詳細(xì)介紹了go如何實(shí)現(xiàn)一個自動注入跟蹤代碼,并輸出有層次感的函數(shù)調(diào)用鏈跟蹤命令行工具,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2023-11-11
使用go實(shí)現(xiàn)常見的數(shù)據(jù)結(jié)構(gòu)
這篇文章主要介紹了使用go實(shí)現(xiàn)常見的數(shù)據(jù)結(jié)構(gòu),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
vscode上搭建go開發(fā)環(huán)境詳細(xì)完整過程
這篇文章主要給大家介紹了關(guān)于vscode上搭建go開發(fā)環(huán)境的詳細(xì)完整過程,Go語言或?qū)⒊蔀樾碌闹髁﹂_發(fā)語言,Go是google開發(fā)的一種靜態(tài)強(qiáng)類型、編譯型、并發(fā)型,并具有垃圾回收功能的編程語言,所以我們有必要學(xué)習(xí)并掌握它,需要的朋友可以參考下2023-10-10
golang實(shí)現(xiàn)通過smtp發(fā)送電子郵件的方法
這篇文章主要介紹了golang實(shí)現(xiàn)通過smtp發(fā)送電子郵件的方法,實(shí)例分析了Go語言基于SMTP協(xié)議發(fā)送郵件的相關(guān)技巧,需要的朋友可以參考下2016-07-07
如何使用go實(shí)現(xiàn)創(chuàng)建WebSocket服務(wù)器
文章介紹了如何使用Go語言和gorilla/websocket庫創(chuàng)建一個簡單的WebSocket服務(wù)器,并實(shí)現(xiàn)商品信息的實(shí)時廣播,感興趣的朋友一起看看吧2024-11-11

