深度探讨 Golang 中并发发送 HTTP 请求的最佳技术

  • 💂 个人网站:【 海拥】【神级代码资源网站】【办公神器】
  • 🤟 基于Web端打造的:👉轻量化工具创作平台
  • 💅 想寻找共同学习交流的小伙伴,请点击【全栈技术交流群】

在这里插入图片描述

在 Golang 领域,并发发送 HTTP 请求是优化 Web 应用程序的一项重要技能。本文探讨了实现此目的的各种方法,从基本的 goroutine 到涉及通道和sync.WaitGroup 的高级技术。我们将深入研究并发环境中性能和错误处理的最佳实践,为你提供提高 Go 应用程序速度和可靠性的策略。让我们深入探讨 Golang 中并发 HTTP 请求的世界!

使用 Goroutines 的基本方法

当谈到在 Golang 中实现并发时,最直接的方法是使用 goroutine。这些是 Go 中并发的构建块,提供了一种简单而强大的并发执行函数的方法。

Goroutine 入门

要启动一个 goroutine,只需在函数调用前加上go关键字即可。这会将函数作为 goroutine 启动,从而允许主程序继续独立运行。这就像开始一项任务并继续前进而不等待它完成。

例如,考虑发送 HTTP 请求的场景。通常,你会调用类似 的函数sendRequest(),并且你的程序将等待该函数完成。使用 goroutine,你可以同时执行此操作:

go sendRequest("http://example.com")

处理多个请求

假设你有一个 URL 列表,并且需要向每个 URL 发送一个 HTTP 请求。如果没有 goroutine,你的程序将一个接一个地发送这些请求,这非常耗时。使用 goroutine,你几乎可以同时发送它们:

urls := []string{"http://example.com", "http://another.com", ...}  
for _, url := range urls {  
go sendRequest(url)  
}

这个循环为每个 URL 启动一个新的 goroutine,大大减少了程序发送所有请求所需的时间。

并发 HTTP 请求的方法

在本节中,我们将深入研究在 Go 中并发处理 HTTP 请求的各种方法。每种方法都有其独特的特点,了解这些可以帮助你选择适合特定需求的正确方法。

我们使用 insrequester 包(开源请求程序)来处理本文中提到的 HTTP请求

基本 Goroutine

在 Go 中并发发送 HTTP 请求的最简单方法是使用 goroutine。Goroutines 是由 Go 运行时管理的轻量级线程。这是一个基本示例:

requester := insrequester.NewRequester().Load()  
  
urls := []string{"http://example.com", "http://example.org", "http://example.net"}  
for _, url := range urls {  
go requester.Get(insrequester.RequestEntity{Endpoint: url})  
}  
  
time.Sleep(2 * time.Second) // 等待 goroutine 完成

这种方法很简单,但一旦启动就缺乏对 goroutine 的控制。通过这种方式无法获取Get方法的返回值。你需要睡眠大约一段时间来等待所有 goroutine。即使你调用 sleep,你可能仍然不确定它们是否完成。

WaitGroup

为了改进基本的 goroutine,sync.WaitGroup可用于更好的同步。它等待 goroutine 集合完成执行:

requester := insrequester.NewRequester().Load()  
wg := sync.WaitGroup{}  
  
urls := []string{"http://example.com", "http://example.org", "http://example.net"}  
wg.Add(len(urls))  
  
for _, url := range urls {  
go requester.Get(insrequester.RequestEntity{Endpoint: url})  
}  
  
wg.Wait() //等待所有要完成的 goroutine

这确保了 main 函数等待所有 HTTP 请求完成。

Channels

Channels 是 Go 中用于 goroutine 之间通信的强大功能。它们可用于从多个 HTTP 请求收集数据:

requester := insrequester.NewRequester().Load()  
  
urls := []string{"http://example.com", "http://example.org", "http://example.net"}  
ch := make(chan string, len(urls))  
  
for _, url := range urls {  
go func() {  
res, _ := requester.Get(insrequester.RequestEntity{Endpoint: url})  
ch <- fmt.Sprintf("%s: %d", url, res.StatusCode)  
}()  
}  
  
for range urls {  
response := <-ch  
fmt.Println(response)  
}

通道不仅可以同步 goroutine,还可以促进它们之间的数据传递。

Worker Pools

Worker Pool 是一种模式,其中创建固定数量的工作人员(goroutines)来处理可变数量的任务。这有助于限制并发 HTTP 请求的数量,从而防止资源耗尽。

以下是在 Go 中实现 Worker Pool 的方法:

// 定义 Job 结构体,包含一个 URL 字段
type Job struct {
	URL string
}

// worker 函数用于处理作业,接收请求者、作业通道、结果通道和等待组作为参数
func worker(requester *insrequester.Request, jobs <-chan Job, results chan<- *http.Response, wg *sync.WaitGroup) {
	for job := range jobs {
		// 使用请求者获取 URL 对应的响应
		res, _ := requester.Get(insrequester.RequestEntity{Endpoint: job.URL})
		// 将结果发送到结果通道,并减少等待组计数
		results <- res
		wg.Done()
	}
}

func main() {
	// 创建并加载请求者
	requester := insrequester.NewRequester().Load()

	// 定义要处理的 URL 列表
	urls := []string{"http://example.com", "http://example.org", "http://example.net"}
	// 定义工作池中的工作者数量
	numWorkers := 2

	// 创建作业通道和结果通道
	jobs := make(chan Job, len(urls))
	results := make(chan *http.Response, len(urls))
	var wg sync.WaitGroup

	// 启动工作者
	for w := 0; w < numWorkers; w++ {
		go worker(requester, jobs, results, &wg)
	}

	// 将作业发送到工作者池
	wg.Add(len(urls))
	for _, url := range urls {
		jobs <- Job{URL: url}
	}
	close(jobs)
	wg.Wait()

	// 收集结果并输出
	for i := 0; i < len(urls); i++ {
		fmt.Println(<-results)
	}
}

使用工作池可以让你有效地管理大量并发 HTTP 请求。它是一个可扩展的解决方案,可以根据工作负载和系统容量进行调整,从而优化资源利用率并提高整体性能。

使用通道限制 Goroutine

该方法使用通道创建类似信号量的机制来限制并发 goroutine 的数量。它在你需要限制 HTTP 请求以避免服务器不堪重负或达到速率限制的情况下非常有效。

以下是实现它的方法:

// 创建请求者并加载配置
requester := insrequester.NewRequester().Load()

// 定义要处理的 URL 列表
urls := []string{"http://example.com", "http://example.org", "http://example.net"}
maxConcurrency := 2 // 限制并发请求的数量

// 创建一个用于限制并发请求的通道
limiter := make(chan struct{}, maxConcurrency)

// 遍历 URL 列表
for _, url := range urls {
    limiter <- struct{}{} // 获取一个令牌。在这里等待令牌从限制器释放
    go func(url string) {
        defer func() { <-limiter }() // 释放令牌
        // 使用请求者进行 POST 请求
        requester.Post(insrequester.RequestEntity{Endpoint: url})
    }(url)
}

// 等待所有 goroutine 完成
for i := 0; i < cap(limiter); i++ {
    limiter <- struct{}{}
}

在这种情况下使用延迟至关重要。如果将 <-limiter语句放在 Post 方法之后,并且 Post 方法触发恐慌或类似异常,则 <-limiter行将不会被执行。这可能会导致无限等待,因为信号量令牌永远不会被释放,最终导致超时问题。

使用信号量限制 Goroutines

sync/semaphore 包提供了一种干净有效的方法来限制并发运行的 goroutine 数量。当你想要更系统地管理资源分配时,此方法特别有用。

// 创建请求者并加载配置
requester := insrequester.NewRequester().Load()

// 定义要处理的 URL 列表
urls := []string{"http://example.com", "http://example.org", "http://example.net"}
maxConcurrency := int64(2) // 设置最大并发请求数量

// 创建一个带权重的信号量
sem := semaphore.NewWeighted(maxConcurrency)
ctx := context.Background()

// 遍历 URL 列表
for _, url := range urls {
    // 在启动 goroutine 前获取信号量权重
    if err := sem.Acquire(ctx, 1); err != nil {
       fmt.Printf("无法获取信号量:%v\n", err)
       continue
    }

    go func(url string) {
       defer sem.Release(1) // 在完成时释放信号量权重
       // 使用请求者获取 URL 对应的响应
       res, _ := requester.Get(insrequester.RequestEntity{Endpoint: url})
       fmt.Printf("%s: %d\n", url, res.StatusCode)
    }(url)
}

// 等待所有 goroutine 释放它们的信号量权重
if err := sem.Acquire(ctx, maxConcurrency); err != nil {
    fmt.Printf("等待时无法获取信号量:%v\n", err)
}

与手动管理通道相比,这种使用信号量包的方法提供了一种更加结构化和可读的并发处理方式。当处理复杂的同步要求或需要更精细地控制并发级别时,它特别有用。

那么,最好的方法是什么?

在探索了 Go 中处理并发 HTTP 请求的各种方法之后,问题出现了:最好的方法是什么?正如软件工程中经常出现的情况一样,答案取决于应用程序的具体要求和约束。让我们考虑确定最合适方法的关键因素:

评估你的需求

  • 请求规模:如果你正在处理大量请求,工作池或基于信号量的方法可以更好地控制资源使用。
  • 错误处理:如果强大的错误处理至关重要,那么使用通道或信号量包可以提供更结构化的错误管理。
  • 速率限制:对于需要遵守速率限制的应用程序,使用通道或信号量包限制 goroutine 可能是有效的。
  • 复杂性和可维护性:考虑每种方法的复杂性。虽然渠道提供了更多控制,但它们也增加了复杂性。另一方面,信号量包提供了更直接的解决方案。

错误处理

由于 Go 中并发执行的性质,goroutines 中的错误处理是一个棘手的话题。由于 goroutine 独立运行,管理和传播错误可能具有挑战性,但对于构​​建健壮的应用程序至关重要。以下是一些有效处理并发 Go 程序中错误的策略:

集中误差通道

一种常见的方法是使用集中式错误通道,所有 goroutine 都可以通过该通道发送错误。然后,主 goroutine 可以监听该通道并采取适当的操作。

func worker(errChan chan<- error) {
    // 执行任务
    if err := doTask(); err != nil {
        errChan <- err // 将任何错误发送到错误通道
    }
}

func main() {
    errChan := make(chan error, 1) // 用于存储错误的缓冲通道

    go worker(errChan)

    if err := <-errChan; err != nil {
        // 处理错误
        log.Printf("发生错误:%v", err)
    }
}

或者你可以在不同的 goroutine 中监听 errChan。

func worker(errChan chan<- error, job Job) {
 // 执行任务
 if err := doTask(job); err != nil {
  errChan <- err // 将任何错误发送到错误通道
 }
}

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 完成(具体方式需要根据代码的实际情况进行实现)
 done <- struct{}{} // 通知 goroutine 停止监听错误
}

Error Group

golang.org/x/sync/errgroup 包提供了一种便捷的方法来对多个 goroutine 进行分组并处理它们产生的任何错误。errgroup.Group确保一旦任何 goroutine 发生错误,所有后续操作都将被取消。

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 {
            // 替换为实际的 HTTP 请求逻辑
            _, err := fetchURL(ctx, url)
            return err
        })
    }

    // 等待所有请求完成
    if err := g.Wait(); err != nil {
        log.Printf("发生错误:%v", err)
    }
}

这种方法简化了错误处理,特别是在处理大量 goroutine 时。

包装 Goroutine

另一种策略是将每个 goroutine 包装在一个处理其错误的函数中。这种封装可以包括从恐慌或其他错误管理逻辑中恢复。

func work() error {
  // 进行一些工作
  return err
}

func main() {
 go func() {
   err := work()
   if err != nil {
     // 处理错误
   }
 }()

 // 等待工作完成的某种方式
}

综上所述,Go 并发编程中错误处理策略的选择取决于应用程序的具体要求和上下文。无论是通过集中式错误通道、专用错误处理 goroutine、使用错误组,还是将 goroutine 包装在错误管理函数中,每种方法都有自己的优点和权衡。

总结

总之,本文探讨了在 Golang 中并发发送 HTTP 请求的各种方法,这是优化 Web 应用程序的一项关键技能。我们已经讨论了基本的 goroutine、sync.WaitGroup、通道、工作池以及限制 goroutine 的方法。每种方法都有其独特的特点,可以根据特定的应用要求进行选择。

此外,本文还强调了并发 Go 程序中错误处理的重要性。管理并发环境中的错误可能具有挑战性,但对于构建健壮的应用程序至关重要。已经讨论了使用集中式错误通道、errgroup 包或使用错误处理逻辑包装 goroutine 等策略来帮助开发人员有效地处理错误。

最终,在 Go 中处理并发 HTTP 请求的最佳方法的选择取决于请求规模、错误处理要求、速率限制以及代码的整体复杂性和可维护性等因素。开发人员在应用程序中实现并发功能时应仔细考虑这些因素。

⭐️ 好书推荐

《Go专家编程(第2版)》

在这里插入图片描述

【内容简介】

本书深入地讲解了Go语言常见特性的内部机制和实现方式,大部分内容源自对Go源码的分析,并从中提炼出实现原理。通过阅读本书,读者可以快速、轻松地了解Go语言的内部运作机制。

本书首先介绍常见数据结构及控制结构的实现原理,包括管道、切片、Hash表、select 和 for-range 等,这部分内容大都以几个精心准备的测验题目开头,每个测验题目均对应一个知识点,读者借此可以测验自身对该知识点的掌握程度。接着介绍了Go语言最核心的概念,包括协程的概念、协程调度模型、协程调度策略,以及内存分配和垃圾回收相关的内容。本书还介绍了测试、泛型、依赖管理等比较实用的特性。最后结合笔者的见闻,整理了一些发生在真实项目中的编程陷阱。

📚 京东购买链接:《Go专家编程(第2版)》

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/322877.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java学习(十八)--网络编程

介绍 需求 如何准确地定位网络上一台或多台主机&#xff1b; 定位主机上的特定的应用 找到主机后如何可靠高效地进行数据传输 目的 直接或间接地通过网络协议与其它计算机实现数据交换&#xff0c;进行通讯&#xff1b; 网络通信 网络&#xff1a;两台或多台设备通过一…

详解SpringCloud微服务技术栈:Nacos服务搭建及服务分级存储模型

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位大四、研0学生&#xff0c;正在努力准备大四暑假的实习 &#x1f30c;上期文章&#xff1a;详解SpringCloud微服务技术栈&#xff1a;强推&#xff01;源码跟踪分析Ribbon负载均衡原理、Eureka服务部署 &#x1f4da;订阅…

MIT 6s081 lab 1.Xv6 and Unix utilities

Lab1: Xv6 and Unix utilities 作业网址&#xff1a;https://pdos.csail.mit.edu/6.828/2020/labs/util.html Boot xv6(easy) 下载&#xff0c;启动xv6系统 $ git clone git://g.csail.mit.edu/xv6-labs-2020 Cloning into xv6-labs-2020... ... $ cd xv6-labs-2020 $ git …

解决jmeter响应乱码的问题

HTTP请求响应乱码 方法一&#xff1a;添加后置处理器BeanShell PostProcessor&#xff0c;写入【prev.setDataEncoding("utf-8")】 方法二&#xff1a;修改bin目录下的配置文件jmeter.properties&#xff0c;将配置修改为【sampleresult.default.encodingUTF-8】 J…

【LeetCode每日一题】82. 删除排序链表中的重复元素 II

2024-1-15 文章目录 [82. 删除排序链表中的重复元素 II](https://leetcode.cn/problems/remove-duplicates-from-sorted-list-ii/) 82. 删除排序链表中的重复元素 II 思路&#xff1a; 创建一个虚拟节点 dummy 作为头节点的前置节点。使用两个指针 pre 和 cur 分别指向前一个非…

光纤和光缆有何不同之处?

很多人会有这样的疑问&#xff0c;光纤和光缆有何不同之处&#xff1f;主要是因为光纤和光缆这两个名词容易引起混淆。在严格的定义下&#xff0c;光纤和光缆是两种不同的东西&#xff0c;然而在现实生活中&#xff0c;许多人仍然会混淆这两者。为了更好地理解光纤和光缆之间的…

全国博物馆数据, shp+excel数据,数据来源可靠,基于国家文物局发布的《2021年度全国博物馆名录》

数据名称: 全国博物馆数据 数据格式: shpexcel 数据几何类型: 点 数据坐标系: WGS84 数据来源&#xff1a;网络公开数据&#xff0c;数据名录来源于国家文物局发布的《2021年度全国博物馆名录》 数据字段&#xff1a; 序号字段名称字段说明1province省份名称2city城市名…

GoZero微服务个人探索之路(三)Go-Zero官方rpc demo示例探究

官方网址&#xff1a;https://go-zero.dev/docs/tasks/cli/grpc-demo 项目结构 demo包 两个文件均为protoc-gen-go-grpc自动生成构成一个完整的 gRPC 服务的定义和实现 democlient包 demo.go goctl生成的客户端代码 Request 和 Response 别名&#xff1a; 定义了 Request 和…

知识付费saas租户平台:揭秘成功的密码

明理信息科技知识付费saas租户平台 随着互联网的快速发展&#xff0c;人们越来越重视知识的获取和价值的挖掘。在这个信息爆炸的时代&#xff0c;知识付费已经成为了一种新的商业模式&#xff0c;为知识的传播和价值的转化提供了更加高效和便捷的途径。本文将探讨知识付费的发…

UI设计入门:解析用户界面的基础概念

UI设计是什么&#xff1a;用户界面设计的基础 用户的视觉体验。一个好的用户界面需要强大、可靠和良好的使用感。用户界面设计应尽量减少用户与产品互动的能量&#xff0c;使用户更容易实现目标。 以我们最熟悉的应用程序界面为例。通常&#xff0c;手机软件的UI用户界面设计…

LeetCode刷题---基本计算器

解题思路&#xff1a; 根据题意&#xff0c;字符串中包含的运算符只有和- 使用辅助栈的方法来解决该问题 定义结果集res和符号位sign(用于判断对下一数的加减操作),接着对字符串进行遍历。 如果当前字符为数字字符&#xff0c;判断当前字符的下一个字符是否也是数字字符&#x…

样式集-发布,发表页面完整代码及示意图

发表页面完整代码示意图 完整代码 wxml代码&#xff1a; <!--pages/publish/publish.wxml--> <view class"biaoqing" catchtap"showEmoji"><text>&#x1f60a;</text></view> <view class"fabiao" catchtap…

Jmeter分布式性能测试

在做后端服务器性能测试中&#xff0c;我们会经常听到分布式。哪你&#xff0c;是否了解分布式呢&#xff1f;今天&#xff0c;我们就来给大家讲讲&#xff0c;在企业实战中&#xff0c;如何使用分布式进行性能测试&#xff0c;实战过程中&#xff0c;又有哪些地方要特别注意&a…

引领安全创新 | 安全狗方案入选工信部《2023年工业和信息化领域数据安全典型案例名单》

近日&#xff0c;工业和信息化部网络安全管理局公布了2023年工业和信息化领域数据安全典型案例名单。 安全狗与厦门卫星定位应用股份有限公司、中移 (上海) 信息通信科技有限公司联合申报的智慧交通云数据安全与隐私保障典型案例也成功入选。 厦门服云信息科技有限公司&#xf…

感知器学习算法和Adaline规则

一.感知器的发展过程 感知器的发展可以追溯到20世纪50年代。它是一种简单的人工神经网络模型&#xff0c;最早由美国心理学家和计算机科学家弗兰克罗森布拉特&#xff08;Frank Rosenblatt&#xff09;于1957年提出。感知器的设计灵感来源于生物神经元的工作原理&#xff0c;旨…

Oracle全系列版本官网下载保姆及教程

Oracle全系列版本官网下载方法 下面以下载Oracle12cR2为例说明下载的整个过程。 基本步骤如下&#xff1a; 先注册一个Oracle账号并登录&#xff1b;进入到客户下载页面搜索要下载的数据库版本&#xff1b;得到Oracle下载器(Oracle_SSN_DML_xxxxx.exe)&#xff0c;注意&#xf…

Docker之Dockerfile构建镜像

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《Docker之Dockerfile构建镜像》。&#x1f3af;&…

Shiro框架:ShiroFilterFactoryBean过滤器源码解析

目录 1.Shiro自定义拦截器SpringShiroFilter 1.1 ShiroFilterFactoryBean解析 1.1.1 实现FactoryBean接口 1.1.2 实现BeanPostProcessor接口 1.2 SpringShiroFilter解析 1.2.1 OncePerRequestFilter过滤逻辑实现 1.2.2 AbstractShiroFilter过滤逻辑实现 1.2.2.1 创建Sub…

网页测试遇到自动弹窗,Alert类无法处理?或许你该来学学这招了!

相信大家在使用selenium做网页自动化时&#xff0c;会遇到如下这样的一个场景&#xff1a; 在你使用get访问某一个网址时&#xff0c;会在页面中弹出如上图所示的弹出框。 首先想到是利用Alert类来处理它。 然而&#xff0c;很不幸&#xff0c;Alert类处理的结果就是没有结果…