Go语言开发小技巧&易错点100例(四)

简介: Go语言开发小技巧&易错点100例(四)

往期回顾:

本期看点(技巧类用【技】表示,易错点用【易】表示)

(1)goroutine控制并发数量的方式【技】

(2)Go发起HTTP请求【技】

(3)包循环依赖导致的异常【易】

正文如下:

1 goroutine控制并发数量的方式【技】

首先我们思考一个问题,为什么要控制goroutine的数量呢?

虽然goroutine的创建成本很低,而且占用的内存也很少,但是一旦数量没有控制,导致短时间内大量的goroutine同时执行也会造成内存崩溃、CPU占用率过高等问题,因此我们在生产级的项目中一定要注意控制好goroutine的数量,以免发生生产事故。

那么,我们都有哪些方式来控制goroutine的数量呢?

  • sync.WaitGroup
  • channel
  • sync.WaitGroup+channel
  • semaphore

(1)最简单的方式

func main() {
   group := sync.WaitGroup{}
   group.Add(3)
   for i := 0; i < 3; i++ {
      go func() {
         fmt.Println("hello...")
         group.Done()
      }()
   }
   group.Wait()
}
复制代码

这种方式非常的简单,但是弊端就是不容易灵活扩展

(2)sync.WaitGroup+channel方式

type Pool struct {
   queue chan int
   wg    *sync.WaitGroup
}
func New(size int) *Pool {
   if size <= 0 {
      size = 1
   }
   return &Pool{
      queue: make(chan int, size),
      wg:    &sync.WaitGroup{},
   }
}
func (p *Pool) Add(delta int) {
   for i := 0; i < delta; i++ {
      p.queue <- 1
   }
   for i := 0; i > delta; i-- {
      <-p.queue
   }
   p.wg.Add(delta)
}
func (p *Pool) Done() {
   <-p.queue
   p.wg.Done()
}
func (p *Pool) Wait() {
   p.wg.Wait()
}
复制代码

测试:

func main() {
   pool := pool.New(10)
   for i := 0; i < 100; i++ {
      pool.Add(1)
      go func() {
         time.Sleep(time.Second)
         fmt.Printf("%d hello...\n", i)
         pool.Done()
      }()
   }
   pool.Wait()
}
复制代码

2 Go发起HTTP请求【技】

服务端:

type Student struct {
   Name string
   Age  int
}
func HttpServe() {
   /**
   URL:http://localhost:8080
   Method:Get
    */
   http.HandleFunc("/get", func(w http.ResponseWriter, r *http.Request) {
      str := r.URL.Query().Get("str")
      fmt.Println("Get Method Str is " + str)
      w.Write([]byte("Hello Http Get!"))
   })
   /**
   URL:http://localhost:8080
      Method:Get
      Param:str
    */
   http.HandleFunc("/get/form", func(w http.ResponseWriter, r *http.Request) {
      name := r.URL.Query().Get("name")
      age := r.URL.Query().Get("age")
      ageStr, err := strconv.Atoi(age)
      if err != nil {
         fmt.Println("err...")
      }
      stu := Student{Name: name, Age: ageStr}
      fmt.Println("Get Method Str is ", stu)
      w.Write([]byte("Hello Http Get Form!"))
   })
   /**
   URL:http://localhost:8080
      Method:Get
      Param:str
    */
   http.HandleFunc("/get/json", func(w http.ResponseWriter, r *http.Request) {
      fmt.Println("req method : ", r.Method)
      body, err := io.ReadAll(r.Body)
      if err != nil {
         fmt.Printf("获取请求体错误 , %v\n", err)
         return
      }
      fmt.Println("请求体 :", string(body))
      var stu Student
      if err = json.Unmarshal(body, &stu); err != nil {
         fmt.Printf("反序列化失败 , %v\n", err)
         return
      }
      fmt.Printf("反序列化成功,JSON解析结果 %+v", stu)
      w.Write([]byte("Hello Http Get Form!"))
   })
   err := http.ListenAndServe(":8080", nil)
   if err != nil {
      fmt.Println(err)
   }
}
复制代码

Go发送Http请求:

func HttpGet() {
   resp, err := http.Get("http://localhost:8080/get?str=ymx") // url
   if err != nil {
      fmt.Printf("get请求失败 error: %+v", err)
      return
   }
   defer resp.Body.Close()
   body, err := io.ReadAll(resp.Body)
   if err != nil {
      fmt.Printf("读取Body失败 error: %+v", err)
      return
   }
   fmt.Println(string(body))
}
func HttpPost() {
   resp, err := http.PostForm("http://localhost:8080/form", 
      url.Values{
         "name": {"jack"}, 
      })
   if err != nil {
      fmt.Printf("postForm请求失败 error: %+v", err)
      return
   }
   defer resp.Body.Close()
   body, err := io.ReadAll(resp.Body)
   if err != nil {
      fmt.Printf("读取Body失败 error: %+v", err)
      return
   }
   fmt.Println(string(body))
}
复制代码

3 包循环依赖导致的异常【易】

循环依赖是一个在代码层面很常见的概念了,简单来说就是A依赖B,B依赖A,从而导致的先有蛋还是先有鸡的问题,下面来一个示例:


网络异常,图片无法展示
|


package_a代码:

package package_a
import (
   "encoding/json"
   "other/article5/pack/package_b"
)
func MakeStudent(stu package_b.Student) string {
   bytes, _ := json.Marshal(stu)
   return string(bytes)
}
复制代码

package_b代码:

package package_b
import "other/article5/pack/package_a"
type Student struct {
   Id   int64
   Name string
}
func (stu *Student) GetStuJSON() string {
   return package_a.MakeStudent(*stu)
}
复制代码

测试方法:

package main
import (
   "fmt"
   "other/article5/pack/package_b"
)
func main() {
   student := package_b.Student{
      Name: "zs",
   }
   str:= student.GetStuJSON()
   fmt.Println(str)
}
复制代码

执行结果:

网络异常,图片无法展示
|


如何避免循环依赖呢?

说实话没有什么特别好的办法,就是在平时写代码前先做好设计,设计好每一层的依赖关系,尽量不要产生额外的循环依赖即可。


相关文章
|
4月前
|
JSON 中间件 Go
Go 网络编程:HTTP服务与客户端开发
Go 语言的 `net/http` 包功能强大,可快速构建高并发 HTTP 服务。本文从创建简单 HTTP 服务入手,逐步讲解请求与响应对象、URL 参数处理、自定义路由、JSON 接口、静态文件服务、中间件编写及 HTTPS 配置等内容。通过示例代码展示如何使用 `http.HandleFunc`、`http.ServeMux`、`http.Client` 等工具实现常见功能,帮助开发者掌握构建高效 Web 应用的核心技能。
250 61
|
2月前
|
数据采集 Go API
Go语言实战案例:多协程并发下载网页内容
本文是《Go语言100个实战案例 · 网络与并发篇》第6篇,讲解如何使用 Goroutine 和 Channel 实现多协程并发抓取网页内容,提升网络请求效率。通过实战掌握高并发编程技巧,构建爬虫、内容聚合器等工具,涵盖 WaitGroup、超时控制、错误处理等核心知识点。
|
2月前
|
数据采集 JSON Go
Go语言实战案例:实现HTTP客户端请求并解析响应
本文是 Go 网络与并发实战系列的第 2 篇,详细介绍如何使用 Go 构建 HTTP 客户端,涵盖请求发送、响应解析、错误处理、Header 与 Body 提取等流程,并通过实战代码演示如何并发请求多个 URL,适合希望掌握 Go 网络编程基础的开发者。
|
3月前
|
JSON 前端开发 Go
Go语言实战:创建一个简单的 HTTP 服务器
本篇是《Go语言101实战》系列之一,讲解如何使用Go构建基础HTTP服务器。涵盖Go语言并发优势、HTTP服务搭建、路由处理、日志记录及测试方法,助你掌握高性能Web服务开发核心技能。
|
3月前
|
Go
如何在Go语言的HTTP请求中设置使用代理服务器
当使用特定的代理时,在某些情况下可能需要认证信息,认证信息可以在代理URL中提供,格式通常是:
261 0
|
4月前
|
JSON 编解码 API
Go语言网络编程:使用 net/http 构建 RESTful API
本章介绍如何使用 Go 语言的 `net/http` 标准库构建 RESTful API。内容涵盖 RESTful API 的基本概念及规范,包括 GET、POST、PUT 和 DELETE 方法的实现。通过定义用户数据结构和模拟数据库,逐步实现获取用户列表、创建用户、更新用户、删除用户的 HTTP 路由处理函数。同时提供辅助函数用于路径参数解析,并展示如何设置路由器启动服务。最后通过 curl 或 Postman 测试接口功能。章节总结了路由分发、JSON 编解码、方法区分、并发安全管理和路径参数解析等关键点,为更复杂需求推荐第三方框架如 Gin、Echo 和 Chi。
|
5月前
|
分布式计算 Go C++
初探Go语言RPC编程手法
总的来说,Go语言的RPC编程是一种强大的工具,让分布式计算变得简单如同本地计算。如果你还没有试过,不妨挑战一下这个新的编程领域,你可能会发现新的世界。
124 10
|
5月前
|
人工智能 缓存 安全
Go开发遇见的一次Data Race
本文通过一段 Go 语言代码示例,分析了并发编程中的数据竞争(Data Race)问题。代码实现了一个带缓存的内存存储系统,包含 `LRUCache` 和 `MemoryCache` 两个核心组件。尽管在 `MemoryCache` 的 `Set` 方法中加了锁保护,但由于直接调用 `LRUCache` 的 `GetLength` 方法时未加锁,导致底层数据结构在多 goroutine 环境下被同时读写,从而触发 Data Race。文章详细解析了问题根源,并提出了解决方案:为 `LRUCache` 的 `Add` 方法添加锁保护,确保并发安全。
|
8月前
|
存储 缓存 安全
Go 语言中的 Sync.Map 详解:并发安全的 Map 实现
`sync.Map` 是 Go 语言中用于并发安全操作的 Map 实现,适用于读多写少的场景。它通过两个底层 Map(`read` 和 `dirty`)实现读写分离,提供高效的读性能。主要方法包括 `Store`、`Load`、`Delete` 等。在大量写入时性能可能下降,需谨慎选择使用场景。
|
11月前
|
存储 负载均衡 监控
如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
在数字化时代,构建高可靠性服务架构至关重要。本文探讨了如何利用Go语言的高效性、并发支持、简洁性和跨平台性等优势,通过合理设计架构、实现负载均衡、构建容错机制、建立监控体系、优化数据存储及实施服务治理等步骤,打造稳定可靠的服务架构。
223 1